よくある質問
ESモジュールはCommonJSモジュールよりも優れているのはなぜですか?
ESモジュールは公式の標準であり、JavaScriptコード構造の明確な将来の道筋です。一方、CommonJSモジュールは、ESモジュールが提案される前に一時的な解決策として機能した特殊なレガシー形式です。ESモジュールは、ツリーシェイキングやスコープホイスティングなどの最適化に役立つ静的解析を可能にし、循環参照やライブバインディングなどの高度な機能を提供します。
「ツリーシェイキング」とは何ですか?
ツリーシェイキングは、「ライブコードインクルージョン」とも呼ばれ、Rollupが特定のプロジェクトで使用されていないコードを排除するプロセスです。これはデッドコード除去の一種ですが、出力サイズに関しては他のアプローチよりもはるかに効率的です。その名前は、モジュールの抽象構文木(モジュールグラフではない)に由来しています。アルゴリズムはまずすべての関連するステートメントをマークし、次に「構文木をシェイク」してすべてのデッドコードを削除します。これはマークアンドスイープ型のガベージコレクションアルゴリズムと同様の考え方です。このアルゴリズムはESモジュールに限定されていませんが、ESモジュールでは、すべてのモジュールを共有バインディングを持つ大きな抽象構文木としてまとめて扱うことができるため、はるかに効率的になります。
CommonJSモジュールを使用してNode.jsでRollupを使用するにはどうすればよいですか?
Rollupは、Node.js、NPM、`require()`、およびCommonJSの動作ではなく、ESモジュールの仕様を実装しようと努めています。その結果、CommonJSモジュールの読み込みとNodeのモジュールロケーション解決ロジックの使用はどちらも、オプションのプラグインとして実装され、Rollupコアにはデフォルトで含まれていません。commonjsとnode-resolveプラグインを`npm install`し、`rollup.config.js`ファイルを使用して有効にすれば、準備完了です。モジュールがJSONファイルを読み込む場合は、jsonプラグインも必要です。
node-resolveがビルトイン機能ではないのはなぜですか?
主な理由は2つあります。
哲学的には、RollupはNodeとブラウザの両方でネイティブなモジュールローダーのポリフィルのようなものであるためです。ブラウザでは、`import foo from 'foo'`は機能しません。なぜなら、ブラウザはNodeの解決アルゴリズムを使用しないからです。
実際的なレベルでは、優れたAPIでこれらの懸念事項をきちんと分離しておけば、ソフトウェアの開発がはるかに容易になります。Rollupのコアは非常に大きく、それをより小さくするものはすべて良いことです。一方、バグの修正と機能の追加が容易になります。Rollupをスリムに保つことで、技術的負債の可能性が小さくなります。
より詳細な説明については、この問題を参照してください。
コード分割時に、追加のインポートがエントリチャンクに表示されるのはなぜですか?
デフォルトでは、複数のチャンクを作成する場合、エントリチャンクの依存関係のインポートは、空のインポートとしてエントリチャンク自体に追加されます。例
// input
// main.js
import value from './other-entry.js';
console.log(value);
// other-entry.js
import externalValue from 'external';
export default 2 * externalValue;
// output
// main.js
import 'external'; // this import has been hoisted from other-entry.js
import value from './other-entry.js';
console.log(value);
// other-entry.js
import externalValue from 'external';
var value = 2 * externalValue;
export default value;
これはコードの実行順序や動作には影響しませんが、コードの読み込みとパースの速度を向上させます。この最適化を行わない場合、JavaScriptエンジンは`main.js`を実行するために次の手順を実行する必要があります。
- `main.js`を読み込んでパースします。最後に、`other-entry.js`へのインポートが検出されます。
- `other-entry.js`を読み込んでパースします。最後に、`external`へのインポートが検出されます。
- `external`を読み込んでパースします。
- `main.js`を実行します。
この最適化により、JavaScriptエンジンはエントリモジュールの解析後にすべての推移的な依存関係を検出し、ウォーターフォールを回避します。
- `main.js`を読み込んでパースします。最後に、`other-entry.js`と`external`へのインポートが検出されます。
- `other-entry.js`と`external`を読み込んでパースします。`other-entry.js`からの`external`のインポートは既に読み込まれてパースされています。
- `main.js`を実行します。
この最適化が望ましくない場合もあります。その場合は、`output.hoistTransitiveImports`オプションを使用してオフにすることができます。この最適化は、`output.preserveModules`オプションを使用する場合にも適用されません。
Rollupバンドルにポリフィルを追加するにはどうすればよいですか?
Rollupは通常、バンドル時に正確なモジュール実行順序を維持しようとしますが、コード分割と外部依存関係の場合、必ずしもそうとは限りません。この問題は、外部依存関係で最も顕著です。次の例を参照してください。
// main.js
import './polyfill.js';
import 'external';
console.log('main');
// polyfill.js
console.log('polyfill');
ここでは、実行順序は`polyfill.js`→`external`→`main.js`です。コードをバンドルすると、次のようになります。
import 'external';
console.log('polyfill');
console.log('main');
実行順序は`external`→`polyfill.js`→`main.js`です。これは、Rollupがインポートをバンドルの先頭に配置したことが原因ではありません。インポートは、ファイル内の場所にかかわらず、常に最初に実行されます。この問題は、より多くのチャンクを作成することで解決できます。`polyfill.js`が`main.js`とは異なるチャンクに含まれる場合、正しい実行順序が維持されます。ただし、Rollupでこれを自動的に行う方法はまだありません。コード分割の場合、Rollupはコードが不要なものは実行されないようにしながら、できるだけ少ないチャンクを作成しようとしているため、状況は似ています。
ほとんどのコードでは、これは問題ではありません。Rollupは次を保証できるためです。
モジュールAがモジュールBをインポートし、循環インポートがない場合、Bは常にAの前に実行されます。
ただし、これはポリフィルには問題です。ポリフィルは通常最初に実行する必要がありますが、通常、すべてのモジュールにポリフィルのインポートを配置することは望ましくありません。幸いなことに、これは必要ありません。
- ポリフィルに依存する外部依存関係がない場合、各静的エントリポイントにポリフィルのインポートを最初のステートメントとして追加するだけで十分です。
- それ以外の場合、ポリフィルを別個のエントリまたは手動チャンクにすることで、常に最初に実行されるようにすることができます。
Rollupはライブラリまたはアプリケーションの構築を目的としていますか?
Rollupは既に多くの主要なJavaScriptライブラリで使用されており、ほとんどのアプリケーションの構築にも使用できます。ただし、古いブラウザでコード分割または動的インポートを使用する場合は、不足しているチャンクの読み込みを処理するために追加のランタイムが必要です。SystemJS Production Buildをお勧めします。これはRollupのシステム形式出力と適切に統合され、すべてのESモジュールライブバインディングと再エクスポートのエッジケースを適切に処理できます。または、AMDローダーも使用できます。
ブラウザでRollup自体を実行するにはどうすればよいですか?
通常のRollupビルドはいくつかのNodeJS機能に依存しますが、ブラウザAPIのみを使用するブラウザビルドも用意されています。次のようにインストールできます。
npm install @rollup/browser
そして、スクリプトで、次のようにインポートします。
import { rollup } from '@rollup/browser';
または、CDNからインポートすることもできます。たとえば、ESMビルドの場合
import * as rollup from 'https://unpkg.com/@rollup/browser/dist/es/rollup.browser.js';
UMDビルドの場合
<script src="https://unpkg.com/@rollup/browser/dist/rollup.browser.js"></script>
これにより、グローバル変数`window.rollup`が作成されます。ブラウザビルドはファイルシステムにアクセスできないため、バンドルするすべてのモジュールを解決して読み込むプラグインを提供する必要があります。これは、これを行う無理のある例です。
const modules = {
'main.js': "import foo from 'foo.js'; console.log(foo);",
'foo.js': 'export default 42;'
};
rollup
.rollup({
input: 'main.js',
plugins: [
{
name: 'loader',
resolveId(source) {
if (modules.hasOwnProperty(source)) {
return source;
}
},
load(id) {
if (modules.hasOwnProperty(id)) {
return modules[id];
}
}
}
]
})
.then(bundle => bundle.generate({ format: 'es' }))
.then(({ output }) => console.log(output[0].code));
この例は、` "main.js"`と` "foo.js"`の2つのインポートのみをサポートし、相対インポートはサポートしません。これは、絶対URLをエントリポイントとして使用し、相対インポートをサポートする別の例です。その場合、Rollup自体を再バンドルしているだけですが、ESモジュールを公開する他のURLでも使用できます。
rollup
.rollup({
input: 'https://unpkg.com/rollup/dist/es/rollup.js',
plugins: [
{
name: 'url-resolver',
resolveId(source, importer) {
if (source[0] !== '.') {
try {
new URL(source);
// If it is a valid URL, return it
return source;
} catch {
// Otherwise make it external
return { id: source, external: true };
}
}
return new URL(source, importer).href;
},
async load(id) {
const response = await fetch(id);
return response.text();
}
}
]
})
.then(bundle => bundle.generate({ format: 'es' }))
.then(({ output }) => console.log(output));