跳至內容

外掛程式開發

外掛概觀

Rollup 外掛是一個物件,包含一個或多個 屬性建置掛鉤輸出產生掛鉤(如下所述),並遵循我們的 慣例。外掛應以套件形式發行,匯出一個可使用外掛特定選項呼叫並傳回此類物件的函式。

外掛允許您自訂 Rollup 的行為,例如在綑綁之前轉譯程式碼,或在您的 node_modules 資料夾中尋找第三方模組。有關如何使用它們的範例,請參閱 使用外掛

可以在 github.com/rollup/awesome 找到外掛清單。如果您想建議外掛,請提交 Pull Request。

一個簡單的範例

以下外掛會攔截任何 virtual-module 的匯入,而不會存取檔案系統。例如,如果您想在瀏覽器中使用 Rollup,則有此必要性。它甚至可用於替換進入點,如範例中所示。

js
// rollup-plugin-my-example.js
export default function myExample () {
  return {
    name: 'my-example', // this name will show up in logs and errors
    resolveId ( source ) {
      if (source === 'virtual-module') {
        // this signals that Rollup should not ask other plugins or check
        // the file system to find this id
        return source;
      }
      return null; // other ids should be handled as usually
    },
    load ( id ) {
      if (id === 'virtual-module') {
        // the source code for "virtual-module"
        return 'export default "This is virtual!"';
      }
      return null; // other ids should be handled as usually
    }
  };
}

// rollup.config.js
import myExample from './rollup-plugin-my-example.js';
export default ({
  input: 'virtual-module', // resolved by our plugin
  plugins: [myExample()],
  output: [{
    file: 'bundle.js',
    format: 'es'
  }]
});

慣例

  • 外掛程式應具有明確的名稱,並加上 rollup-plugin- 前綴。
  • package.json 中包含 rollup-plugin 關鍵字。
  • 應測試外掛程式。我們建議使用 mochaava,它們支援開箱即用的 Promises。
  • 如果可能,請使用非同步方法,例如使用 fs.readFile 取代 fs.readFileSync
  • 以英文撰寫外掛程式的文件。
  • 如果合適,請確保外掛程式輸出正確的原始碼對應。
  • 如果外掛程式使用「虛擬模組」(例如用於輔助函式),請在模組 ID 前加上 \0。此舉可防止其他外掛程式嘗試處理它。

屬性

名稱

類型字串

外掛程式的名稱,用於錯誤訊息和記錄。

版本

類型字串

外掛程式的版本,用於外掛程式間的通訊情境。

建置掛鉤

若要與建置流程互動,外掛程式物件包含「掛鉤」。掛鉤是在建置的不同階段呼叫的函式。掛鉤會影響建置執行的模式、提供建置的資訊,或在建置完成後修改建置。有不同類型的掛鉤

  • async:掛鉤也可以傳回一個 Promise,解析為相同類型的值;否則,掛鉤會標記為 sync
  • first:如果多個外掛程式實作此掛鉤,則會依序執行掛鉤,直到某個掛鉤傳回的值不為 nullundefined
  • sequential:如果多個外掛程式實作此掛鉤,則會依指定的順序執行所有外掛程式。如果掛鉤為 async,則此類後續掛鉤會等到目前的掛鉤解析完畢。
  • parallel:如果多個外掛程式實作這個掛鉤,它們將全部按照指定的順序執行。如果掛鉤是 async,則此類後續掛鉤將會並行執行,而不等待目前的掛鉤。

除了函式,掛鉤也可以是物件。在這種情況下,實際的掛鉤函式(或 banner/footer/intro/outro 的值)必須指定為 handler。這允許您提供額外的選用屬性,以變更掛鉤執行

  • 順序:"pre" | "post" | null
    如果有多個外掛程式實作這個掛鉤,則可以先執行這個外掛程式("pre")、最後執行("post"),或按照使用者指定的順序執行(沒有值或 null)。

    js
    export default function resolveFirst() {
    	return {
    		name: 'resolve-first',
    		resolveId: {
    			order: 'pre',
    			handler(source) {
    				if (source === 'external') {
    					return { id: source, external: true };
    				}
    				return null;
    			}
    		}
    	};
    }

    如果多個外掛程式使用 "pre""post",Rollup 會按照使用者指定的順序執行它們。這個選項可用於所有外掛程式掛鉤。對於並行掛鉤,它會變更掛鉤同步部分的執行順序。

  • 順序:布林值
    不要與其他外掛程式的相同掛鉤並行執行這個掛鉤。只能用於 parallel 掛鉤。使用這個選項會讓 Rollup 等待所有先前外掛程式的結果,然後執行外掛程式掛鉤,再並行執行其餘外掛程式。例如,當您有外掛程式 ABCDE,它們全部實作相同的並行掛鉤,而中間的外掛程式 Csequential: true,則 Rollup 會先並行執行 A + B,然後單獨執行 C,再並行執行 D + E

    當您需要在相互依賴的不同 writeBundle 鉤子中執行多個命令列工具時,這會很有用(請注意,如果可能,建議在順序 generateBundle 鉤子中新增/移除檔案,這比較快,適用於純記憶體建置,並允許其他記憶體建置外掛程式查看檔案)。您可以將此選項與 order 結合使用,以進行額外排序。

    js
    import { resolve } from 'node:path';
    import { readdir } from 'node:fs/promises';
    
    export default function getFilesOnDisk() {
    	return {
    		name: 'getFilesOnDisk',
    		writeBundle: {
    			sequential: true,
    			order: 'post',
    			async handler({ dir }) {
    				const topLevelFiles = await readdir(resolve(dir));
    				console.log(topLevelFiles);
    			}
    		}
    	};
    }

建置鉤子會在建置階段執行,此階段是由 rollup.rollup(inputOptions) 觸發的。它們主要用於在 Rollup 處理輸入檔案之前,找出、提供和轉換輸入檔案。建置階段的第一個鉤子是 options,最後一個總是 buildEnd。如果發生建置錯誤,則會在之後呼叫 closeBundle

平行
順序
第一個
非同步
同步

each entry
external
non-external
not cached
no imports
cached
false
true
each import()
non-external
each import
(cached)
each import
(not cached)
external
unresolved
watchChange
closeWatcher
buildEnd
buildStart
load
moduleParsed
options
resolveDynamicImport
resolveId
shouldTransformCachedModule
transform

此外,在監控模式中,watchChange 鉤子可以在任何時候觸發,以通知一旦目前的執行產生其輸出,就會觸發新的執行。另外,當監控器關閉時,會觸發 closeWatcher 鉤子。

請參閱 輸出產生鉤子,以取得在輸出產生階段執行的鉤子,以修改產生的輸出。

buildEnd

類型(error?: Error) => void
類型非同步、並行
上一個moduleParsedresolveIdresolveDynamicImport
下一個在輸出產生階段的 outputOptions,因為這是建置階段的最後一個掛鉤

在 Rollup 完成綑綁,但尚未呼叫 generatewrite 時呼叫;您也可以傳回 Promise。如果在建置期間發生錯誤,會傳遞給這個掛鉤。

buildStart

類型(options: InputOptions) => void
類型非同步、並行
上一個options
下一個resolveId 並行解析每個進入點

在每個 rollup.rollup 建置中呼叫。當您需要存取傳遞給 rollup.rollup() 的選項時,建議使用這個掛鉤,因為它會考量所有 options 掛鉤的轉換,並包含未設定選項的正確預設值。

closeWatcher

類型() => void
類型非同步、並行
上一個/下一個這個掛鉤可以在建置和輸出產生階段的任何時間觸發。如果是這種情況,目前的建置仍會繼續進行,但不會再觸發任何新的 watchChange 事件

在監控程序關閉時通知外掛程式,以便所有開啟的資源也可以關閉。如果傳回 Promise,Rollup 會等到 Promise 解決後才關閉程序。輸出外掛程式無法使用這個掛鉤。

load

類型(id: string) => LoadResult
類型非同步,第一
上一個resolveIdresolveDynamicImport,已解析載入的 ID。此外,這個掛勾可以隨時從外掛掛勾觸發,方法是呼叫 this.load 來預載與 ID 相應的模組
下一個transform 如果未曾使用快取,或沒有快取副本具有相同的 code,則轉換載入的檔案,否則 shouldTransformCachedModule
typescript
type LoadResult = string | null | SourceDescription;

interface SourceDescription {
	code: string;
	map?: string | SourceMap;
	ast?: ESTree.Program;
	attributes?: { [key: string]: string } | null;
	meta?: { [plugin: string]: any } | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	syntheticNamedExports?: boolean | string | null;
}

定義自訂載入器。傳回 null 會遞延到其他 load 函式(最終會遞延到從檔案系統載入的預設行為)。為了防止額外的解析開銷,例如這個掛勾已經使用 this.parse 產生某個原因的 AST,這個掛勾可以選擇傳回 { code, ast, map } 物件。ast 必須是標準 ESTree AST,每個節點都有 startend 屬性。如果轉換不會移動程式碼,你可以透過將 map 設定為 null 來保留現有的來源對應。否則,你可能需要產生來源對應。請參閱 來源程式碼轉換 部分。

如果 moduleSideEffects 傳回 false,而且沒有其他模組從這個模組匯入任何內容,則即使這個模組有副作用,也不會包含在套件中。如果傳回 true,Rollup 會使用其預設演算法來包含模組中所有有副作用的陳述式(例如修改全域或匯出的變數)。如果傳回 "no-treeshake",則會關閉這個模組的樹狀搖晃,即使它是空的,它也會包含在其中一個產生的區塊中。如果傳回 null 或省略旗標,則 moduleSideEffects 會由解析這個模組的第一個 resolveId 掛勾、treeshake.moduleSideEffects 選項決定,或最終預設為 truetransform 掛勾可以覆寫這個設定。

屬性包含此模組在匯入時所使用的匯入屬性。目前,它們不會影響已套件模組的呈現,而是用於文件目的。如果傳回 null 或省略標記,則 屬性將由解析此模組的第一個 resolveId 掛勾決定,或由此模組的第一個匯入中的屬性決定。轉換掛勾可以覆寫此設定。

請參閱 合成命名匯出 以了解 syntheticNamedExports 選項的效果。如果傳回 null 或省略標記,則 syntheticNamedExports 將由解析此模組的第一個 resolveId 掛勾決定,或最終預設為 false轉換掛勾可以覆寫此設定。

請參閱 自訂模組元資料 以了解如何使用 meta 選項。如果此掛勾傳回 meta 物件,它將與 resolveId 掛勾傳回的任何 meta 物件進行淺層合併。如果沒有掛勾傳回 meta 物件,它將預設為空物件。轉換掛勾可以進一步新增或取代此物件的屬性。

您可以在此掛勾中使用 this.getModuleInfo 找出 屬性metamoduleSideEffectssyntheticNamedExports 的先前值。

moduleParsed

類型(moduleInfo: ModuleInfo) => void
類型非同步、並行
上一個轉換 目前處理的文件已轉換
下一個resolveIdresolveDynamicImport 以並行解析所有已發現的靜態和動態匯入(如果存在),否則 buildEnd

每次 Rollup 完整解析模組時,都會呼叫此掛勾。請參閱 this.getModuleInfo,以了解傳遞至這個掛勾的資訊。

