Published on

Webpack Tree Shaking & Dynamic Import

Tree Shaking

  • Webpack이 JS모듈을 번들링할 때 사용하지 않는 코드를 제거하는 최적화 과정
  • 아래 코드에서 abc.js의 b와 c 함수는 사용하지 않으므로 번들링 과정에서 제거된다. (단, production 모드로 빌드하였을때만)
//abc.js
function a() {
  console.log('a');
}
function b() {
  console.log('b');
}
function c() {
  console.log('c');
}
export { a, b, c };

//main.js
import * as abc from './abc';
abc.a();
  • 외부 라이브러리를 사용할 때 라이브러리에 Tree Shaking이 적용되기 위해서는 외부 패키지의 package.json에 있는 sideEffects(Webpack 4.0부터 지원) 설정이 false로 되어있어야 한다.

  • Webpack이 직접 코드를 평가하고 실행하는 대신에, sideEffects 설정을 보고 side effect 존재 여부를 판단하다. 이 옵션을 명시하지 않으면 Tree Shaking시에 side effect가 발생할 수 있다고 판단하여 해당 패키지를 Tree Shaking의 대상에서 제외한다.

  • 라이브러리에 sideEffects가 false로 되어있다고 무조건 Tree Shaking이 적용되는 것은 아니다. Webpack은 ES Module로 의존성을 관리하는 모듈만 Tree Shaking을 한다.

  • 아래 코드에서는 lodash-es 전체를 번들링에 포함하지 않고, 사용하는 snakeCase와 toUpper만 번들링에 포함된다. (lodash-es는 sideEffects 설정이 false로 되어있다.)

//main.js
import { snakeCase, toUpper } from 'lodash-es';

const snakeCaseStr = snakeCase('sddMMM');
console.log(snakeCaseStr);
const toUpperStr = toUpper(snakeCaseStr);
console.log(toUpperStr);

splitChunks

  • Webpack 4.0의 splitChunks옵션을 통해 번들파일을 분리할 수 있다.
  • 번들파일 분리를 통하여 브라우저 캐시를 전략적으로 활용하고, 초기 로딩속도를 최적화 할 수 있다.
  • cacheGroups : 명시적으로 특정 파일들을 청크로 분리할 때 사용한다.
  • test : 대상이 되는 파일들을 정규식으로 포함한다.
  • name : 청크로 분리할 때 사용될 파일명
  • chunks : 모듈의 종류에 따라 청크에 포함할지 말지를 결정하는 옵션. all은 test조건에 포함되는 모든 것을 분리. initial은 초기로딩에 필요한 경우. async는 import()를 이용해 다이나믹하게 사용되는 경우 분리.
  • 분리된 파일들은 HtmlWebpackPlugin을 통해 index.html에 주입된다.
  • 아래 코드에서 lodash만 node_modules에서 따로 분리하고 뒤에서 나올 Dynamic Import를 적용하면 처음부터 모든 Library를 로드하지 않고, 사용하는 시점에 lodash 번들을 로드할 수 있다.
module.exports = {
  entry: {
    app: ['./scripts/main'],
  },
  output: {
    filename: '[name].bundle.js',
    chunkFilename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false,
        lodash: {
          test: /[\\/]node_modules[\\/]lodash[\\/]/,
          name: 'lodash',
          enforce: true,
          chunks: 'all',
          priority: 30,
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          enforce: true,
          priority: 10,
        },
        commons: {
          name: 'commons',
          chunks: 'all',
          minChunks: 2,
          priority: 20,
          enforce: true,
        },
      },
    },
  },
};

Dynamic Imports

  • dynamic import를 vue-router와 연동하여, 라우팅 기준으로 파일 번들링을 하고, 필요한 시점에 모듈을 동적으로 로드하도록 지원한다.
  • Babel이 dynamic import statement를 처리할 수 있도록 syntax-dynamic-import Plugin을 설치해주어야 한다.
  • dynamic import statement 안에 /* webpackChunkName: "login" */ 를 넣어 청크 네임을 지정할 수 있다.
  • webpack.config.js의 output.chunkFilename 을 통하여 non-entry 청크 파일들의 파일명을 설정한다.
//.babelrc
{
    "plugins": ["@babel/plugin-syntax-dynamic-import"]
}
 //package.json
"devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
//main.js
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const router = new VueRouter({
  routes: [
    { path: '*', component: () => import(/* webpackChunkName: "login" */ './pages/login') },
    { path: '/guide', component: () => import(/* webpackChunkName: "guide" */ './pages/guide') },
  ],
});
//webpack.config.js
module.exports = {
    entry: {
        app: ['./scripts/main']
    },
    output: {
        filename: '[name].bundle.js',
        chunkFilename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },

참조