撰寫外掛程式

撰寫外掛時應注意的事項

外掛 API

#

有幾種不同類型的外掛。它們看起來都很相似,但保持分離,因此我們可以對每個外掛允許執行的動作有嚴格的合約。

有一些規則應遵循於每種類型的外掛

外掛 API 都遵循一個常見的形狀

import { NameOfPluginType } from "@parcel/plugin";

export default new NameOfPluginType({
async methodName(opts: JSONObject): Promise<JSONObject> {
return result;
},
});

外掛的每個方法都是一個非同步函式,它

如果你需要某些東西未透過 opts 傳遞,請與 Parcel 團隊討論。避免嘗試從其他來源(特別是檔案系統)自行取得資訊。

模組格式

#

Parcel 支援以 CommonJS 或 ES 模組編寫的外掛。外掛的模組格式由解析器模組的檔案副檔名或其 package.json 決定。對於 ES 模組外掛,請使用 .mjs 副檔名,對於 CommonJS,請使用 .cjs.js。如果在外掛的 package.json 中宣告了 "type": "module",則 .js 副檔名會被視為 ESM,而不是 CommonJS。此行為與 Node.js 載入模組的方式相符。

ES 模組外掛目前為實驗性質。請將您遇到的任何問題回報 Github

載入設定

#

許多外掛需要從使用者的專案載入某種設定。在某些情況下,外掛包裝的編譯器或工具會內建設定載入機制。在其他情況下,您需要為您的外掛建立設定檔格式。

注意:使用 Parcel 的設定載入機制非常重要,而不是直接從檔案系統讀取。所有外掛的結果都會暫存在 Parcel 中,如果您不使用 Parcel 的設定載入系統,它將不會知道您自己讀取的檔案,也無法正確地使快取失效。

設定載入是在 loadConfig 方法中完成,大多數外掛類型都支援此方法。它會收到一個 Config 物件,其中包含用於載入設定檔的工具方法,以及用於告知 Parcel 設定檔依賴的檔案和依賴項的方法,這些檔案和依賴項應使結果失效。從 loadConfig 函式傳回的結果會傳遞到其他外掛函式中。

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async loadConfig({config}) {
let {contents, filePath} = await config.getConfig([
'tool.config.json'
]);

return contents;
},
async transform({asset, config}) {
// ...
return [asset];
}
});

上述範例使用 Config 物件的 getConfig 方法來載入設定檔。這會從資產的檔案路徑向上搜尋目錄樹,以尋找與給定檔名相符的設定檔。它可以使用 JSON5(預設)、JavaScript 或 TOML 載入檔案,而檔案路徑會自動新增為設定的無效化項。

新增無效化項

#

如果您使用的是編譯器或工具中內建的設定載入機制,您需要透過呼叫 invalidateOnFileChange 告訴 Parcel 過程中載入的任何檔案。這樣,Parcel 就能在設定變更時,使使用此設定編譯的檔案失效。各種工具通常會在載入設定時,同時回傳載入檔案的清單。

如果未載入設定,或更接近資產的新設定檔會變更結果,您應使用 invalidateOnFileCreate 方法來監控設定檔的建立。這樣,當 Parcel 偵測到新的設定檔時,外掛程式將會重新執行,且新的設定將會載入。

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async loadConfig({config}) {
let {result, files} = await loadToolConfigSomehow(config.searchPath);

if (result) {
// Invalidate whenever one of the loaded files changes.
for (let file of files) {
config.invalidateOnFileChange(file);
}
} else {
// Invalidate when a new config is created.
config.invalidateOnFileCreate({
fileName: 'tool.config.json',
aboveFilePath: config.searchPath
});
}

return result;
}
});

開發相依性

#

Parcel 會自動追蹤 Parcel 外掛程式本身及其所有相依性的原始檔,以進行變更。如果外掛程式的程式碼變更,則必須使快取失效,且必須重建外掛程式產生的任何資產。

有些外掛程式可能會動態載入其他相依性。例如,轉換器可能有其自己的外掛程式,且這些外掛程式會在使用者的專案中設定(例如 Babel)。這些無法自動追蹤,且必須新增為 Config 物件的開發相依性。這是透過使用 addDevDependency 方法來完成。

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async loadConfig({config}) {
let {result, filePath} = await loadToolConfigSomehow(config.searchPath);

for (let plugin of result.plugins) {
config.addDevDependency({
specifier: plugin,
resolveFrom: filePath
});
}

return result;
}
});