transform 掛勾不同,此掛勾絕不會快取,而且可用來取得快取模組和其他模組的資訊,包括 meta 屬性、codeast 的最終形狀。

此掛勾會等到所有匯入都已解析完畢,才能讓 moduleInfo.importedIdsmoduleInfo.dynamicallyImportedIdsmoduleInfo.importedIdResolutionsmoduleInfo.dynamicallyImportedIdResolutions 中的資訊完整且正確。但請注意,匯入模組的資訊可能不完整,因為稍後可能會發現其他匯入者。如果您需要這些資訊,請使用 buildEnd 掛勾。

onLog

類型(level: LogLevel, log: RollupLog) => boolean | null
類型同步,順序
上一個/下一個此掛勾隨時都可能觸發。

請參閱 onLog 選項,以取得可用的 Loglevel 值和 RollupLog 類型。

一個函式,用來接收並篩選 Rollup 和外掛程式產生的記錄和警告,然後再傳遞給 onLog 選項或印到主控台上。

如果這個掛勾傳回 false,記錄就會被篩選掉。否則,記錄會傳遞給下一個外掛程式的 onLog 掛勾、onLog 選項,或印到主控台上。外掛程式也可以透過將記錄傳遞給 this.errorthis.warnthis.infothis.debug,並傳回 false,來變更記錄的記錄等級,或將記錄變成錯誤。請注意,與其他會將外掛程式名稱新增到記錄的外掛程式掛勾不同,這些函式不會新增或變更記錄的屬性。此外,onLog 掛勾產生的記錄不會傳回給同一個外掛程式的 onLog 掛勾。如果另一個外掛程式在其自己的 onLog 掛勾中,針對此類記錄產生記錄,這個記錄也不會傳遞給原始的 onLog 掛勾。

js
function plugin1() {
	return {
		name: 'plugin1',
		buildStart() {
			this.info({ message: 'Hey', pluginCode: 'SPECIAL_CODE' });
		},
		onLog(level, log) {
			if (log.plugin === 'plugin1' && log.pluginCode === 'SPECIAL_CODE') {
				// We turn logs into warnings based on their code. This warnings
				// will not be passed back to the same plugin to avoid an
				// infinite loop, but other plugins will still receive it.
				this.warn(log);
				return false;
			}
		}
	};
}

function plugin2() {
	return {
		name: 'plugin2',
		onLog(level, log) {
			if (log.plugin === 'plugin1' && log.pluginCode === 'SPECIAL_CODE') {
				// You can modify logs in this hooks as well
				log.meta = 'processed by plugin 2';
				// This turns the log back to "info". If this happens in
				// response to the first plugin, it will not be passed back to
				// either plugin to avoid an infinite loop. If both plugins are
				// active, the log will be an info log if the second plugin is
				// placed after the first one
				this.info(log);
				return false;
			}
		}
	};
}

options 鉤子類似,此鉤子無法存取大部分 外掛內容 實用程式功能,因為它可能在 Rollup 完全組態之前執行。唯一支援的屬性為 this.meta,以及用於記錄和錯誤的 this.errorthis.warnthis.infothis.debug

選項

類型(options: InputOptions) => InputOptions | null
類型非同步、順序
上一個這是建置階段的第一個鉤子
下一個buildStart

取代或處理傳遞給 rollup.rollup 的選項物件。傳回 null 不會取代任何內容。如果您只需要讀取選項,建議使用 buildStart 鉤子,因為該鉤子在考量所有 options 鉤子的轉換後,可以存取選項。

onLog 鉤子類似,此鉤子無法存取大部分 外掛內容 實用程式功能,因為它在 Rollup 完全組態之前執行。唯一支援的屬性為 this.meta,以及用於記錄和錯誤的 this.errorthis.warnthis.infothis.debug

解析動態匯入

類型ResolveDynamicImportHook
類型非同步,第一
上一個匯入檔案的 moduleParsed
下一個如果鉤子已使用尚未載入的 ID 解析,則為 load;如果動態匯入包含字串且未由鉤子解析,則為 resolveId;否則為 buildEnd
typescript
type ResolveDynamicImportHook = (
	specifier: string | AstNode,
	importer: string,
	options: { attributes: Record<string, string> }
) => ResolveIdResult;

提示

傳回類型 ResolveIdResultresolveId 鉤子相同。

定義動態載入的客製化解析器。傳回 false 表示載入應維持原樣,而不傳遞給其他解析器,使其成為外部的。類似於 resolveId 鉤子,您也可以傳回物件,同時將載入解析為不同的 ID 並標記為外部的。

attributes 會告訴您載入中有哪些載入屬性。亦即,import("foo", {assert: {type: "json"}}) 會傳遞 attributes: {type: "json"}

如果動態載入傳遞字串作為引數,此鉤子傳回的字串會被解釋為現有的模組 ID,而傳回 null 則會遞延到其他解析器,最後到 resolveId

如果動態載入沒有傳遞字串作為引數,此鉤子會存取原始 AST 節點進行分析,並在下列方面表現得略有不同

  • 如果所有外掛程式都傳回 null,載入會被視為 external,不會產生警告。
  • 如果傳回字串,此字串不會被解釋為模組 ID,而是用來取代載入引數。外掛程式有責任確保產生的程式碼有效。
  • 若要將此類載入解析為現有模組,您仍可以傳回物件 {id, external}

請注意,此鉤子的傳回值不會之後傳遞給 resolveId;如果您需要存取靜態解析演算法,您可以在外掛程式內容使用 this.resolve(source, importer)

resolveId

類型ResolveIdHook
類型非同步,第一
上一個buildStart 如果我們正在解析一個進入點,moduleParsed 如果我們正在解析一個匯入,或作為 resolveDynamicImport 的備用。此外,這個掛鉤可以在建置階段從外掛掛鉤中透過呼叫 this.emitFile 來發出一個進入點,或在任何時間透過呼叫 this.resolve 來手動解析一個 id 而觸發
下一個load 如果解析的 id 尚未被載入,否則為 buildEnd
typescript
type ResolveIdHook = (
	source: string,
	importer: string | undefined,
	options: {
		attributes: Record<string, string>;
		custom?: { [plugin: string]: any };
		isEntry: boolean;
	}
) => ResolveIdResult;

type ResolveIdResult = string | null | false | PartialResolvedId;

interface PartialResolvedId {
	id: string;
	external?: boolean | 'absolute' | 'relative';
	attributes?: Record<string, string> | null;
	meta?: { [plugin: string]: any } | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	resolvedBy?: string | null;
	syntheticNamedExports?: boolean | string | null;
}

定義一個自訂解析器。一個解析器可能對例如尋找第三方依賴有幫助。這裡的 source 是被匯入者,就像它在匯入陳述式中被寫的一樣,例如

js
import { foo } from '../bar.js';

來源將會是 "../bar.js"

importer 是匯入模組的完全解析 id。在解析進入點時,匯入者通常會是 undefined。這裡的一個例外是透過 this.emitFile 產生的進入點,因為在這裡,你可以提供一個 importer 參數。

對於那些情況,isEntry 選項會告訴你我們是否正在解析一個使用者定義的進入點,一個發出的區塊,或如果 isEntry 參數是提供給 this.resolve 內容函數的。

你可以將它用作一個機制來定義進入點的自訂代理模組。以下的外掛會代理所有進入點來注入一個 polyfill 匯入。

js
// We prefix the polyfill id with \0 to tell other plugins not to try to load or
// transform it
const POLYFILL_ID = '\0polyfill';
const PROXY_SUFFIX = '?inject-polyfill-proxy';

