插件编写

编写插件时需要注意的事项

插件 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)

我们要求社区成员共同努力,并在发生分叉时尝试解决它们。如果有人制作了您插件的更好版本,请考虑将更好的包名称让给他们,让他们进行主要版本升级,并将人们重定向到新工具。

有关在不发布插件的情况下在项目中使用插件的建议,请参阅 本地插件

版本控制

#

您必须遵循语义版本控制(尽您所能)。不,它不是完美的系统,但它是我们拥有的最好的系统,人们确实依赖它。

如果插件作者故意不遵循语义版本控制,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 来匹配版本范围。