JavaScript 設定

#

有些工具會使用以 JavaScript 編寫的設定檔,而不是像 JSON、YAML 或 TOML 等靜態設定語言。很遺憾的是,這些程式化設定檔可能會導致 Parcel 中的快取產生問題,因為它們可能會回傳非確定性的結果。

處理此問題的 Parcel 慣例是,在 Parcel 程序重新啟動時,總是使組態無效。這樣一來,JavaScript 組態不會在每次建置時都失效(這會太慢),但如果組態是不確定的,重新啟動 Parcel 可確保組態是最新的。

這可以使用 Config 物件的 invalidateOnStartup 方法來完成。

import {Transformer} from '@parcel/plugin';

export default new Transformer({
async loadConfig({config}) {
let {contents, filePath} = await config.getConfig([
'tool.config.json',
'tool.config.js'
]);

if (filePath.endsWith('.js')) {
config.invalidateOnStartup();
}

return contents;
}
});

請參閱 Config 物件 API 文件,以取得所有可用方法和屬性的更多詳細資訊。

命名

#

所有外掛程式都必須遵循命名系統

官方套件 社群套件 私人公司/範圍團隊套件
組態 @parcel/config-{name} parcel-config-{name} @scope/parcel-config[-{name}]
解析器 @parcel/resolver-{name} parcel-resolver-{name} @scope/parcel-resolver[-{name}]
轉換器 @parcel/transformer-{name} parcel-transformer-{name} @scope/parcel-transformer[-{name}]
打包器 @parcel/bundler-{name} parcel-bundler-{name} @scope/parcel-bundler[-{name}]
命名器 @parcel/namer-{name} parcel-namer-{name} @scope/parcel-namer[-{name}]
執行時期 @parcel/runtime-{name} parcel-runtime-{name} @scope/parcel-runtime[-{name}]
封裝器 @parcel/packager-{name} parcel-packager-{name} @scope/parcel-packager[-{name}]
最佳化器 @parcel/optimizer-{name} parcel-optimizer-{name} @scope/parcel-optimizer[-{name}]
報告器 @parcel/reporter-{name} parcel-reporter-{name} @scope/parcel-reporter[-{name}]
驗證器 @parcel/validator-{name} parcel-validator-{name} @scope/parcel-validator[-{name}]

{name} 必須具有描述性,並與套件的目的直接相關。只要在 .parcelrcpackage.json#devDependencies 中讀取名稱,任何人都應該可以了解套件的功能。

parcel-transformer-posthtml
parcel-packager-wasm
parcel-reporter-graph-visualizer

如果您的外掛程式新增特定工具的支援,請使用該工具的名稱。

parcel-transformer-es6 (bad)
parcel-transformer-babel (good)

如果您的外掛程式是對現有功能的重新實作,請嘗試命名為可以說明為什麼它是獨立功能的名稱。

parcel-transformer-better-typescript (bad)
parcel-transformer-typescript-server (good)

我們要求社群成員合作,並在發生分歧時嘗試解決問題。如果有人製作了您外掛程式的較佳版本,請考慮將較佳的套件名稱讓給他們,請他們進行重大版本升級,並將使用者重新導向到新的工具。

請參閱Local plugins,以取得在專案中使用外掛程式而不發佈外掛程式的建議。

版本控制

#

您必須遵循語意版本控制(盡您所能)。沒錯,這並不是完美的系統,但它是我們擁有的最佳系統,而且人們確實依賴它。

如果外掛程式作者故意不遵循語意版本控制,Parcel 可能會開始警告使用者,他們應該鎖定外掛程式的版本號碼。

引擎

#

您必須指定 package.json#engines.parcel 欄位,並包含外掛程式支援的 Parcel 版本範圍

{
"name": "parcel-transformer-imagemin",
"engines": {
"parcel": "2.x"
}
}

如果您未指定此欄位,Parcel 會輸出警告

Warning: The plugin "parcel-transformer-typescript" needs to specify a
`package.json#engines.parcel` field with the supported Parcel version range.

如果您指定了 parcel 引擎欄位,而使用者使用的是不相容的 Parcel 版本,他們會看到錯誤

Error: The plugin "parcel-transformer-typescript" is not compatible with the
current version of Parcel. Requires "2.x" but the current version is "3.1.4"

Parcel 使用 node-semver 來比對版本範圍。