function injectPolyfillPlugin() {
	return {
		name: 'inject-polyfill',
		async resolveId(source, importer, options) {
			if (source === POLYFILL_ID) {
				// It is important that side effects are always respected
				// for polyfills, otherwise using
				// "treeshake.moduleSideEffects: false" may prevent the
				// polyfill from being included.
				return { id: POLYFILL_ID, moduleSideEffects: true };
			}
			if (options.isEntry) {
				// Determine what the actual entry would have been.
				const resolution = await this.resolve(source, importer, options);
				// If it cannot be resolved or is external, just return it
				// so that Rollup can display an error
				if (!resolution || resolution.external) return resolution;
				// In the load hook of the proxy, we need to know if the
				// entry has a default export. There, however, we no longer
				// have the full "resolution" object that may contain
				// meta-data from other plugins that is only added on first
				// load. Therefore we trigger loading here.
				const moduleInfo = await this.load(resolution);
				// We need to make sure side effects in the original entry
				// point are respected even for
				// treeshake.moduleSideEffects: false. "moduleSideEffects"
				// is a writable property on ModuleInfo.
				moduleInfo.moduleSideEffects = true;
				// It is important that the new entry does not start with
				// \0 and has the same directory as the original one to not
				// mess up relative external import generation. Also
				// keeping the name and just adding a "?query" to the end
				// ensures that preserveModules will generate the original
				// entry name for this entry.
				return `${resolution.id}${PROXY_SUFFIX}`;
			}
			return null;
		},
		load(id) {
			if (id === POLYFILL_ID) {
				// Replace with actual polyfill
				return "console.log('polyfill');";
			}
			if (id.endsWith(PROXY_SUFFIX)) {
				const entryId = id.slice(0, -PROXY_SUFFIX.length);
				// We know ModuleInfo.hasDefaultExport is reliable because
				// we awaited this.load in resolveId
				const { hasDefaultExport } = this.getModuleInfo(entryId);
				let code =
					`import ${JSON.stringify(POLYFILL_ID)};` +
					`export * from ${JSON.stringify(entryId)};`;
				// Namespace reexports do not reexport default, so we need
				// special handling here
				if (hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(entryId)};`;
				}
				return code;
			}
			return null;
		}
	};
}

attributes 告訴你哪些匯入屬性出現在匯入中。例如 import "foo" assert {type: "json"} 會傳遞 attributes: {type: "json"}

傳回 null 會遞延至其他 resolveId 函式,最後會使用預設解析行為。傳回 false 表示 source 應視為外部模組,且不包含在套件中。如果這發生在相對匯入,則 id 將會重新正規化,就像使用 external 選項一樣。

如果您傳回一個物件,則可以解析匯入至不同的 id,同時將其排除在套件之外。這讓您可以使用外部依賴項來取代依賴項,而不需要使用者透過 external 選項手動將其標記為「外部」

js
function externalizeDependencyPlugin() {
	return {
		name: 'externalize-dependency',
		resolveId(source) {
			if (source === 'my-dependency') {
				return { id: 'my-dependency-develop', external: true };
			}
			return null;
		}
	};
}

如果 externaltrue,則絕對 id 將會根據使用者對 makeAbsoluteExternalsRelative 選項的選擇,轉換為相對 id。可以透過傳遞 external: "relative" 來覆寫此選擇,以永遠將絕對 id 轉換為相對 id,或傳遞 external: "absolute" 來將其保留為絕對 id。傳回物件時,相對外部 id(即以 ./../ 開頭的 id),不會 在內部轉換為絕對 id,並在輸出中轉換回相對 id,而是會不變地包含在輸出中。如果您希望相對 id 重新正規化並取消重複,請傳回絕對檔案系統位置作為 id,並選擇 external: "relative"

如果在解析模組 ID 的第一個掛勾中,moduleSideEffects 傳回 false,且沒有其他模組從此模組匯入任何內容,則即使該模組會產生副作用,也不會包含此模組。如果傳回 true,Rollup 會使用其預設演算法來包含模組中所有會產生副作用的陳述式(例如修改全域或匯出的變數)。如果傳回 "no-treeshake",則會關閉此模組的樹狀搖晃,即使它是空的,也會包含在其中一個產生的區塊中。如果傳回 null 或省略標記,則 moduleSideEffects 將由 treeshake.moduleSideEffects 選項決定,或預設為 trueloadtransform 掛勾可以覆寫此設定。

resolvedBy 可以明確宣告在傳回的物件中。它會取代 this.resolve 傳回的對應欄位。

如果您傳回外部模組的 attributes 值,這將決定產生 "es" 輸出的時候,如何呈現此模組的匯入。例如,{id: "foo", external: true, attributes: {type: "json"}} 會讓此模組的匯入顯示為 import "foo" assert {type: "json"}。如果您沒有傳遞值,將會使用 attributes 輸入參數的值。傳遞一個空物件來移除任何屬性。雖然 attributes 不會影響已綑綁模組的呈現,但它們仍需要在模組的所有匯入中保持一致,否則會發出警告。loadtransform 掛勾可以覆寫此設定。

請參閱 合成命名匯出 以了解 syntheticNamedExports 選項的效果。如果傳回 null 或省略標記,則 syntheticNamedExports 將預設為 falseloadtransform 掛勾可以覆寫此設定。

請參閱 自訂模組元資料 以了解如何使用 meta 選項。如果傳回 null 或省略選項,則 meta 將預設為一個空物件。loadtransform 掛勾可以新增或取代此物件的屬性。

請注意,儘管會針對每個模組的匯入呼叫 resolveId,因此可以多次解析為相同的 id,但 externalattributesmetamoduleSideEffectssyntheticNamedExports 的值只能在載入模組之前設定一次。原因是,在呼叫之後,Rollup 將繼續進行該模組的 loadtransform 鉤子,這些鉤子可能會覆寫這些值,如果確實如此,則應優先使用這些鉤子。

透過 this.resolve 從外掛程式觸發此鉤子時,可以將自訂選項物件傳遞給此鉤子。儘管此物件將不經修改地傳遞,但外掛程式應遵循慣例,新增一個 custom 屬性,其中物件的鍵對應於選項所要使用的外掛程式的名稱。有關詳細資訊,請參閱 自訂解析器選項

在監控模式或明確使用快取時,快取模組的已解析匯入也會從快取中取得,而不會再透過 resolveId 鉤子來決定。若要防止這種情況,您可以從該模組的 shouldTransformCachedModule 鉤子傳回 true。這將從快取中移除模組及其匯入解析,並再次呼叫 transformresolveId

shouldTransformCachedModule

類型ShouldTransformCachedModuleHook
類型非同步,第一
上一個load,其中載入快取檔案以將其程式碼與快取版本進行比較
下一個moduleParsed 如果沒有外掛回傳 true,否則 transform
typescript
type ShouldTransformCachedModuleHook = (options: {
	ast: AstNode;
	code: string;
	id: string;
	meta: { [plugin: string]: any };
	moduleSideEffects: boolean | 'no-treeshake';
	syntheticNamedExports: boolean | string;
}) => boolean | NullValue;

如果使用 Rollup 快取(例如在監控模式或透過 JavaScript API 明確使用),則在 load 鉤子之後,如果載入的 code 與快取副本的程式碼相同,Rollup 會略過模組的 transform 鉤子。為了防止這種情況,請捨棄快取副本,並轉而轉換模組,外掛可以實作這個鉤子並回傳 true

這個鉤子也可以用來找出哪些模組已快取,並存取其快取的元資訊。

如果外掛沒有回傳布林值,Rollup 會為其他外掛觸發這個鉤子,否則會略過所有剩下的外掛。

transform

類型(code: string, id: string) => TransformResult
類型非同步、順序
上一個load 目前處理中的檔案載入的位置。如果使用快取,而且有該模組的快取副本,則 shouldTransformCachedModule 如果外掛為該鉤子回傳 true
下一個moduleParsed 一旦檔案已處理並剖析完成
typescript
type TransformResult = string | null | Partial<SourceDescription>;

interface SourceDescription {
	code: string;
	map?: string | SourceMap;
	ast?: ESTree.Program;
	attributes?: { [key: string]: string } | null;
	meta?: { [plugin: string]: any } | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	syntheticNamedExports?: boolean | string | null;
}

可用於轉換個別模組。若要防止額外解析開銷,例如此掛勾已使用 this.parse 產生 AST 以便基於某種原因,此掛勾可以選擇性地傳回 { code, ast, map } 物件。ast 必須是具有每個節點的 startend 屬性的標準 ESTree AST。如果轉換不會移動程式碼,您可以透過將 map 設定為 null 來保留現有的原始碼對應。否則,您可能需要產生原始碼對應。請參閱 原始碼轉換部分

請注意,在監控模式或明確使用快取時,此掛勾的結果會在重新建置時快取,而且僅當模組 idcode 已變更或檔案已變更時,才會再次觸發模組的掛勾,而該檔案是透過 this.addWatchFile 在上次觸發此模組的掛勾時新增的。

在所有其他情況下,會觸發 shouldTransformCachedModule 掛勾,該掛勾會提供對快取模組的存取權。從 shouldTransformCachedModule 傳回 true 會從快取中移除模組,並改為再次呼叫 transform

您也可以使用回傳值的物件形式來設定模組的其他屬性。請注意,可以只傳回屬性,而不要進行程式碼轉換。

如果傳回 falsemoduleSideEffects,而且沒有其他模組從此模組匯入任何內容,則即使模組會產生副作用,也不會包含此模組。

如果傳回 true,Rollup 將使用其預設演算法,以納入模組中所有具有副作用的陳述式(例如修改全域或匯出的變數)。

如果傳回 "no-treeshake",將為此模組關閉樹狀搖晃,即使模組為空,它也會包含在其中一個產生的區塊中。

如果傳回 null 或省略旗標,則 moduleSideEffects 將由載入此模組的 load 掛鉤、解析此模組的第一個 resolveId 掛鉤、treeshake.moduleSideEffects 選項,或最終預設為 true 來決定。

attributes 包含匯入此模組時使用的匯入屬性。目前,它們不會影響已綑綁模組的呈現,而是用於說明文件目的。如果傳回 null 或省略旗標,則 attributes 將由載入此模組的 load 掛鉤、解析此模組的第一個 resolveId 掛鉤,或此模組第一次匯入中存在的屬性來決定。

請參閱 合成命名匯出,以了解 syntheticNamedExports 選項的效果。如果傳回 null 或省略旗標,則 syntheticNamedExports 將由載入此模組的 load 掛鉤、解析此模組的第一個 resolveId 掛鉤、treeshake.moduleSideEffects 選項,或最終預設為 false 來決定。

請參閱 自訂模組元資料,以了解如何使用 meta 選項。如果傳回 null 或省略選項,則 meta 將由載入此模組的 load 掛鉤、解析此模組的第一個 resolveId 掛鉤,或最終預設為空物件來決定。

您可以在此掛勾中使用 this.getModuleInfo 找出 屬性metamoduleSideEffectssyntheticNamedExports 的先前值。

watchChange

類型watchChange: (id: string, change: {event: 'create' | 'update' | 'delete'}) => void
類型非同步、並行
上一個/下一個此掛勾可以在建置和輸出產生階段的任何時間觸發。如果是這樣,目前的建置仍會繼續進行,但新的建置會排程在目前的建置完成後開始,並從 options 重新開始

當 Rollup 在 --watch 模式中偵測到受監控檔案的變更時,會通知外掛程式。如果傳回 Promise,Rollup 會等到 Promise 解決後才排程另一個建置。輸出外掛程式無法使用此掛勾。第二個參數包含變更事件的其他詳細資料。

輸出產生掛勾

輸出產生掛勾可以提供有關已產生套件的資訊,並在完成後修改建置。它們的工作方式和類型與 建置掛勾 相同,但會針對每次呼叫 bundle.generate(outputOptions)bundle.write(outputOptions) 分別呼叫。僅使用輸出產生掛勾的外掛程式也可以透過輸出選項傳入,因此僅針對特定輸出執行。

輸出產生階段的第一個掛勾是 outputOptions,最後一個是 generateBundle(如果輸出已透過 bundle.generate(...) 成功產生)、writeBundle(如果輸出已透過 bundle.write(...) 成功產生),或 renderError(如果在輸出產生期間的任何時間發生錯誤)。

平行
順序
第一個
非同步
同步

each chunk
each chunk
next chunk
each import()
each import.meta.*
import.meta.url
other
next chunk
banner
footer
intro
outro
renderDynamicImport
resolveFileUrl
resolveImportMeta
augmentChunkHash
closeBundle
generateBundle
outputOptions
renderChunk
renderError
renderStart
writeBundle

此外,closeBundle 可作為最後一個掛鉤呼叫,但手動呼叫 bundle.close() 以觸發此動作是使用者的責任。CLI 將始終確保如此。

augmentChunkHash

類型(chunkInfo: ChunkInfo) => 字串
類型同步,順序
上一個renderChunk
下一個renderChunk 如果仍有其他區塊需要處理,否則 generateBundle

可用於增加個別區塊的雜湊。針對每個 Rollup 輸出區塊呼叫。傳回假值不會修改雜湊。真值會傳遞至 hash.updatechunkInfogenerateBundle 中其中一個較精簡的版本,不含 codemap,且使用檔案名稱中的雜湊佔位符。

下列外掛程式會使用目前時間戳記使區塊 foo 的雜湊失效

js
function augmentWithDatePlugin() {
	return {
		name: 'augment-with-date',
		augmentChunkHash(chunkInfo) {
			if (chunkInfo.name === 'foo') {
				return Date.now().toString();
			}
		}
	};
}
類型字串 | ((chunk: ChunkInfo) => 字串)
類型非同步、順序
上一個resolveFileUrl 針對每個使用 import.meta.ROLLUP_FILE_URL_referenceId 的情況,以及 resolveImportMeta 針對目前區塊中所有其他存取 import.meta 的情況
下一個renderDynamicImport 針對下一個區塊中的每個動態匯入表示式(如果有另一個區塊),否則 renderChunk 針對第一個區塊

請參閱 output.banner/output.footer

closeBundle

類型closeBundle: () => Promise<void> | void
類型非同步、並行
上一個buildEnd 如果有建置錯誤,否則在呼叫 bundle.close() 時,在這種情況下,這將是觸發的最後一個掛鉤

可用於清理任何可能正在執行的外部服務。Rollup 的 CLI 會確保在每次執行後呼叫此掛鉤,但 JavaScript API 的使用者有責任在完成套件產生後手動呼叫 bundle.close()。因此,任何依賴此功能的外掛都應在其文件清楚說明這一點。

如果外掛想要在監控模式下保留跨組建的資源,它們可以在此掛鉤中檢查 this.meta.watchMode,並在 closeWatcher 中執行監控模式的必要清理。

類型字串 | ((chunk: ChunkInfo) => 字串)
類型非同步、順序
上一個resolveFileUrl 針對每個使用 import.meta.ROLLUP_FILE_URL_referenceId 的情況,以及 resolveImportMeta 針對目前區塊中所有其他存取 import.meta 的情況
下一個renderDynamicImport 針對下一個區塊中的每個動態匯入表示式(如果有另一個區塊),否則 renderChunk 針對第一個區塊

請參閱 output.banner/output.footer

generateBundle

類型(options: OutputOptions, bundle: { [fileName: string]: OutputAsset | OutputChunk }, isWrite: boolean) => void
類型非同步、順序
上一個augmentChunkHash
下一個writeBundle 如果輸出是透過 bundle.write(...) 產生的,否則這是輸出產生階段的最後一個掛鉤,並且如果產生另一個輸出,可能會再次後跟 outputOptions
typescript
interface OutputAsset {
	fileName: string;
	name?: string;
	needsCodeReference: boolean;
	source: string | Uint8Array;
	type: 'asset';
}

interface OutputChunk {
	code: string;
	dynamicImports: string[];
	exports: string[];
	facadeModuleId: string | null;
	fileName: string;
	implicitlyLoadedBefore: string[];
	imports: string[];
	importedBindings: { [imported: string]: string[] };
	isDynamicEntry: boolean;
	isEntry: boolean;
	isImplicitEntry: boolean;
	map: SourceMap | null;
	modules: {
		[id: string]: {
			renderedExports: string[];
			removedExports: string[];
			renderedLength: number;
			originalLength: number;
			code: string | null;
		};
	};
	moduleIds: string[];
	name: string;
	preliminaryFileName: string;
	referencedFiles: string[];
	sourcemapFileName: string | null;
	type: 'chunk';
}

bundle.generate() 結束時或在 bundle.write() 中寫入檔案之前立即呼叫。若要在寫入檔案後修改檔案,請使用 writeBundle 掛鉤。bundle 提供正在寫入或產生的所有檔案的完整清單及其詳細資料。

您可以在此掛鉤中從套件物件中刪除檔案,以防止發射檔案。若要發射其他檔案,請使用 this.emitFile 外掛內容函數。

危險

不要直接將資產新增至套件。這會規避 Rollup 追蹤資產的內部機制。這也可能會導致你的資產遺失 Rollup 內部依賴的重要屬性,而且你的外掛可能會在 Rollup 發布次要版本時中斷。

請務必改用 this.emitFile

intro

類型字串 | ((chunk: ChunkInfo) => 字串)
類型非同步、順序
上一個resolveFileUrl 針對每個使用 import.meta.ROLLUP_FILE_URL_referenceId 的情況,以及 resolveImportMeta 針對目前區塊中所有其他存取 import.meta 的情況
下一個renderDynamicImport 針對下一個區塊中的每個動態匯入表示式(如果有另一個區塊),否則 renderChunk 針對第一個區塊

參閱 output.intro/output.outro

outputOptions

類型(outputOptions: OutputOptions) => OutputOptions | null
類型同步,順序
上一個buildEnd 如果這是第一次產生輸出,否則會根據先前產生的輸出,產生 generateBundlewriteBundlerenderError。這是輸出產生階段的第一個掛鉤
下一個renderStart

取代或處理傳遞給 bundle.generate()bundle.write() 的輸出選項物件。傳回 null 就不會取代任何東西。如果你只需要讀取輸出選項,建議使用 renderStart 掛鉤,因為這個掛鉤可以在所有 outputOptions 掛鉤的轉換都考慮進去後,存取輸出選項。

outro

類型字串 | ((chunk: ChunkInfo) => 字串)
類型非同步、順序
上一個resolveFileUrl 針對每個使用 import.meta.ROLLUP_FILE_URL_referenceId 的情況,以及 resolveImportMeta 針對目前區塊中所有其他存取 import.meta 的情況
下一個renderDynamicImport 針對下一個區塊中的每個動態匯入表示式(如果有另一個區塊),否則 renderChunk 針對第一個區塊

參閱 output.intro/output.outro

renderChunk

類型RenderChunkHook
類型非同步、順序
上一個bannerfooterintrooutro 的最後一個區塊
下一個augmentChunkHash
typescript
type RenderChunkHook = (
	code: string,
	chunk: RenderedChunk,
	options: NormalizedOutputOptions,
	meta: { chunks: Record<string, RenderedChunk> }
) => { code: string; map?: SourceMapInput } | string | null;

可用於轉換個別區塊。針對每個 Rollup 輸出區塊檔案呼叫。傳回 null 就不會套用任何轉換。如果你在此掛鉤中變更程式碼,而且想要支援原始碼對應,你需要傳回描述你的變更的 map,請參閱 原始碼轉換部分

chunk 包含使用與 generateBundle 掛鉤相同的 ChunkInfo 類型的區塊其他資訊,差異如下

  • codemap 未設定。請改用此掛勾的 code 參數。
  • 所有包含雜湊的參考區塊檔案名稱都將包含雜湊佔位符。這包括 fileNameimportsimportedBindingsdynamicImportsimplicitlyLoadedBefore。當您在此掛勾傳回的程式碼中使用此類佔位符檔案名稱或其一部分時,Rollup 會在 generateBundle 之前將佔位符替換為實際雜湊,確保雜湊反映最終產生區塊的實際內容,包括所有參考檔案雜湊。

chunk 是可變動的,在此掛勾中套用的變更會傳播到其他外掛程式和產生的套件。這表示如果您在此掛勾中新增或移除匯入或匯出,您應該更新 importsimportedBindings 和/或 exports

meta.chunks 包含 Rollup 產生所有區塊的資訊,並讓您可以存取其 ChunkInfo,再次使用雜湊佔位符。這表示您可以在此掛勾中探索整個區塊圖。

renderDynamicImport

類型renderDynamicImportHook
類型同步,第一個
上一個如果是第一個區塊,則為 renderStart,否則為前一個區塊的 bannerfooterintrooutro
下一個resolveFileUrl 針對每個使用 import.meta.ROLLUP_FILE_URL_referenceId 的情況,以及 resolveImportMeta 針對目前區塊中所有其他存取 import.meta 的情況
typescript
type renderDynamicImportHook = (options: {
	customResolution: string | null;
	format: string;
	moduleId: string;
	targetModuleId: string | null;
}) => { left: string; right: string } | null;

此掛勾提供對動態匯入如何呈現的細緻控制,提供匯入表達式參數左側 (import() 和右側 ()) 的程式碼替換。傳回 null 會遞延到此類型的其他掛勾,並最終呈現特定格式的預設值。

format 是呈現的輸出格式,moduleId 是執行動態匯入的模組 ID。如果匯入可以解析為內部或外部 ID,則 targetModuleId 會設定為此 ID,否則會為 null。如果動態匯入包含非字串表達式,且已由 resolveDynamicImport 掛勾解析為替換字串,則 customResolution 會包含該字串。

以下程式碼會以自訂處理函式取代所有動態匯入,並加入 import.meta.url 作為第二個參數,讓處理函式可以正確解析相對匯入

js
function dynamicImportPolyfillPlugin() {
	return {
		name: 'dynamic-import-polyfill',
		renderDynamicImport() {
			return {
				left: 'dynamicImportPolyfill(',
				right: ', import.meta.url)'
			};
		}
	};
}

// input
import('./lib.js');

// output
dynamicImportPolyfill('./lib.js', import.meta.url);

下一個外掛程式會確保所有 esm-lib 的動態匯入標記為外部,並保留為匯入表達式,例如允許 CommonJS 建置在 Node 13+ 中匯入 ES 模組,請參閱如何 從 CommonJS 匯入 ES 模組,請參閱 Node 文件。

js
function retainImportExpressionPlugin() {
	return {
		name: 'retain-import-expression',
		resolveDynamicImport(specifier) {
			if (specifier === 'esm-lib') return false;
			return null;
		},
		renderDynamicImport({ targetModuleId }) {
			if (targetModuleId === 'esm-lib') {
				return {
					left: 'import(',
					right: ')'
				};
			}
		}
	};
}

請注意,當此掛鉤在非 ES 格式中改寫動態匯入時,不會產生任何互通碼來確保例如預設匯出可用作 .default。外掛程式有責任確保改寫的動態匯入傳回一個 Promise,並解析為適當的命名空間物件。

renderError

類型(error: Error) => void
類型非同步、並行
上一個任何從 renderStartrenderChunk 的掛鉤
下一個如果呼叫它,這是輸出產生階段的最後一個掛鉤,如果產生另一個輸出,它可能會再次由 outputOptions 接續

當 Rollup 在 bundle.generate()bundle.write() 期間遇到錯誤時呼叫。錯誤傳遞給此掛鉤。若要在產生成功完成時收到通知,請使用 generateBundle 掛鉤。

renderStart

類型(outputOptions: OutputOptions, inputOptions: InputOptions) => void
類型非同步、並行
上一個outputOptions
下一個第一個區塊中每個動態匯入表達式的 renderDynamicImport

每次呼叫 bundle.generate()bundle.write() 時,最初會呼叫。若要接收產生完成的通知,請使用 generateBundlerenderError 鉤子。這是需要存取傳遞給 bundle.generate()bundle.write() 的輸出選項時建議使用的鉤子,因為它會考量所有 outputOptions 鉤子的轉換,且包含未設定選項的正確預設值。它也會接收傳遞給 rollup.rollup() 的輸入選項,以便可用作輸出外掛程式的外掛程式(例如,僅使用 產生 階段鉤子的外掛程式)可以存取這些選項。

resolveFileUrl

類型ResolveFileUrlHook
類型同步,第一個
上一個renderDynamicImport,針對目前區塊中的每個動態匯入表達式
下一個bannerfooterintrooutro,針對目前區塊並行執行
typescript
type ResolveFileUrlHook = (options: {
	chunkId: string;
	fileName: string;
	format: InternalModuleFormat;
	moduleId: string;
	referenceId: string;
	relativePath: string;
}) => string | NullValue;

允許自訂 Rollup 如何解析外掛程式透過 this.emitFile 發出的檔案 URL。預設情況下,Rollup 會為 import.meta.ROLLUP_FILE_URL_referenceId 產生程式碼,該程式碼應正確產生發出檔案的絕對 URL,且與輸出格式和部署程式碼的主機系統無關。

為此,除了 CommonJS 和 UMD 之外的所有格式都假設它們在瀏覽器環境中執行,其中 URLdocument 可用。如果失敗或要產生更最佳化的程式碼,可以使用這個鉤子自訂此行為。為此,可以使用下列資訊

  • chunkId:此檔案從中引用的區塊 ID。如果區塊檔案名稱包含雜湊,這個 ID 將包含一個佔位符。如果 Rollup 最終將此佔位符放入產生的程式碼中,它會將這個佔位符替換為實際的檔案名稱。
  • fileName:發出檔案的路徑和檔案名稱,相對於 output.dir,且不含開頭的 ./。同樣地,如果這是名稱中會包含雜湊的區塊,它將包含一個佔位符。
  • format:呈現的輸出格式。
  • moduleId:此檔案所引用的原始模組 ID。這有助於有條件地以不同方式解析特定資產。
  • referenceId:檔案的參考 ID。
  • relativePath:發射檔案的路徑和檔名,相對於檔案所引用的區塊。此路徑不會包含開頭的 ./,但可能會包含開頭的 ../

以下外掛程式會始終將所有檔案解析為相對於目前文件的檔案

js
function resolveToDocumentPlugin() {
	return {
		name: 'resolve-to-document',
		resolveFileUrl({ fileName }) {
			return `new URL('${fileName}', document.baseURI).href`;
		}
	};
}

resolveImportMeta

類型(屬性:字串 | null,{chunkId:字串,moduleId:字串,格式:字串}) => 字串 | null
類型同步,第一個
上一個renderDynamicImport,針對目前區塊中的每個動態匯入表達式
下一個bannerfooterintrooutro,針對目前區塊並行執行

允許自訂 Rollup 處理 import.metaimport.meta.someProperty 的方式,特別是 import.meta.url。在 ES 模組中,import.meta 是物件,而 import.meta.url 包含目前模組的 URL,例如瀏覽器的 http://server.net/bundle.js 或 Node 中的 file:///path/to/bundle.js

預設情況下,對於非 ES 模組的格式,Rollup 會以嘗試比對此行為的程式碼取代 import.meta.url,方法是傳回目前區塊的動態 URL。請注意,除了 CommonJS 和 UMD 之外,所有格式都假設它們在瀏覽器環境中執行,其中 URLdocument 可用。對於其他屬性,import.meta.someProperty 會以 undefined 取代,而 import.meta 則會以包含 url 屬性的物件取代。

這種行為也可以透過此掛勾變更,包括 ES 模組。對於每個 import.meta<.someProperty> 出現,此掛勾會呼叫屬性的名稱,或在直接存取 import.meta 時呼叫 null。例如,以下程式碼會使用原始模組到目前工作目錄的相對路徑來解析 import.meta.url,並再次在執行時根據目前文件的基礎 URL 來解析此路徑

js
function importMetaUrlCurrentModulePlugin() {
	return {
		name: 'import-meta-url-current-module',
		resolveImportMeta(property, { moduleId }) {
			if (property === 'url') {
				return `new URL('${path.relative(
					process.cwd(),
					moduleId
				)}', document.baseURI).href`;
			}
			return null;
		}
	};
}

