Articles

Tree Shaking

Tree shakingは、デッドコードの除去のためにJavaScriptのコンテキストで一般的に使用される用語です。 これは、ES2015モジュール構文の静的構造、つまりimportexportに依存しています。 名前と概念は、ES2015module bundler rollupによって普及されています。

webpack2リリースには、ES2015モジュール(alias harmonyモジュール)と未使用のモジュールのエクスポート検出のサポートが組み込まれていました。 新しいwebpack4リリースでは、この機能を拡張し、"sideEffects"package.jsonsrc/math.js二つの関数をエクスポートします。

project

webpack-demo|- package.json|- webpack.config.js|- /dist |- bundle.js |- index.html|- /src |- index.js+ |- math.js|- /node_modules

src/math。js

export function square(x) { return x * x;}export function cube(x) { return x * x * x;}

バンドルが縮小されていないことを確認するために、mode設定オプションをdevelopmentに設定します。

webpack。設定。js

const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },+ mode: 'development',+ optimization: {+ usedExports: true,+ },};

それを設定して、これらの新しいメソッドのいずれかを利用するようにエントリスクリプトを更新し、lodashを削除しjs

- import _ from 'lodash';+ import { cube } from './math.js'; function component() {- const element = document.createElement('div');+ const element = document.createElement('pre');- // Lodash, now imported by this script- element.innerHTML = _.join(, ' ');+ element.innerHTML = .join('\n\n'); return element; } document.body.appendChild(component());

我々はしなかったことに注意してくださいimportsquaresrc/math.jsexportを意味します。 次に、npmスクリプトnpm run buildを実行し、出力バンドルを調べてみましょう:

dist/bundle。上記のunused harmony export squaresquareがインポートされていないことがわかりますが、バンドルにはまだ含まれています。 私たちは、次のセクションでそれを修正します。100%ESMモジュールの世界では、副作用を識別するのは簡単です。 しかし、私たちはまだそこにいないので、その間にあなたのコードの”純粋さ”についてwebpackのコンパイラにヒントを提供する必要があります。これが達成される方法は、"sideEffects"パッケージです。jsonプロパティ。

{ "name": "your-project", "sideEffects": false}

上記のすべてのコードには副作用が含まれていないため、プロパティをfalseとしてマークして、未使用のコードに副作用がある場合は、代わりに配列を提供できます。

{ "name": "your-project", "sideEffects": }

配列は、関連するファイルへの単純なglobパターンを受け入れます。 内部でglob-to-regexpを使用します(サポート:***{a,b}*.css/**/*.css"sideEffects"module.rules設定オプションから設定することもできます。

{ "name": "your-project", "sideEffects": }

最後に、"sideEffects"module.rules設定オプションから設定することもできます。

木の揺れと副作用を明確にする

sideEffectsusedExports(より多くの木の揺れとして知られている)最適化は、二つの異なるものです。p>

sideEffectsは、モジュール/ファイル全体と完全なサブツリーをスキップすることができるので、はるかに効果的です。

usedExportsステートメント内の副作用を検出するためにterserに依存しています。 これはJavaScriptでは困難な作業であり、単純なsideEffectsフラグほど効果的ではありません。 また、副作用を評価する必要があると仕様が言っているので、サブツリー/依存関係をスキップすることはできません。 関数のエクスポートは正常に動作しますが、Reactの高次成分(HOC)はこの点で問題があります。p>

import { Button } from '@shopify/polaris';

事前にバンドルされたバージョンは次のようになります。

import hoistStatics from 'hoist-non-react-statics';function Button(_ref) { // ...}function merge() { var _final = {}; for ( var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++ ) { objs = arguments; } for (var _i = 0, _objs = objs; _i < _objs.length; _i++) { var obj = _objs; mergeRecursively(_final, obj); } return _final;}function withAppProvider() { return function addProvider(WrappedComponent) { var WithProvider = /*#__PURE__*/ (function (_React$Component) { // ... return WithProvider; })(Component); WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes; var FinalComponent = hoistStatics(WithProvider, WrappedComponent); return FinalComponent; };}var Button$1 = withAppProvider()(Button);export { // ..., Button$1,};

Buttonexport { Button$1 };export { Button$1 };export { Button$1 };

import hoistStatics from 'hoist-non-react-statics';function Button(_ref) { // ...}function merge() { var _final = {}; for ( var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++ ) { objs = arguments; } for (var _i = 0, _objs = objs; _i < _objs.length; _i++) { var obj = _objs; mergeRecursively(_final, obj); } return _final;}function withAppProvider() { return function addProvider(WrappedComponent) { var WithProvider = /*#__PURE__*/ (function (_React$Component) { // ... return WithProvider; })(Component); WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes; var FinalComponent = hoistStatics(WithProvider, WrappedComponent); return FinalComponent; };}var Button$1 = withAppProvider()(Button);export { // ..., Button$1,};

import hoistStatics from 'hoist-non-react-statics';function Button(_ref) { // ...}function merge() { var _final = {}; for ( var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++ ) { objs = arguments; } for (var _i = 0, _objs = objs; _i < _objs.length; _i++) { var obj = _objs; mergeRecursively(_final, obj); } return _final;}function withAppProvider() { return function addProvider(WrappedComponent) { var WithProvider = /*#__PURE__*/ (function (_React$Component) { // ... return WithProvider; })(Component); WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes; var FinalComponent = hoistStatics(WithProvider, WrappedComponent); return FinalComponent; };}var Button$1 = withAppProvider()(Button);export { // ..., Button$1,};

import hoistStatics from 'hoist-non-react-statics';function Button(_ref) { // ...}function merge() { var _final = {}; for ( var _len = arguments.length, objs = new Array(_len), _key = 0; _key < _len; _key++ ) { objs = arguments; } for (var _i = 0, _objs = objs; _i < _objs.length; _i++) { var obj = _objs; mergeRecursively(_final, obj); } return _final;}function withAppProvider() { return function addProvider(WrappedComponent) { var WithProvider = /*#__PURE__*/ (function (_React$Component) { // ... return WithProvider; })(Component); WithProvider.contextTypes = WrappedComponent.contextTypes ? merge(WrappedComponent.contextTypes, polarisAppProviderContextTypes) : polarisAppProviderContextTypes; var FinalComponent = hoistStatics(WithProvider, WrappedComponent); return FinalComponent; };}var Button$1 = withAppProvider()(Button);export { // ..., Button$1,};

div>残りのすべてのコードを残します。 したがって、質問は「このコードには副作用がありますか、それとも安全に削除できますか?”. 特にこの行のwithAppProvider()(Button)withAppProvidermergehoistStaticsWithProvider.contextTypes(Setter?)または読むときWrappedComponent.contextTypes(Getter?).Terserは実際にそれを把握しようとしますが、多くの場合、確かにわかりません。

Terserは実際にそれを把握しようとしますが、多くの場合、確かにわかりま これは、terserがそれを理解できないためにその仕事をうまくやっていないことを意味するものではありません。 JavaScriptのような動的言語でそれを確実に判断するのはあまりにも難しいです。しかし、/*#__PURE__*/アノテーションを使用してterserを助けることができます。 これは、副作用のない文としてフラグを立てます。 したがって、単純な変更により、コードをツリーシェイクすることが可能になります。

var Button$1 = /*#__PURE__*/ withAppProvider()(Button);

これにより、このコードを削除できます。 しかし、副作用を含む可能性があるため、インポートにはまだ含まれる/評価される必要がある質問があります。これに対処するには、package.json"sideEffects"/*#__PURE__*/"sideEffects"property):”no-sideEffectsフラグが付けられたモジュールからの直接エクスポートが使用されていない場合、バンドラーはモジュールの副作用の評価をスキップすることができます。”.

ShopifyのPolarisの例では、元のモジュールは次のようになります。

index。js

import './configure';export * from './types';export * from './components';

コンポーネント/インデックス。js

// ...export { default as Breadcrumbs } from './Breadcrumbs';export { default as Button, buttonFrom, buttonsFrom } from './Button';export { default as ButtonGroup } from './ButtonGroup';// ...

パッケージ。json

// ..."sideEffects": ,// ...

Forimport { Button } from "@shopify/polaris";これには次の意味があります。

  • include it:モジュールを含め、評価し、依存関係の分析を継続します
  • スキップ:include it:評価しないで、依存関係の分析を継続します
  • exclude it:include it:評価しないで、依存関係の分析を継続します
  • exclude it:include it:評価しないで、依存関係の分析を継続します
  • exclude it:include it:評価しないで、依存関係の分析を継続します
  • exclude it:include it:評価しないで、依存関係の分析を継続します
  • exclude it:include it:評価しないで、依存関係の分析を継続します依存関係を分析しない

具体的には一致するリソースごとに:

  • index.jsindex.jsindex.js: 直接エクスポートは使用されませんが、sideEffectsでフラグが設定されています->include it
  • configure.js:エクスポートは使用されませんが、sideEffectsでフラグが設定されています->include it
  • types/index.js>除外
  • components/index.js:直接エクスポートは使用されず、サイドエフェクトでフラグが設定されていませんが、再エクスポー29d8f34980″>: エクスポートは使用されず、sideEffectsフラグは付けられません->components/Breadcrumbs.cssようなすべての依存関係も除外されました。
  • components/Button.js>それを含めます
  • components/Button.css:エクスポーこの場合、バンドルには4つのモジュールのみが含まれます。

    • index.js: かなり空
    • configure.js
    • components/Button.js
    • components/Button.css

    この最適化後も、他の最適化が適用されます。 たとえば、buttonFrombuttonsFromButton.jsusedExports最適化はそれをピックアップし、terserはモジュールからいくつかのステートメントを削除できるかもしれません。

    モジュールの連結も適用されます。 これらの4つのモジュールとエントリモジュール(そしておそらくより多くの依存関係)を連結できるようにします。 index.js/*#__PURE__*/アノテーションを使用することで、関数呼び出しが副作用のない(純粋な)ものであることをwebpackに伝えることができます。 関数呼び出しの前に配置して、副作用のないものとしてマークすることができます。 関数に渡される引数は注釈によってマークされていないため、個別にマークする必要がある場合があります。 未使用の変数の変数宣言の初期値が副作用のない(純粋な)と見なされると、デッドコードとしてマークされ、ミニマイザによって実行されず、ドロップされ この動作は、optimization.innerGraphtrueに設定されている場合に有効になります。

    ファイル。js

    /*#__PURE__*/ double(55);

    出力を縮小

    したがって、importexportmodeproductionに設定します。

    webpack。設定。これを二乗すると、別のnpm run buildを実行して、何かが変更されているかどうかを確認できます。p>

    dist/bundle.jssquarecubefunction r(e){return e*e*e}n.a=r)。 縮小と木の揺れで、私たちのバンドルは今、数バイト小さくなっています! この工夫された例ではそれほど多くはないように見えるかもしれませんが、複雑な依存関係ツリーを持つ大規模なアプリケーションで作業すると、ツリーシェーキングバンドルのサイズが大幅に減少する可能性があります。だから、私たちが学んだことは、木の揺れを利用するためには、あなたがしなければならないということです。

    結論

    だから、私たちは学んだこ..ES2015モジュール構文を使用します(つまり、importexport)。

  • ES2015モジュールの構文をCommonJSモジュールに変換するコンパイラがないことを確認してください(これは一般的なBabel preset@babel/preset-envのデフォルトの動作です。プロジェクトのpackage.json"sideEffects"プロパティを追加します。
  • productionmode設定オプションを使用して、縮小やツリーの揺れを含むさまざまな最適化を有効にします。

アプリケーションをツリーとして想像することができます。 実際に使用するソースコードとライブラリは、木の緑の生きた葉を表しています。 デッドコードは、秋に消費される木の茶色の枯れ葉を表します。 枯れ葉を取り除くためには、それらが落ちる原因となる、木を振る必要があります。あなたの出力を最適化するためのより多くの方法に興味がある場合は、生産のための構築の詳細については、次のガイドにジャンプしてください。