跳至內容

常見問題

為何 ES 模組比 CommonJS 模組好?

ES 模組是 JavaScript 程式碼結構的官方標準和明確前進方向,而 CommonJS 模組是一種特殊遺留格式,在 ES 模組提出之前作為權宜解決方案。ES 模組允許靜態分析,有助於執行樹狀搖晃和範圍提升等最佳化,並提供循環參考和動態繫結等進階功能。

什麼是「樹狀搖晃?」

樹狀搖晃,也稱為「動態程式碼包含」,是 Rollup 的程序,用於消除特定專案中實際上未使用的程式碼。它是 一種形式的死程式碼消除,但相較於其他方法,在輸出大小方面可能更有效率。這個名稱源自模組的 抽象語法樹(而非模組圖表)。此演算法會先標記所有相關陳述,然後「搖晃語法樹」以移除所有死程式碼。其概念類似於 標記清除垃圾回收演算法。雖然此演算法不限於 ES 模組,但它們讓 Rollup 能將所有模組視為一個具有共用繫結的大型抽象語法樹,因此讓演算法更有效率。

如何在 Node.js 中使用 Rollup 搭配 CommonJS 模組?

Rollup 致力於實作 ES 模組的規範,而不一定實作 Node.js、NPM、require() 和 CommonJS 的行為。因此,CommonJS 模組的載入和使用 Node 的模組位置解析邏輯都實作為選用外掛程式,未預設包含在 Rollup 核心。只要使用 npm install 安裝 commonjsnode-resolve 外掛程式,然後使用 rollup.config.js 檔案啟用它們,即可設定完成。如果模組匯入 JSON 檔案,您還需要 json 外掛程式。

為何 node-resolve 不是內建功能?

主要有兩個原因

  1. 從哲學角度來看,這是因為 Rollup 本質上是一種 polyfill,用於 Node 和瀏覽器中的原生模組載入器。在瀏覽器中,import foo from 'foo' 無法運作,因為瀏覽器不使用 Node 的解析演算法。

  2. 從實務角度來看,如果這些問題能透過良好的 API 清楚區分,開發軟體會容易得多。Rollup 的核心相當龐大,任何能避免它變更大的做法都是好事。同時,這樣也能更容易修正錯誤並新增功能。透過保持 Rollup 精簡,技術負債的潛在風險會很小。

請參閱 此議題 以取得更詳細的說明。

當進行程式碼分割時,為何會在入口區塊中出現額外的匯入?

預設情況下,在建立多個區塊時,入口區塊的依賴項匯入會新增為入口區塊本身的空匯入。 範例

js
// 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

  1. 載入並解析 main.js。最後,會發現匯入 other-entry.js
  2. 載入並解析 other-entry.js。最後,會發現匯入 external
  3. 載入並解析 external
  4. 執行 main.js

透過這個最佳化,JavaScript 引擎會在解析一個入口模組後發現所有遞移相依性,避免瀑布

  1. 載入並解析 main.js。最後,會發現匯入 other-entry.jsexternal
  2. 載入並解析 other-entry.jsexternalother-entry.js 中匯入的 external 已經載入並解析。
  3. 執行 main.js

在某些情況下,這個最佳化是不需要的,在這種情況下,你可以透過 output.hoistTransitiveImports 選項將其關閉。當使用 output.preserveModules 選項時,這個最佳化也永遠不會套用。

如何將 polyfill 新增到 Rollup 捆綁?

即使 Rollup 通常會在捆綁時盡量維持精確的模組執行順序,但在以下兩種情況下,並非總是如此:程式碼分割和外部相依性。這個問題在外部相依性中是最明顯的,請參閱以下 範例

js
// main.js
import './polyfill.js';
import 'external';
console.log('main');

// polyfill.js
console.log('polyfill');

在此執行順序為 polyfill.jsexternalmain.js。現在當你打包程式碼時,你會得到

js
import 'external';
console.log('polyfill');
console.log('main');

執行順序為 externalpolyfill.jsmain.js。這不是 Rollup 將 import 放在程式碼最上方所造成的問題,不論它們位於檔案中的何處,匯入總是會先執行。這個問題可以透過建立更多區塊來解決:如果 polyfill.js 出現在與 main.js 不同的區塊中,正確的執行順序將會被保留。然而,在 Rollup 中還沒有自動執行此操作的方法。對於程式碼分割,情況類似,因為 Rollup 嘗試建立儘可能少的區塊,同時確保不會執行不需要的程式碼。

對於大多數程式碼,這不是問題,因為 Rollup 可以保證

如果模組 A 匯入模組 B,且沒有循環匯入,則 B 將會永遠在 A 之前執行。

然而,這對於 polyfill 來說是個問題,因為它們通常需要先執行,但通常不希望在每個模組中都放置 polyfill 的匯入。幸運的是,這並不需要

  1. 如果沒有依賴於 polyfill 的外部依賴項,則將 polyfill 的匯入新增為每個靜態進入點的第一個陳述就足夠了。
  2. 否則,另外將 polyfill 設為一個獨立的進入點或 手動區塊 將會永遠確保它先執行。

Rollup 是用於建置函式庫還是應用程式?

Rollup 已被許多主要的 JavaScript 函式庫使用,也可以用來建構絕大多數的應用程式。不過,如果你想在較舊的瀏覽器中使用程式碼分割或動態匯入,你需要額外的執行時間來處理載入遺失的區塊。我們建議使用 SystemJS 生產建置,因為它與 Rollup 的系統格式輸出整合得很好,並且能夠適當地處理所有 ES 模組即時繫結和重新匯出的邊緣案例。或者,也可以使用 AMD 載入器。

如何在瀏覽器中執行 Rollup 本身

雖然一般的 Rollup 建置依賴於一些 NodeJS 功能,但也有瀏覽器建置可以使用,它只使用瀏覽器 API。你可以透過以下方式安裝

shell
npm install @rollup/browser

在你的指令碼中,透過以下方式匯入

js
import { rollup } from '@rollup/browser';

或者,你可以從 CDN 匯入,例如 ESM 建置

js
import * as rollup from 'https://unpkg.com/@rollup/browser/dist/es/rollup.browser.js';

以及 UMD 建置

html
<script src="https://unpkg.com/@rollup/browser/dist/rollup.browser.js"></script>

這將建立一個全域變數 window.rollup。由於瀏覽器建置無法存取檔案系統,你需要提供外掛程式來解析和載入所有你想要組合的模組。以下是一個這樣做的虛構範例

js
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",沒有相對匯入。以下是另一個使用絕對 URL 作為進入點並支援相對匯入的範例。在這種情況下,我們只是重新組合 Rollup 本身,但它可以用於任何公開 ES 模組的其他 URL

js
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));

Rollup 標誌是誰製作的?它很漂亮。

Julian Lloyd!

在 MIT 授權下發布。