如果 chunkId 包含雜湊,它將包含一個佔位符。如果此佔位符出現在產生的程式碼中,Rollup 會以實際的區塊雜湊取代它。

writeBundle

類型(options: OutputOptions, bundle: { [fileName: string]: AssetInfo | ChunkInfo }) => void
類型非同步、並行
上一個generateBundle
下一個如果呼叫它,這是輸出產生階段的最後一個掛鉤,如果產生另一個輸出,它可能會再次由 outputOptions 接續

僅在所有檔案寫入完畢後,於 bundle.write() 結尾呼叫。與 generateBundle 掛鉤類似,bundle 提供寫入的完整檔案清單及其詳細資料。

外掛程式內容

許多公用程式功能和資訊位元可透過 this 從大多數 掛鉤 存取

this.addWatchFile

類型(id: string) => void

新增其他檔案在監控模式中監控,以便變更這些檔案會觸發重新建置。id 可以是檔案或目錄的絕對路徑,或相對於目前工作目錄的路徑。此內容函式可用於所有外掛程式掛鉤,但 closeBundle 除外。但是,如果在 輸出產生掛鉤 中使用時 watch.skipWrite 設為 true,則不會產生作用。

注意:通常在監控模式中,為提升重新建置速度,僅當給定模組的內容實際變更時,才會觸發 transform 掛鉤。在 transform 掛鉤中使用 this.addWatchFile 可確保在監控檔案變更時,也會重新評估此模組的 transform 掛鉤。

