woolta

webpack5 에서는 어떤것들이 변경되었을까?? (webpack5 변경점 살펴보기)

wooltaUserImgb00032 | etc | 2020-12-26

이번에는 20년 10월에 드디어 릴리즈를 거친 webpack5의 몇가지 변경점에 대해 알아보려 합니다. 이글에서는 모든 변화에 대해서는 다루지 않고 일부에 대하여 다루고 있습니다.. 기타 다른 변경점에 대해서는 https://webpack.js.org/blog/2020-10-10-webpack-5-release/ 로 들어가서 확인하실 수 있습니다. :) 기존버전에서 v5버전으로 마이그레이션을 하실 분들은 webpack5 마이그레이션 가이드 를 참고하시면 됩니다.~
또한 아래의 예시 코드는 다음 git 주소 에서 확인해보실 수 있습니다.

https://github.com/sunginHwang/webpack5-test

이번 업데이트 방향

이번 업데이트의 주요 방향성은 다음과 같습니다.

  • 영구 캐싱을 통한 빌드 성능 개선
  • 향상된 알고리즘과 기본값을 통해 장기 캐싱 개선
  • 트리 쉐이킹의 개선 및 코드 생성으로 번들 크기 개선
  • 웹 플랫폼과의 호환성 향상
  • v4에서 기능을 구현하면서 이상한 상태로 남겨진 내부 구조 정리 (v4에 대한 리팩토링?..)
  • v5에 주요 변경점을 도입하여 추후 기능 업데이트에 대비해 가능한 v5 버전을 오래 유지하도록 목표

Node.js 자동 폴리필 제거

초창기 webpack은 브라우저에서 대부분의 Node.js 모듈을 실행할 수 있도록하기 위해 사용하였으나 최근들어 대부분 모듈 사용이 주로 프론트엔드 쪽에서 많이 사용하고 있고 대부분의 폴리필 을 사용하지 않습니다. 이는 필요없는 번들사이즈만 늘리는 단점이기 때문에 Node.js 자동 폴리필은 더이상 지원하지 않고 필요한 경우 수동으로 폴리필을 추가하여 구성하도록 변경되었습니다. 그렇기 때문에 5버전부터는 node.js 모듈을 사용하려면 수동으로 폴리필을 구성해야 합니다.

중첩 트리쉐이킹 지원 (Nested tree-shaking)

기존의 webpack은 트리쉐이킹을 지원하긴 하지만 모듈이 중첩되어 사용되어진 경우는 지원하지 못하였으나 v5버전으로 업데이트 되면서 중첩된 경우에도 트리쉐이킹을 지원하도록 개선이 되었습니다.~
일단 중첩된 케이스를 테스트 하기 위해 아래와 같이 파일을 만들고 작성 하도록 하겠습니다.

테스트 폴더 구성

중첩된 테스트를 진행하기 위해 실제 사용할 값을 다른 한개의 파일에 추가로 걸쳐 사용하록 하겠습니다. 테스트 폴더는 아래 이미지와 같이 구성하고 각 파일 별 역할은 다음과 같습니다. https://image.woolta.com/3fe7f47cbf1d3d33.png

  • index.js : webpack 번들링 되는 entry 입니다. 이곳에서 중첩 모듈을 사용하도록 합니다.
  • module.js : 중첩을 위해 사용하려는 모듈입니다. 실제 index.js 에서 사용할 모듈입니다.
  • inner.js : 실제 사용되는 값은 이곳에 있지만 해당 파일은 module.js 에서 사용하도록 합니다.

테스트 파일 작성

inner.js

export const a = 1;
export const b = 2;

module.js

export * as inner from './inner';

index.js

import * as module from './module';
console.log(module.inner.a);

위의 코드를 보면 실제로 사용하는건 inner.js 이지만 중첩 테스트를 위해 가운데 module.js 가 추가되어 사용하였습니다.

webpack 4, 5 버전 별 빌드 결과

webpack 4 빌드 결과

! function(e) {
  ... // 각종 폴리필
}([function(e, t, r) {
  "use strict";
  r.r(t);
  var n = {};
  r.r(n), r.d(n, "a", (function() { // a 선언
    return o
  })), r.d(n, "b", (function() { // b 선언
    return u
  }));
  const o = 1,
    u = 2;
  console.log(n.a)
}]);

