Open4

TreeShakingについて

oosukeoosuke

TreeShaking

webpackなどのバンドラーでファイルを出力する際にデッドコード(利用されないコード)除去し、ファイルサイズが肥大化するのを防ぐ(不要なコードが巻き取られない)

条件

ES2015(ES6)のimport/export構文でモジュールを取り扱う(require()、modeule.exportsで記述した場合はTreeShakingされない)
productionモードで実行する

oosukeoosuke

webpack注意書き

TreeShakingが有効かどうかは-- display-used-exportsをつけることで確認できる。

webpack + babel-loaderを利用する場合、設定によってはTreeShakingされないケースがあるため、注意する。
具体的にはbabelの設定ファイルにmodules: falseの設定がないとESモジュール構文が別のモジュールタイプへ変換されてしまうため、上記の条件のとおり設定が必要になる。

{
  "presets": [
    [
      "@babel/preset-env", # 別の機会に
      {
        "useBuiltIns": "entry" # 別の機会に
      },
      {
        "modules": false
      },
    ]
  ]
}

https://babeljs.io/docs/en/babel-preset-env#modules

Enable transformation of ES module syntax to another module type. Note that cjs is just an alias for commonjs. Setting this to false will preserve ES modules. Use this only if you intend to ship native ES Modules to > browsers. If you are using a bundler with Babel, the default modules: "auto" is always preferred.

書かれている通り初期値はautoとなっている。このautoに関しては以下

The default auto will automatically select false if ES6 module syntax is already supported by the caller, or "commonjs" otherwise.

呼び出し元(target)がES6をサポートしている環境であれば自動的にimport/exportは有効でmodules: falseと同じ意味になる。

oosukeoosuke

コード側でのケア

TreeShakingされない

import Module from "./module"

TreeShakingされる

import { hello } from "./module"
oosukeoosuke

TreeShakingではないがES5モジュール構文でない場合のケア

TreeShakingではないが、不要なコードを読み込まない(有名なlodashだと)
lodashはimport/exportで記述されていないため、TreeShakingが利用できない

必要なモジュールだけ読み込む。一ファイルにlodashの複数メソッドを読み込んでこねくり回すなんてそうないので以下のように必要なモジュールだけ読み込んでおけばいい。

import map from "lodash/map"
import each from "lodash/each"

上記を複数書くと可読性が下がるため、以下のように読み込めるようにして、不要なファイルも除去したい

import { map, each } from 'lodash`

そこで babel-plugin-tranform-imports を利用する方法がある

https://www.npmjs.com/package/babel-plugin-transform-imports

$ yarn add -D babel-plugin-tranform-imports

.babelrc

{
  "plugins": [
    ["transform-imports", {
      "react-bootstrap": {
        "transform": "react-bootstrap/lib/${member}",
        "preventFullImport": true
      },
      "lodash": {
        "transform": "lodash/${member}",
        "preventFullImport": true # 後述
      }
    }]
  ]
}

上記リンクままですが、こうすることでimport { map, each } from 'lodashといった記述ができるようになり、かつ不要なファイルは除去される。

preventFullImportのオプションはモジュール全体が読み込まれるのを防ぐ設定。この設定を追加してimport _ from 'lodashとするとトランスパイルでエラーとなる。