一般而言,建議在依賴監控檔案的掛鉤中使用 this.addWatchFile

this.debug

類型(log: string | RollupLog | (() => RollupLog | string), position?: number | { column: number; line: number }) => void

產生一個 "debug" 紀錄。有關詳細資訊,請參閱 this.warn。Rollup 會自動加入 code: "PLUGIN_LOG" 至偵錯紀錄。請務必為這些紀錄加入一個獨特的 pluginCode,以便輕鬆篩選。

只有在 logLevel 選項明確設為 "debug" 時,才會處理這些紀錄,否則不會執行任何動作。因此,建議在外掛程式中加入有用的偵錯紀錄,因為這有助於找出問題,同時這些紀錄在預設情況下會被有效地靜音。如果您需要進行昂貴的運算來產生紀錄,請務必使用函式形式,這樣只有在實際處理紀錄時才會執行這些運算。

js
function plugin() {
	return {
		name: 'test',
		transform(code, id) {
			this.debug(
				() =>
					`transforming ${id},\n` +
					`module contains, ${code.split('\n').length} lines`
			);
		}
	};
}

this.emitFile

類型(emittedFile: EmittedChunk | EmittedPrebuiltChunk | EmittedAsset) => string
typescript
interface EmittedChunk {
	type: 'chunk';
	id: string;
	name?: string;
	fileName?: string;
	implicitlyLoadedAfterOneOf?: string[];
	importer?: string;
	preserveSignature?: 'strict' | 'allow-extension' | 'exports-only' | false;
}

interface EmittedPrebuiltChunk {
	type: 'prebuilt-chunk';
	fileName: string;
	code: string;
	exports?: string[];
	map?: SourceMap;
}

interface EmittedAsset {
	type: 'asset';
	name?: string;
	needsCodeReference?: boolean;
	fileName?: string;
	source?: string | Uint8Array;
}

發佈一個包含在建置輸出的新檔案,並傳回一個 referenceId,可在各個地方用來參照發佈的檔案。您可以發佈區塊、預建置區塊或資產。

在發佈區塊或資產時,可以提供 namefileName。如果提供 fileName,它將會原封不動地用作產生檔案的名稱,如果這會造成衝突,則會擲回錯誤。否則,如果提供 name,它將會用作對應 output.chunkFileNamesoutput.assetFileNames 模式中的 [name] 的替換,可能會在檔名尾端加上一個唯一的數字以避免衝突。如果未提供 namefileName,則會使用預設名稱。預建置區塊必須始終有一個 fileName

你可以透過 loadtransform 外掛程式,參考由 檔案 URL 所發射的檔案 URL,詳細資訊和範例請參閱 檔案 URL

取代 import.meta.ROLLUP_FILE_URL_referenceId 的產生程式碼,可以透過 resolveFileUrl 外掛程式進行自訂。你也可以使用 this.getFileName(referenceId) 來確定檔案名稱,只要檔案名稱一出現即可。如果檔案名稱沒有明確設定,則

  • 資產檔案名稱會從 renderStart 程式開始出現。對於稍後發射的資產,檔案名稱會在發射資產後立即出現。
  • 不包含雜湊的區塊檔案名稱,會在 renderStart 程式之後,區塊建立後立即出現。
  • 如果區塊檔案名稱包含雜湊,在 generateBundle 之前的任何程式中使用 getFileName,會傳回包含佔位符的名稱,而不是實際名稱。如果你在 renderChunk 中轉換區塊時,使用這個檔案名稱或其中一部分,Rollup 會在 generateBundle 之前,用實際雜湊取代佔位符,確保雜湊反映最終產生區塊的實際內容,包括所有參考的檔案雜湊。

如果 typechunk,則會發射一個新的區塊,並將給定的模組 id 作為進入點。為了解析它,id 會透過建置程式,就像一般的進入點一樣,從 resolveId 開始傳遞。如果提供 importer,這會作為 resolveId 的第二個參數,對於正確解析相對路徑非常重要。如果沒有提供,路徑會相對於目前的工作目錄解析。如果提供 preserveSignature 的值,這會覆寫這個特定區塊的 preserveEntrySignatures

這不會在圖形中產生重複模組,而是會在必要時分割現有區塊,或建立具有重新輸出的外觀區塊。具有指定 fileName 的區塊將始終產生個別區塊,而其他發出的區塊可能會與現有區塊重複,即使 name 不相符。如果未重複這樣的區塊,將會使用 output.chunkFileNames 名稱模式。

預設情況下,Rollup 假設發出的區塊獨立於其他進入點執行,甚至可能在執行任何其他程式碼之前執行。這表示如果發出的區塊與現有進入點共用相依性,Rollup 將為那些進入點之間共用的相依性建立額外的區塊。針對 implicitlyLoadedAfterOneOf 提供非空的模組 ID 陣列將透過在某些情況下提供 Rollup 其他資訊來改變該行為,以防止這種情況。這些 ID 將以與 id 屬性相同的方式解析,如果提供 importer 屬性,則會尊重該屬性。Rollup 現在會假設只有在導致 implicitlyLoadedAfterOneOf 中的某個 ID 被載入的其中一個進入點已經執行的情況下,才會執行發出的區塊,並建立與僅能透過 implicitlyLoadedAfterOneOf 中的模組的動態匯入來存取新發出區塊時相同的區塊。以下是使用此功能建立具有多個指令碼的簡單 HTML 檔案的範例,建立最佳化的區塊以尊重其執行順序

js
// rollup.config.js
function generateHtmlPlugin() {
	let ref1, ref2, ref3;
	return {
		name: 'generate-html',
		buildStart() {
			ref1 = this.emitFile({
				type: 'chunk',
				id: 'src/entry1'
			});
			ref2 = this.emitFile({
				type: 'chunk',
				id: 'src/entry2',
				implicitlyLoadedAfterOneOf: ['src/entry1']
			});
			ref3 = this.emitFile({
				type: 'chunk',
				id: 'src/entry3',
				implicitlyLoadedAfterOneOf: ['src/entry2']
			});
		},
		generateBundle() {
			this.emitFile({
				type: 'asset',
				fileName: 'index.html',
				source: `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="UTF-8">
          <title>Title</title>
         </head>
        <body>
          <script src="${this.getFileName(ref1)}" type="module"></script>
          <script src="${this.getFileName(ref2)}" type="module"></script>
          <script src="${this.getFileName(ref3)}" type="module"></script>
        </body>
        </html>`
			});
		}
	};
}

export default {
	input: [],
	preserveEntrySignatures: false,
	plugins: [generateHtmlPlugin()],
	output: {
		format: 'es',
		dir: 'dist'
	}
};

如果沒有動態導入,這將建立三個區塊,其中第一個區塊包含 src/entry1 的所有依賴項,第二個區塊僅包含 src/entry2 的依賴項(不包含在第一個區塊中),從第一個區塊導入這些依賴項,第三個區塊也是如此。

請注意,即使任何模組 ID 都可以在 implicitlyLoadedAfterOneOf 中使用,如果無法將此類 ID 與區塊唯一關聯,Rollup 將會擲回錯誤,例如因為無法從現有的靜態進入點隱式或明確地存取 id,或因為檔案已完全樹狀搖晃。不過,僅使用進入點(由使用者定義或先前發出的區塊)始終有效。

如果 typeprebuilt-chunk,它會發出一個區塊,其中包含由 code 參數提供的固定內容。目前,fileName 也需要提供區塊的名稱。如果它匯出一些變數,我們應該透過選用的 exports 列出這些變數。透過 map,我們可以提供與 code 相應的原始碼對應。

要在導入中參照預建區塊,我們需要在 resolveId 掛鉤中將「模組」標記為外部,因為預建區塊不屬於模組圖表。相反地,它們的行為就像具有區塊元資料的資產