webpack 5 빌드 결과

(()=>{"use strict";console.log(1)})();

이전 버전에서는 중첩 트리쉐이킹이 지원되지 않아 a,b 둘다 선언되어있지만 5버전에서는 중첩되었어도 실제 사용하는 a 만 빌드된 것을 확인할 수 있습니다. :)

주의 사항

webpack 옵션중 minimize 옵션이 비활성화 되어있는 경우는 트리쉐이킹이 되지 않기때문에 정상작동 하지 않는다면 해당 옵션이 true 로 되어있는지 꼭 확인하세요.~!
( https://github.com/webpack/webpack/issues/11821 )

commonJS 트리 쉐이킹 지원

webpack5 에서 부터는 require 을 사용하는 commonjs 방식에서도 트리쉐이킹을 지원하도록 개선되었습니다. (기존 4버전까지는 commonJS 방식에 대해선 트리쉐이킹이 동작하지 않습니다.)

단 모든 commonjs 방식에 대해서는 지원 하지 않고 아래와 같이 일부 방식에서만 지원 (20년 11월 기준) 합니다. 추후 나머지 방식도 지원하도록 개선중이라고 합니다. :)

https://image.woolta.com/3fea39d603a60046.png

적용 가능한 commonJS 방식 예시

위의 지원가능한 방식 및 불가능한 양식을 한가지 예시로 첨부해 드립니다. (개인적으로 아래 예시중 불가능한 방식으로 사용중이여서 이부분도 얼른 지원하였으면 하는 마음입니다...)

exports 적용 예시

// 지원 방식
module.exports.sum = function(a, b) {
  return a + b;
}

// 미지원 방식
function sum(a, b) {
  console.log('[CALL] sum function');
  return a + b;
}
 
module.exports = {
  sum,
}

require 적용 예시

// 지원 방식
const sum = require('./commonJs').sum;

// 미지원 방식
const { sum } = require('./commonJs');

commonJS 트리쉐이킹 적용 예시 코드 작성

commonJS 에서의 트리쉐이킹이 제대로 동작하는지 확인하기 위해 아래 예시 코드 처럼 sum 함수와 minus 함수를 commonJS 방식으로 작성후 sum 함수만 사용하여 트리쉐이킹의 적용여부를 확인해보도록 하겠습니다. entry 는 동일하게 index.js 로 진행하도록 하겠습니다.

math.js

// 해당 함수가 빌드파일에 들어가는지 확인이 편하도록 각각 로그를 찍도록 하였습니다.

module.exports.sum = function(a, b) {
  console.log('[CALL] sum function');
  return a + b;
}
 
module.exports.minus = function(a, b) {
  console.log('[CALL] minus function');
  return a - b
}

index.js

// const { sum }  = require('./commonJs'); 방식으로 가져오게 되면 webpack5 에서도 트리쉐이킹을 지원하지 않습니다.
const sum = require('./commonJs').sum;
sum(1,2);

webpack 4, 5 버전 별 빌드 결과

webpack 4 빌드 결과

! function(e) {
  var n = {};
  ...폴러필 ...
}([function(e, n) {
  e.exports.sum = function(e, n) {
    return console.log("[CALL] sum function"), e + n // sum 함수
  }, e.exports.minus = function(e, n) {
    return console.log("[CALL] minus function"), e - n // minus 함수
  }
}, function(e, n, t) {
  "use strict";
  t.r(n);
  (0, t(0).sum)(1, 2)
}]);

webpack 5 빌드 결과

(() => {
  var r = {
      118: r => {
        r.exports.S = function(r, o) {
          return console.log("[CALL] sum function"), r + o // sum 함수
        }
      }
    },
    o = {};
  (0, function t(n) {
    if (o[n]) return o[n].exports;
    var e = o[n] = {
      exports: {}
    };
    return r[n](e, e.exports, t), e.exports
  }(118).S)(1, 2)
})();

다음과 같이 5버전에서는 sum 함수만 빌드하고 사용하지않는 minus 함수는 제거하여 commonJS 에서도 정상적으로 트리쉐이킹을 지원하는 것을 볼 수 있습니다.~!!

Entry output 개별설정

