web-dev-qa-db-ja.com

Webpack 4がsideEffectsを持つパッケージに期待すること:false

Webpack 4は新しい機能を追加しました:バンドルしているモジュールのpackage.jsonsideEffectsフラグをサポートするようになりました。

Webpack 4:本日リリース

過去30日間にわたり、各フレームワークと緊密に連携して、それぞれのCLIなどでwebpack 4をサポートする準備ができていることを確認しました。すぐにバンドルサイズが減少するバージョンが表示されます。

From Webpack docs

「sideEffects」:big-moduleのpackage.jsonのfalseフラグは、パッケージのモジュールに(評価上)副作用がなく、エクスポートのみを公開することを示します。これにより、webpackなどのツールが再エクスポートを最適化できます。

2番目のリンクはフラグの使用結果を示していますが、副作用を構成するものを明確に説明していません。 ES6には here で概説されているモジュールの副作用の概念が含まれていますが、これはWebpackが副作用と見なすものとどのように関連していますか。

sideEffectsフラグのコンテキストでは、sideEffects:falseを問題なく使用するためにモジュールが回避する必要があること、または逆に、sideEffects:falseを使用しないためにモジュールが実行する必要があること問題。

完全を期すため、以下の@SeanLarkinの確固たる答えにもかかわらず、私は次の点について明確にしたいと思います。

  1. 明らかに副作用とはfpに特有のものを意味し、ロギング(コンソールまたは他の場所)およびエラーのスローが含まれます。この文脈では、これらは完全に受け入れられると思いますか?

  2. モジュールに循環参照を含め、sideEffects: falseを引き続き使用できますか?

  3. モジュールがその誤用に起因するエラーを追跡しようとすることを超えてsideEffects: falseできることを検証する方法、またはモジュールが検証できる方法はありますか?

  4. モジュールがsideEffects: falseを使用できないようにする他の要因はありますか?

32
Undistraction

Webpackチームのショーン!ここであなたの質問に答えるために、まだ進行中のドキュメントの代わりに最善を尽くします!

ECMAモジュール仕様によると(リンクを見つけようとはしないので、リンクが埋まっているのでここで私を信頼する必要があります)、

モジュールreexportsすべてのエクスポート(使用または未使用に関係なく)を評価し、それらのエクスポートのいずれかが副作用を作成した場合に実行する必要がある場合他のと。

たとえば、ケースをよりよく視覚化するために、写真付きの小さなシナリオを作成しました。

この写真では、1回のインポートで3つの個別のモジュールが1つのモジュールにインポートされ、デフォルトのエクスポートが行われ、reexportsそれらが表示されますそのモジュールから:

Example of No Side Effects from Reexported Modules

ここで、再エクスポートはお互いに影響を受けないことがわかります。したがって(webpackにシグナルが与えられた場合)、エクスポートbcをトレースまたは使用(サイズとビルド時のパフォーマンスの利点)。

enter image description here

ただし、この場合、エクスポートcbaの合計に再割り当てされるため、ローカル状態の変更によって「影響を受ける」ことがわかります。したがって、(仕様がこれを必要とする理由です)baの両方とその依存関係をバンドルに含める必要があります。

コンパイル時間とビルドサイズの両方を節約する方法として「sideEffects:false」を選択しました。これにより、開発者/ライブラリ作成者が副作用がないことを即座に(明示的に)プルーニングできるためです(ただし、 package.json、または構成の2〜3行以上)。

技術的にはこの例は非常に原始的ですが、開発者エクスペリエンスの上位レベル(Three.js、Angular、lodash-esなど)にモジュールの束を再エクスポートするフレームワークまたはライブラリの処理を開始する場合、パフォーマンスの向上は重要です(sideEffectフリーモジュールのエクスポートの場合)この方法でフラグを立てます。

追加の説明:

  1. 明らかに副作用とはfpに特有のものを意味し、ロギング(コンソールまたは他の場所)およびエラーのスローが含まれます。この文脈では、これらは完全に受け入れられると思いますか?

これが解決しようとしている場合、はい。モジュールのエクスポートに対して作成された効果が、プルーニングが受け入れられない原因となる他者の影響を受けない限り。

  1. モジュールに循環参照を含め、sideEffects: false?を引き続き使用できますか

理論的にはそうすべきです。

  1. モジュールが誤用に起因するエラーを追跡しようとする以上に、sideEffects: falseを使用できることを確認する方法はありますか?

私が知っているわけではありませんが、これは素晴らしいツールです。

  1. モジュールがsideEffects: falseを使用できないようにする他の要因はありますか?

プロパティがpackage.jsonにないか、module.rulesに定義されていない場合、またはmode: productionが設定されていない場合(最適化を活用します)。

50
Sean Larkin

このsideEffects設定は非常に曖昧であり、ドキュメントで十分に説明されていません。ドキュメントはほとんど「副作用のないモジュール用のsideEffectsフラグがあります」のようなものです。

コンセンサスは、「副作用がない」というフレーズは、「最上位のモジュールの外部のものとは話さない」と解読できるということです。

私の現在の理解では、このsideEffectsフラグは「再エクスポート」専用です。「再エクスポート」は次のとおりです。

export { a } from './lib/a'
export { b } from './lib/b'

<npm-package>/index.jsのどこかに(または<npm-package>内の他のファイル)。

アプリケーションが<npm-package>からaのみをインポートし、bをインポートしないことをWebpackが検出した場合、Webpackはexport { b } from './lib/b'行を<npm-package>/index.jsから単にドロップして、結果のバンドルに'./lib/b.js'ファイルを含めません'./lib/b.js'ファイルのサイズだけ小さくします)。

ここで、'./lib/b.js'にいくつかの「副作用」を実行するトップレベルのコード行がある場合、つまり'./lib/b.js'が次のようなことをした場合:

  • window.jQuery = ...
  • if (!global.Set) global.Set = require('babel-polyfill').Set
  • new XmlHttpRequest().post('/analytics', data)

その場合、'./lib/b.js'は、その最上位コード(import './lib/b'で実行される)が'./lib/b.js'ファイルのスコープ外の何かに影響を与えるため、「副作用」があると言われます。

同時に、'./lib/b.js'トップレベルコードがその*.jsファイルの外側に到達しない限り、「副作用」はありません。

let a = 1
a = a + 1 + computeSomeValue()
export default a
export const b = a + 1
export const c = b + 1

これらはすべて「副作用」ではありません。

最後の落とし穴があります。npmパッケージに、ユーザーがimportできる*.cssファイルがある場合、これらの*.cssファイルはすべて「副作用」です。

import 'npm-package/style.css'

このimportに割り当てられた変数はありません。これは、Webpackの「このインポートされたモジュールは、アプリケーションのどこでも使用されない」ことを意味します。したがって、Webpackは、'npm-package/style.css'npm-packageフラグがある場合、「ツリーシェーキング」プロセスの一部としてバンドルからsideEffects: falseファイルを単に破棄します。したがって、sideEffects: falseを書く代わりに、常に"sideEffects": ["*.css"]を書きます。 npmパッケージがCSSファイルをエクスポートしない場合でも、将来的にはそうする可能性があります。これにより、前述の「CSSファイルが含まれていません」バグを防ぐことができます。

11
asdfasdfads