js
function emitPrebuiltChunkPlugin() {
	return {
		name: 'emit-prebuilt-chunk',
		resolveId(source) {
			if (source === './my-prebuilt-chunk.js') {
				return {
					id: source,
					external: true
				};
			}
		},
		buildStart() {
			this.emitFile({
				type: 'prebuilt-chunk',
				fileName: 'my-prebuilt-chunk.js',
				code: 'export const foo = "foo"',
				exports: ['foo']
			});
		}
	};
}

然後,您可以在程式碼中參照預建區塊

js
import { foo } from './my-prebuilt-chunk.js';

目前,發出預建區塊是一項基本功能。期待您的回饋。

如果 typeasset,則會發射一個任意的檔案,其內容為給定的 source。可以延後設定 source,透過 this.setAssetSource(referenceId, source),以便在建置階段參考檔案,同時在產生階段為每個輸出分別設定來源。具有指定 fileName 的資產將永遠產生獨立的檔案,而其他發射的資產可能會與現有資產重複,即使 name 不相符,只要它們具有相同的來源。如果沒有 fileName 的資產沒有重複,則會使用 output.assetFileNames 名稱模式。如果 needsCodeReference 設定為 true,且此資產未透過 import.meta.ROLLUP_FILE_URL_referenceId 被輸出中的任何程式碼參考,則 Rollup 將不會發射它。這也會尊重透過 tree-shaking 移掉的參考,亦即,如果對應的 import.meta.ROLLUP_FILE_URL_referenceId 是原始碼的一部分,但實際上沒有使用,且參考已透過 tree-shaking 移掉,則不會發射資產。

this.error

類型(error: string | RollupLog | Error, position?: number | { column: number; line: number }) => never

結構上等同於 this.warn,但它也會中斷綑綁程序,並傳回錯誤。請參閱 onLog 選項,以取得有關 RollupLog 類型的資訊。

如果傳遞 Error 執行個體,則會原樣使用,否則會建立新的 Error 執行個體,並附上給定的錯誤訊息和所有其他提供的屬性。

onLog 鉤子以外的所有鉤子中,錯誤會擴充 code: "PLUGIN_ERROR"plugin: plugin.name 屬性。如果 code 屬性已存在,且程式碼未以 PLUGIN_ 開頭,則會將其重新命名為 pluginCode

onLog 鉤子中,此函式是一種簡單的方法,可在保留警告的所有其他屬性的同時,將警告轉換為錯誤

js
function myPlugin() {
	return {
		name: 'my-plugin',
		onLog(level, log) {
			if (level === 'warn' && log.code === 'THIS_IS_NOT_OK') {
				return this.error(log);
			}
		}
	};
}

當使用於 transform 掛鉤時,目前模組的 id 也會加入,且可以提供 position。這是一個字元索引或檔案位置,將用於使用 posloc(一個標準的 { file, line, column } 物件)和 frame(顯示位置的程式碼片段)來擴充日誌。

this.getCombinedSourcemap

類型() => SourceMap

取得所有先前外掛的合併原始碼對應。此內容函式只能用於 transform 外掛掛鉤。

this.getFileName

類型(referenceId: string) => string

取得透過 this.emitFile 發射的區塊或資產的檔案名稱。檔案名稱會相對於 outputOptions.dir

this.getModuleIds

類型() => IterableIterator<string>

傳回一個 Iterator,提供存取目前圖形中所有模組 ID 的功能。它可以透過

js
for (const moduleId of this.getModuleIds()) {
	/* ... */
}

或透過 Array.from(this.getModuleIds()) 轉換成陣列。

this.getModuleInfo

類型(moduleId: string) => (ModuleInfo | null)
typescript
interface ModuleInfo {
	id: string; // the id of the module, for convenience
	code: string | null; // the source code of the module, `null` if external or not yet available
	ast: ESTree.Program; // the parsed abstract syntax tree if available
	hasDefaultExport: boolean | null; // is there a default export, `null` if external or not yet available
	isEntry: boolean; // is this a user- or plugin-defined entry point
	isExternal: boolean; // for external modules that are referenced but not included in the graph
	isIncluded: boolean | null; // is the module included after tree-shaking, `null` if external or not yet available
	importedIds: string[]; // the module ids statically imported by this module
	importedIdResolutions: ResolvedId[]; // how statically imported ids were resolved, for use with this.load
	importers: string[]; // the ids of all modules that statically import this module
	exportedBindings: Record<string, string[]> | null; // contains all exported variables associated with the path of `from`, `null` if external
	exports: string[] | null; // all exported variables, `null` if external
	dynamicallyImportedIds: string[]; // the module ids imported by this module via dynamic import()
	dynamicallyImportedIdResolutions: ResolvedId[]; // how ids imported via dynamic import() were resolved
	dynamicImporters: string[]; // the ids of all modules that import this module via dynamic import()
	implicitlyLoadedAfterOneOf: string[]; // implicit relationships, declared via this.emitFile
	implicitlyLoadedBefore: string[]; // implicit relationships, declared via this.emitFile
	attributes: { [key: string]: string }; // import attributes for this module
	meta: { [plugin: string]: any }; // custom module meta-data
	moduleSideEffects: boolean | 'no-treeshake'; // are imports of this module included if nothing is imported from it
	syntheticNamedExports: boolean | string; // final value of synthetic named exports
}

interface ResolvedId {
	id: string; // the id of the imported module
	external: boolean | 'absolute'; // is this module external, "absolute" means it will not be rendered as relative in the module
	attributes: { [key: string]: string }; // import attributes for this import
	meta: { [plugin: string]: any }; // custom module meta-data when resolving the module
	moduleSideEffects: boolean | 'no-treeshake'; // are side effects of the module observed, is tree-shaking enabled
	resolvedBy: string; // which plugin resolved this module, "rollup" if resolved by Rollup itself
	syntheticNamedExports: boolean | string; // does the module allow importing non-existing named exports
}

傳回關於相關模組的其他資訊。

在建置期間,此物件表示目前關於模組的可用資訊,在 buildEnd 掛鉤之前可能不準確

  • idisExternal 永遠不會變更。
  • codeasthasDefaultExportexportsexportedBindings 僅在解析後可用,亦即在 moduleParsed 鉤子中或在等待 this.load 之後。在那個時間點,它們將不再變更。
  • 如果 isEntrytrue,它將不再變更。然而,模組有可能在解析後透過 this.emitFile 或因為外掛在 resolveId 鉤子中解析進入點時透過 this.load 檢查潛在進入點而成為進入點。因此,不建議在 transform 鉤子中依賴此旗標。它將在 buildEnd 之後不再變更。
  • 類似地,implicitlyLoadedAfterOneOf 可以透過 this.emitFilebuildEnd 之前隨時接收其他進入點。
  • importersdynamicImportersimplicitlyLoadedBefore 將從空陣列開始,並在發現新的匯入者和隱含依賴項時接收其他進入點。它們將在 buildEnd 之後不再變更。
  • isIncluded 僅在 buildEnd 之後可用,在那個時間點,它將不再變更。
  • 當模組已解析且其依賴項已解析時,importedIdsimportedIdResolutionsdynamicallyImportedIdsdynamicallyImportedIdResolutions 可用。這是 moduleParsed 鉤子中的情況,或在使用 resolveDependencies 旗標等待 this.load 之後。在那個時間點,它們將不再變更。
  • attributesmetamoduleSideEffectssyntheticNamedExports 可由 loadtransform 鉤子變更。此外,儘管大多數屬性為唯讀,但這些屬性可寫入,且若在觸發 buildEnd 鉤子之前變更,變更將會被採用。meta 本身不應被覆寫,但隨時變異其屬性以儲存模組的元資訊是沒問題的。這樣做的優點在於,若使用 meta,它會被保留到快取並從快取中還原,例如在 CLI 中使用監控模式時。

如果找不到模組 ID,則傳回 null

this.getWatchFiles

類型() => string[]

取得先前已監控檔案的 ID。包括由外掛程式使用 this.addWatchFile 新增的檔案,以及 Rollup 在建置期間隱式新增的檔案。

this.info

類型(log: string | RollupLog | (() => RollupLog | string), position?: number | { column: number; line: number }) => void

產生 "info" 日誌。請參閱 this.warn 以取得詳細資訊。Rollup 會始終為資訊日誌新增 code: "PLUGIN_LOG"。由於這些日誌會預設顯示,因此請將其用於非警告但對所有使用者在每次建置時顯示都合理的資訊。

如果 logLevel 選項設為 "warn""silent",此方法不會執行任何動作。

this.load

類型載入
typescript
type Load = (options: {
	id: string;
	resolveDependencies?: boolean;
	attributes?: Record<string, string> | null;
	meta?: CustomPluginOptions | null;
	moduleSideEffects?: boolean | 'no-treeshake' | null;
	syntheticNamedExports?: boolean | string | null;
}) => Promise<ModuleInfo>;

載入並剖析與給定 id 相應的模組,如果提供的話,會附加額外的元資訊到模組。這會觸發與模組被其他模組匯入時會觸發的相同 loadtransformmoduleParsed 掛鉤。

這允許你在決定如何在 resolveId 掛鉤中解析它們,並例如解析為代理模組之前,檢查模組的最終內容。如果模組稍後成為圖形的一部分,則使用此內容函式不會產生額外的開銷,因為模組不會再次被剖析。簽章允許你直接將 this.resolve 的傳回值傳遞給此函式,只要它既不是 null 也不是外部的。

在模組已完全轉換和剖析完成,但在任何匯入已解析之前,傳回的 Promise 會解析。這表示結果的 ModuleInfo 會有空的 importedIdsdynamicallyImportedIdsimportedIdResolutionsdynamicallyImportedIdResolutions。這有助於在 resolveId 掛鉤中等待 this.load 時避免死結情況。如果你對 importedIdsdynamicallyImportedIds 有興趣,你可以實作 moduleParsed 掛鉤或傳遞 resolveDependencies 旗標,這會讓 this.load 傳回的 Promise 等待,直到所有依賴項 id 已解析。

請注意,關於 attributesmetamoduleSideEffectssyntheticNamedExports 選項,與 resolveId 掛鉤套用相同的限制:它們的值只有在模組尚未載入時才會產生作用。因此,首先使用 this.resolve 來找出是否有任何外掛程式想要在其 resolveId 掛鉤中為這些選項設定特殊值,並在適當的情況下將這些選項傳遞給 this.load,這一點非常重要。以下範例展示如何處理這一點,以新增一個代理模組,供包含特殊程式碼註解的模組使用。請注意重新匯出預設匯出的特殊處理