기존에는 output 에 대한 이름을 개별 설정하고 싶은 경우 정식적으로 지원히지 않아서 entry 이름을 통해 파일이름을 설정하는 등 여러가지 다른 방식을 통해 개별 output 파일을 만들고 있었습니다.

기존의 multi 아웃풋 설정을 위해 entry 이름을 통한 output 설정 하기

module.exports = {
 entry: {
   'module/math/index': './src/math.js',
   'module/inner/index': './src/inner.js',
 },
 output: {
   path: path.resolve(__dirname, 'dist'),
   filename: '[name].js'
 },
 mode: 'production',
 optimization:{
   minimize: true, // <---- disables uglify.
 }
};

// output 생성
module
ㄴ math
    ㄴ - index.js
ㄴ inner
    ㄴ - index.js

그러나 이제는 entry 속성에 filename 을 작성해 주게 되면 해당 output 이름으로 별도로 설정이 가능하도록 변경되었습니다. :)

webpack5 개별 entry 설정

module.exports = {
  entry: {
    // filename 속성에 해당 entry 가 output 될 위치 및 파일이름을 구성해 적습니다.
    math: { import: './src/math.js', filename: 'pages/math/[name].js' },  // dist/pages/math/math.js
    inner: { import: './src/inner.js', filename: 'pages/inner/[name]-2.js' } // dist/pages/inner/inner-2.js
  },
  mode: 'production',
  optimization:{
    minimize: true,
  }
};

위와 같이 설정 후 빌드를 돌리게 되면 다음과 같이 개별 output이 성공적으로 빌드된것을 볼수 있습니다.

https://image.woolta.com/3fc903e0e30f1b9c.png

entry 별 library 설정

webpack5에서 추가된 entry 관련 기능들 중 entry 별로 library 를 개별적으로 선언할 수 있도록 개선되었습니다. 잘 쓰이지는 않을 기능일 수도 있으나 entry 별 amd, umd 등을 나누어야 하는 경우가 생긴다면 해당 기능은 상당히 매력적으로 다가오실 수 있습니다. :)
이번 예시에서는 같은 파일을 각각 library type 을 다르게 선언해 해당 파일의 output 결과가 정상적으로 동작하는지 확인해보도록 하겠습니다.

webpack 옵션 설정하기

library를 설정하는 방법은 entry의 속성에 library 라는 속성값을 추가해 주시면 됩니다. 이번 예시에서는 1개의 파일을 각각 다른 entry로 library 타입을 다르게 빌드해서 그 결과를 확인해 보도록 하겠습니다.

module.exports = {
  entry: {
    commonjs: {
      import: './src/math.js',
      library: {
        type: 'commonjs-module' // commonjs-module 스타일
      }
    },
    amd: {
      import: './src/math.js',
      library: {
        type: 'amd' // amd 스타일 
      }
    },
    umd: {
      import: './src/math.js',
      library: {
        type: 'umd' // umd 스타일
      }
    }
  },
  mode: 'production',
  optimization:{
    minimize: true,
  }
};

각각 타입에 대한 entry 를 만들어 구성하였습니다. :) 해당 빌드 결과는 다음과 같이 실제로 각각 별도로 빌드된것을 확인하실 수 있습니다.

빌드 결과

빌드 결과 이미지

https://image.woolta.com/3fdd91ea37ea2cc2.png

common.js 빌드 결과

module.exports = (() => {
  "use strict";
  var e = {
      52: (e, r, t) => {
        ....
    },
    r = {};
 .....
})();

amd 빌드 결과

define((() => (() => {
  "use strict";
  var e = {
      ...
    },
    r = {};
 
  function t(o) {
    if (r[o]) return r[o].exports;
    var n = r[o] = {
      exports: {}
    };
    return e[o](n, n.exports, t), n.exports
  }....
})()));

umd 빌드 결과

! function(e, t) {
  if ("object" == typeof exports && "object" == typeof module) module.exports = t();
  else if ("function" == typeof define && define.amd) define([], t);
  else {
    var o = t();
.......  }
}(self, (function() {
  return (() => {
    "use strict";
    var e = {
       ....... }
      },
  .....
}));

참조

Copyright © 2018 woolta.com

gommpo111@gmail.com