js
export default function addProxyPlugin() {
	return {
		async resolveId(source, importer, options) {
			if (importer?.endsWith('?proxy')) {
				// Do not proxy ids used in proxies
				return null;
			}
			// We make sure to pass on any resolveId options to
			// this.resolve to get the module id
			const resolution = await this.resolve(source, importer, options);
			// We can only pre-load existing and non-external ids
			if (resolution && !resolution.external) {
				// we pass on the entire resolution information
				const moduleInfo = await this.load(resolution);
				if (moduleInfo.code.includes('/* use proxy */')) {
					return `${resolution.id}?proxy`;
				}
			}
			// As we already fully resolved the module, there is no reason
			// to resolve it again
			return resolution;
		},
		load(id) {
			if (id.endsWith('?proxy')) {
				const importee = id.slice(0, -'?proxy'.length);
				// Note that namespace reexports do not reexport default
				// exports
				let code = `console.log('proxy for ${importee}'); export * from ${JSON.stringify(
					importee
				)};`;
				// We know that while resolving the proxy, importee was
				// already fully loaded and parsed, so we can rely on
				// hasDefaultExport
				if (this.getModuleInfo(importee).hasDefaultExport) {
					code += `export { default } from ${JSON.stringify(importee)};`;
				}
				return code;
			}
			return null;
		}
	};
}

如果模組已載入,this.load 會等到剖析完成,然後傳回模組資訊。如果模組尚未由其他模組匯入,它不會自動觸發載入此模組匯入的其他模組。相反地,靜態和動態相依性只會在這個模組實際匯入至少一次後才載入。

雖然在 resolveId 鉤子中使用 this.load 是安全的,但在 loadtransform 鉤子中等待它時,你應該非常小心。如果模組圖中存在循環相依性,這很容易導致死結,因此任何外掛程式都需要手動小心避免在與載入模組有循環的任何模組的 loadtransform 中等待 this.load

以下是一個更精緻的範例,我們透過 resolveDependencies 選項和重複呼叫 this.load 來掃描整個相依性子圖。我們使用處理過的模組 ID 的 Set 來處理循環相依性。外掛程式的目標是將記錄新增到每個動態匯入的區塊中,其中僅列出區塊中的所有模組。雖然這只是一個玩具範例,但可以用此技術來例如為子圖中匯入的所有 CSS 建立一個樣式標籤。

js
// The leading \0 instructs other plugins not to try to resolve, load or
// transform our proxy modules
const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:';

export default function dynamicChunkLogsPlugin() {
	return {
		name: 'dynamic-chunk-logs',
		async resolveDynamicImport(specifier, importer) {
			// Ignore non-static targets
			if (!(typeof specifier === 'string')) return;
			// Get the id and initial meta information of the import target
			const resolved = await this.resolve(specifier, importer);
			// Ignore external targets. Explicit externals have the
			// "external" property while unresolved imports are "null".
			if (resolved && !resolved.external) {
				// We trigger loading the module without waiting for it
				// here because meta information attached by resolveId
				// hooks, that may be contained in "resolved" and that
				// plugins like "commonjs" may depend upon, is only
				// attached to a module the first time it is loaded. This
				// ensures that this meta information is not lost when we
				// later use "this.load" again in the load hook with just
				// the module id.
				this.load(resolved);
				return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`;
			}
		},
		async load(id) {
			// Ignore all files except our dynamic import proxies
			if (!id.startsWith('\0dynamic-import:')) return null;
			const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length);
			// To allow loading modules in parallel while keeping
			// complexity low, we do not directly await each "this.load"
			// call but put their promises into an array where we await
			// them via an async for loop.
			const moduleInfoPromises = [
				this.load({ id: actualId, resolveDependencies: true })
			];
			// We track each loaded dependency here so that we do not load
			// a file twice and also do  not get stuck when there are
			// circular dependencies.
			const dependencies = new Set([actualId]);
			// "importedIdResolutions" tracks the objects created by
			// resolveId hooks. We are using those instead of "importedIds"
			// so that again, important meta information is not lost.
			for await (const { importedIdResolutions } of moduleInfoPromises) {
				for (const resolved of importedIdResolutions) {
					if (!dependencies.has(resolved.id)) {
						dependencies.add(resolved.id);
						moduleInfoPromises.push(
							this.load({ ...resolved, resolveDependencies: true })
						);
					}
				}
			}
			// We log all modules in a dynamic chunk when it is loaded.
			let code = `console.log([${[...dependencies]
				.map(JSON.stringify)
				.join(', ')}]); export * from ${JSON.stringify(actualId)};`;
			// Namespace reexports do not reexport default exports, which
			// is why we reexport it manually if it exists
			if (this.getModuleInfo(actualId).hasDefaultExport) {
				code += `export { default } from ${JSON.stringify(actualId)};`;
			}
			return code;
		}
	};
}

this.meta

類型{rollupVersion: string, watchMode: boolean}

包含可能有用的 Rollup 元資料的物件

  • rollupVersion:在 package.json 中定義的 Rollup 目前執行的版本。
  • watchMode:如果 Rollup 是透過 rollup.watch(...) 或命令列加上 --watch 啟動,則為 true,否則為 false

meta 是唯一可從 options 掛鉤存取的內容屬性。

this.parse

類型(code: string, options?: ParseOptions) => ESTree.Program
typescript
interface ParseOptions {
	allowReturnOutsideFunction?: boolean;
}

使用 Rollup 內部的基於 SWC 的解析器將程式碼解析為與 ESTree 相容 的 AST。

  • allowReturnOutsideFunction:當為 true 時,這允許回傳陳述式出現在函式外部,例如支援解析 CommonJS 程式碼。

this.resolve

類型解析
typescript
type Resolve = (
	source: string,
	importer?: string,
	options?: {
		skipSelf?: boolean;
		isEntry?: boolean;
		attributes?: { [key: string]: string };
		custom?: { [plugin: string]: any };
	}
) => ResolvedId;

提示

此掛鉤的回傳類型 ResolvedId 定義於 this.getModuleInfo

使用 Rollup 使用的相同外掛程式將匯入解析為模組 ID(即檔案名稱),並判斷匯入是否應為外部。如果回傳 null,表示匯入無法由 Rollup 或任何外掛程式解析,但使用者並未明確標記為外部。如果回傳絕對外部 ID,則應透過 makeAbsoluteExternalsRelative 選項或 resolveId 掛鉤中的明確外掛程式選擇在輸出中保持絕對,則 external 將為 "absolute",而不是 true

skipSelf 的預設值為 true,因此在解析時將略過呼叫 this.resolve 的外掛程式的 resolveId 掛鉤。當其他外掛程式本身也在其 resolveId 掛鉤中呼叫 this.resolve,且在處理原始 this.resolve 呼叫時具有完全相同的 sourceimporter 時,原始外掛程式的 resolveId 掛鉤也會略過這些呼叫。此處的理由是,外掛程式已聲明它「不知道」如何在此時解析 sourceimporter 的此特定組合。如果您不希望有此行為,請將 skipSelf 設定為 false,並在必要時實作您自己的無限迴圈防範機制。

您也可以透過 custom 選項傳遞外掛程式特定選項的物件,詳情請參閱 自訂解析器選項

您在此處傳遞的 isEntry 值將傳遞給處理此呼叫的 resolveId 掛鉤,否則如果有一個匯入器,將傳遞 false,如果沒有,則傳遞 true

如果您傳遞一個物件給 attributes,它將模擬解析一個帶有斷言的匯入,例如 attributes: {type: "json"} 模擬解析 import "foo" assert {type: "json"}。這將傳遞給處理此呼叫的任何 resolveId 掛鉤,並且最終可能成為傳回物件的一部分。

resolveId 掛鉤呼叫此函式時,您應始終檢查傳遞 isEntrycustomattributes 選項是否對您有意義。

resolvedBy 的值指的是哪個外掛程式解析了此來源。如果它是由 Rollup 本身解析的,則值將為「rollup」。如果外掛程式中的 resolveId 掛鉤解析了此來源,則值將為外掛程式的名稱,除非它傳回 resolvedBy 的明確值。此標記僅用於偵錯和文件目的,並且 Rollup 沒有進一步處理。

this.setAssetSource

類型(referenceId: string, source: string | Uint8Array) => void

設定資產的遞延來源。請注意,您也可以傳遞 Node Buffer 作為 source,因為它是 Uint8Array 的子類別。

this.warn

類型(log: string | RollupLog | (() => RollupLog | string), position?: number | { column: number; line: number }) => void

使用此方法將為組建產生警告,這些警告是記錄層級為 "warn" 的記錄。請參閱 onLog 選項以取得關於 RollupLog 類型的資訊。若要產生其他記錄,請參閱 this.infothis.debug。若要產生錯誤,請參閱 this.error

就像內部產生的警告一樣,這些記錄會先傳遞給並由外掛 onLog 鉤子過濾,然後才會轉發給自訂的 onLogonwarn 處理常式或列印到主控台。

warning 參數可以是 string 或一個物件(至少有 message 屬性)

js
this.warn('hmm...');
// is equivalent to
this.warn({
	message: 'hmm...',
	pluginCode: 'CODE_TO_IDENTIFY_LOG',
	meta: 'Additional plugin specific information'
});

我們鼓勵您使用具有 pluginCode 屬性的物件,因為這將允許使用者在 onLog 處理常式中輕鬆過濾這些記錄。如果您需要新增額外資訊,您可以使用 meta 屬性。如果記錄包含 code 且尚未有 pluginCode 屬性,它將重新命名為 pluginCode,因為外掛警告總會由 Rollup 新增 codePLUGIN_WARNING。為了防止這種行為,外掛可以改用傳遞給 buildStart 鉤子的正規化 onLog 選項。從外掛呼叫此選項不會在傳遞記錄給外掛 onLog 處理常式和 onLogonwarn 處理常式時變更屬性。

如果您需要進行昂貴的運算來產生記錄,您也可以傳遞一個回傳 stringRollupLog 物件的函式。此函式只會在記錄未被 logLevel 選項過濾時呼叫。

js
// This will only run if the logLevel is set to "debug"
this.debug(() => generateExpensiveDebugLog());

當使用於 transform 掛鉤時,目前模組的 id 也會加入,且可以提供 position。這是一個字元索引或檔案位置,將用於使用 posloc(一個標準的 { file, line, column } 物件)和 frame(顯示位置的程式碼片段)來擴充日誌。

如果 logLevel 選項設為 "silent",此方法不會執行任何動作。

檔案 URL

若要從 JS 程式碼內參照檔案 URL 參照,請使用 import.meta.ROLLUP_FILE_URL_referenceId 取代。這會產生依賴於輸出格式的程式碼,並產生指向目標環境中已發射檔案的 URL。請注意,除了 CommonJS 和 UMD 之外的所有格式都假設它們在瀏覽器環境中執行,其中 URLdocument 可用。

以下範例將偵測 .svg 檔案的匯入,將匯入的檔案發射為資產,並傳回其 URL 以用於例如 img 標籤的 src 屬性

js
function svgResolverPlugin() {
	return {
		name: 'svg-resolver',
		resolveId(source, importer) {
			if (source.endsWith('.svg')) {
				return path.resolve(path.dirname(importer), source);
			}
		},
		load(id) {
			if (id.endsWith('.svg')) {
				const referenceId = this.emitFile({
					type: 'asset',
					name: path.basename(id),
					source: fs.readFileSync(id)
				});
				return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
			}
		}
	};
}

用法

js
import logo from '../images/logo.svg';
const image = document.createElement('img');
image.src = logo;
document.body.appendChild(image);

有時,參照此資產的程式碼僅有條件地使用,如下面的範例所示

js
import logo from '../images/logo.svg';
if (COMPILER_FLAG) {
	const image = document.createElement('img');
	image.src = logo;
	document.body.appendChild(image);
}

如果外掛程式將 COMPILER_FLAG 取代為 false,則我們將得到一個意外的結果:未參照的資產仍會發射,但未被使用。我們可以在呼叫 this.emitFile 時將 needsCodeReference 設為 true 來解決此問題,如下面的程式碼所示

js
function svgResolverPlugin() {
	return {
		/* ... */
		load(id) {
			if (id.endsWith('.svg')) {
				const referenceId = this.emitFile({
					type: 'asset',
					name: path.basename(id),
					needsCodeReference: true,
					source: fs.readFileSync(id)
				});
				return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
			}
		}
	};
}

現在,只有當實際在程式碼中使用參照 import.meta.ROLLUP_FILE_URL_referenceId 時,資產才會新增到套件中。

與資產類似,已發射的區塊也可以透過 import.meta.ROLLUP_FILE_URL_referenceId 從 JS 程式碼內參照。

以下範例將偵測以 register-paint-worklet: 為字首的匯入,並產生必要的程式碼和個別區塊來產生 CSS 繪製工作列。請注意,這只會在現代瀏覽器中運作,而且只有在輸出格式設為 es 時才會運作。

js
const REGISTER_WORKLET = 'register-paint-worklet:';

function registerPaintWorkletPlugin() {
	return {
		name: 'register-paint-worklet',
		load(id) {
			if (id.startsWith(REGISTER_WORKLET)) {
				return `CSS.paintWorklet.addModule(import.meta.ROLLUP_FILE_URL_${this.emitFile(
					{
						type: 'chunk',
						id: id.slice(REGISTER_WORKLET.length)
					}
				)});`;
			}
		},
		resolveId(source, importer) {
			// We remove the prefix, resolve everything to absolute ids and
			// add the prefix again. This makes sure that you can use
			// relative imports to define worklets
			if (source.startsWith(REGISTER_WORKLET)) {
				return this.resolve(
					source.slice(REGISTER_WORKLET.length),
					importer
				).then(resolvedId => REGISTER_WORKLET + resolvedId.id);
			}
			return null;
		}
	};
}

用法

js
// main.js
import 'register-paint-worklet:./worklet.js';
import { color, size } from './config.js';
document.body.innerHTML += `<h1 style="background-image: paint(vertical-lines);">color: ${color}, size: ${size}</h1>`;

// worklet.js
import { color, size } from './config.js';
registerPaint(
	'vertical-lines',
	class {
		paint(ctx, geom) {
			for (let x = 0; x < geom.width / size; x++) {
				ctx.beginPath();
				ctx.fillStyle = color;
				ctx.rect(x * size, 0, 2, geom.height);
				ctx.fill();
			}
		}
	}
);

// config.js
export const color = 'greenyellow';
export const size = 6;

如果您建立此程式碼,主區塊和工作單元都會透過共用區塊,共用來自 config.js 的程式碼。這讓我們能夠利用瀏覽器快取,以減少傳輸資料並加速載入工作單元。

變壓器

變壓器外掛程式 (例如,傳回 transform 函數,用於轉譯非 JS 檔案) 應支援 options.includeoptions.exclude,兩者都可以是 minimatch 模式或 minimatch 模式陣列。如果省略 options.include 或其長度為零,則預設應包含檔案;否則,僅當 ID 符合其中一個模式時,才應包含檔案。

如果 transform 勾子傳回物件,也可以包含 ast 屬性。只有在知道自己在做什麼時,才使用此功能。請注意,在變換鏈中,只有最後一個 AST 會被使用 (而且如果有變換,則 load 勾子產生的任何 AST 都會被轉換的模組捨棄。)

範例變壓器

(使用 @rollup/pluginutils 來取得常見所需函數,並以建議的方式實作變壓器。)

js
import { createFilter } from '@rollup/pluginutils';

function transformCodePlugin(options = {}) {
	const filter = createFilter(options.include, options.exclude);

	return {
		name: 'transform-code',
		transform(code, id) {
			if (!filter(id)) return;

			// proceed with the transformation...
			return {
				code: generatedCode,
				map: generatedSourceMap
			};
		}
	};
}

原始碼變換

如果外掛程式變換原始碼,則應自動產生原始碼對應,除非有特定的 sourceMap: false 選項。Rollup 只關心 mappings 屬性 (其他所有內容都會自動處理)。magic-string 提供一個簡單的方法,可以為基本變換 (例如新增或移除程式碼片段) 產生此類對應。

如果產生原始碼對應沒有意義 (例如 rollup-plugin-string),則傳回一個空的原始碼對應

js
return {
	code: transformedCode,
	map: { mappings: '' }
};

如果變換不會移動程式碼,則您可以傳回 null 來保留現有的原始碼對應

js
return {
	code: transformedCode,
	map: null
};

如果你建立的插件你認為對其他人有幫助,請發布到 NPM 並將其提交到 github.com/rollup/awesome

合成命名輸出

透過在 resolveIdloadtransform 鉤子中設定模組的 syntheticNamedExports 選項,可以指定遺失輸出項目的備用輸出。如果 syntheticNamedExports 使用字串值,此模組會將任何遺失的命名輸出項目的解析備份到指定名稱的命名輸出的屬性

dep.js: ({syntheticNamedExports: '__synthetic'})

js
export const foo = 'explicit';
export const __synthetic = {
	foo: 'foo',
	bar: 'bar'
};

main.js

js
import { foo, bar, baz, __synthetic } from './dep.js';

// logs "explicit" as non-synthetic exports take precedence
console.log(foo);

// logs "bar", picking the property from __synthetic
console.log(bar);

// logs "undefined"
console.log(baz);

// logs "{foo:'foo',bar:'bar'}"
console.log(__synthetic);

當用作進入點時,只會公開明確的輸出。合成備用輸出,即範例中的 __synthetic,不會公開 syntheticNamedExports 的字串值。不過,如果值是 true,預設輸出會公開。這是 syntheticNamedExports: truesyntheticNamedExports: 'default' 之間唯一的顯著差異。

插件間通訊

在使用許多專用插件時,在某些時候,可能需要不相關的插件在建置期間能夠交換資訊。Rollup 提供了幾種機制讓這成為可能。

自訂解析器選項

假設您有一個外掛程式,它應該根據另一個外掛程式產生匯入,來解析成不同的 ID。達成此目的的方法之一,就是重新撰寫匯入,以使用特殊代理 ID,例如透過 CommonJS 檔案中的 require("foo") 轉譯的匯入,可能會變成具有特殊 ID import "foo?require=true" 的一般匯入,讓解析器外掛程式知道這一點。

然而,這裡的問題是,這個代理 ID 在傳遞給其他解析器時,可能會或可能不會造成意外的副作用,因為它並未真正對應到一個檔案。此外,如果 ID 是由外掛程式 A 建立,而解析發生在外掛程式 B,它會在這些外掛程式之間建立一個相依性,讓 A 無法在沒有 B 的情況下使用。

自訂解析器選項在此提供一個解決方案,允許在透過 this resolve 手動解析模組時,傳遞外掛程式的其他選項。這會在不變更 ID 的情況下發生,因此不會損害其他外掛程式在目標外掛程式不存在時正確解析模組的能力。

js
function requestingPlugin() {
	return {
		name: 'requesting',
		async buildStart() {
			const resolution = await this.resolve('foo', undefined, {
				custom: { resolving: { specialResolution: true } }
			});
			console.log(resolution.id); // "special"
		}
	};
}

function resolvingPlugin() {
	return {
		name: 'resolving',
		resolveId(id, importer, { custom }) {
			if (custom.resolving?.specialResolution) {
				return 'special';
			}
			return null;
		}
	};
}

請注意,自訂選項應使用對應於解析外掛程式外掛程式名稱的屬性來新增的慣例。由解析外掛程式負責指定它尊重哪些選項。

自訂模組元資料

外掛程式可以透過 resolveIdloadtransform 鉤子設定自訂的模組中繼資料,並透過 this.getModuleInfothis.loadmoduleParsed 鉤子存取。此中繼資料應始終可以透過 JSON.stringify 處理,並會持續儲存在快取中,例如在監控模式中。

js
function annotatingPlugin() {
	return {
		name: 'annotating',
		transform(code, id) {
			if (thisModuleIsSpecial(code, id)) {
				return { meta: { annotating: { special: true } } };
			}
		}
	};
}

function readingPlugin() {
	let parentApi;
	return {
		name: 'reading',
		buildEnd() {
			const specialModules = Array.from(this.getModuleIds()).filter(
				id => this.getModuleInfo(id).meta.annotating?.special
			);
			// do something with this list
		}
	};
}

請注意,新增或修改資料的外掛程式應使用與外掛程式名稱對應的屬性,在本例中為 annotating。另一方面,任何外掛程式都可以透過 this.getModuleInfo 讀取其他外掛程式的全部中繼資料。

如果多個外掛程式新增中繼資料,或在不同的鉤子中新增中繼資料,則這些 meta 物件將會淺層合併。這表示如果外掛程式 first 在 resolveId 鉤子中新增 {meta: {first: {resolved: "first"}}},而在 load 鉤子中新增 {meta: {first: {loaded: "first"}}},而外掛程式 secondtransform 鉤子中新增 {meta: {second: {transformed: "second"}}},則產生的 meta 物件將會是 {first: {loaded: "first"}, second: {transformed: "second"}}。在此,resolveId 鉤子的結果將會被 load 鉤子的結果覆寫,因為該外掛程式將它們都儲存在其 first 頂層屬性下。另一方面,另一個外掛程式的 transform 資料將會放在其旁邊。

模組的 meta 物件會在 Rollup 開始載入模組時建立,並會針對模組的每個生命週期掛勾進行更新。如果您儲存此物件的參考,您也可以手動更新它。若要存取尚未載入的模組的 meta 物件,您可以透過 this.load 觸發它的建立並載入模組

js
function plugin() {
	return {
		name: 'test',
		buildStart() {
			// trigger loading a module. We could also pass an initial
			// "meta" object here, but it would be ignored if the module
			// was already loaded via other means
			this.load({ id: 'my-id' });
			// the module info is now available, we do not need to await
			// this.load
			const meta = this.getModuleInfo('my-id').meta;
			// we can also modify meta manually now
			meta.test = { some: 'data' };
		}
	};
}

直接外掛通訊

對於任何其他類型的外掛間通訊,我們建議採用以下模式。請注意,api 絕不會與任何即將推出的外掛掛勾衝突。

js
function parentPlugin() {
	return {
		name: 'parent',
		api: {
			//...methods and properties exposed for other plugins
			doSomething(...args) {
				// do something interesting
			}
		}
		// ...plugin hooks
	};
}

function dependentPlugin() {
	let parentApi;
	return {
		name: 'dependent',
		buildStart({ plugins }) {
			const parentName = 'parent';
			const parentPlugin = plugins.find(
				plugin => plugin.name === parentName
			);
			if (!parentPlugin) {
				// or handle this silently if it is optional
				throw new Error(
					`This plugin depends on the "${parentName}" plugin.`
				);
			}
			// now you can access the API methods in subsequent hooks
			parentApi = parentPlugin.api;
		},
		transform(code, id) {
			if (thereIsAReasonToDoSomething(id)) {
				parentApi.doSomething(id);
			}
		}
	};
}

根據 MIT 授權釋出。