创作插件

创作插件时需要注意的事项

插件 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 上报告您遇到的任何问题。

加载配置

#

许多插件需要从用户的项目中加载某种配置。在某些情况下,插件包装的编译器或工具已经内置了配置加载机制。在其他情况下,您需要为插件创建一个配置文件格式。

note:使用 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) {
// 每当加载的文件之一发生变化时失效。
for (let file of files) {
config.invalidateOnFileChange(file);
}
} else {
// 创建新配置时失效。
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 文档。

Naming

#

All plugins must follow a naming system:

Official package Community packages Private company/scoped team packages
Configs @parcel/config-{name} parcel-config-{name} @scope/parcel-config[-{name}]
Resolvers @parcel/resolver-{name} parcel-resolver-{name} @scope/parcel-resolver[-{name}]
Transformers @parcel/transformer-{name} parcel-transformer-{name} @scope/parcel-transformer[-{name}]
Bundlers @parcel/bundler-{name} parcel-bundler-{name} @scope/parcel-bundler[-{name}]
Namers @parcel/namer-{name} parcel-namer-{name} @scope/parcel-namer[-{name}]
Runtimes @parcel/runtime-{name} parcel-runtime-{name} @scope/parcel-runtime[-{name}]
Packagers @parcel/packager-{name} parcel-packager-{name} @scope/parcel-packager[-{name}]
Optimizers @parcel/optimizer-{name} parcel-optimizer-{name} @scope/parcel-optimizer[-{name}]
Reporters @parcel/reporter-{name} parcel-reporter-{name} @scope/parcel-reporter[-{name}]
Validators @parcel/validator-{name} parcel-validator-{name} @scope/parcel-validator[-{name}]

The {name} must be descriptive and directly related to the purpose of the package. Someone should be able to have an idea of what the package does simply by reading the name in a .parcelrc or package.json#devDependencies.

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

If your plugin adds support for a specific tool, please use the name of the tool.

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

If your plugin is a reimplementation of something that exists, try naming it something that explains why it is a separate:

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

We ask that community members work together and when forks happen to try and resolve them. If someone made a better version of your plugin, please consider giving the better package name over, have them make a major version bump, and redirect people to the new tool.

See Local plugins for recommendations on using plugins in your project without publishing them.

Versioning

#

You must follow semantic versioning (to the best of your ability). No, it's not the perfect system, but it's the best one we have and people do depend on it.

If plugin authors intentionally don't follow semantic versioning, Parcel may start warning users that they should be locking down the version number for your plugin.

Engines

#

您必须在 package.json#engines.parcel 字段中指定插件支持的 Parcel 版本范围:

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

如果您不指定此字段,Parcel 将输出警告:

警告:插件 "parcel-transformer-typescript" 需要指定一个
`package.json#engines.parcel` 字段,其中包含支持的 Parcel 版本范围。

如果您确实指定了 Parcel 引擎字段,并且用户使用不兼容的版本,Parcel 将阻止加载插件并显示错误。

发布

#

文档

#

您的插件应该有一个 README.md 文件,其中包含:

npm 发布

#

使用 npm publish 发布您的插件。对于社区插件,请确保遵循上面列出的命名约定。

持续集成

#

建议为您的插件设置持续集成(CI)。这有助于确保代码质量并自动运行测试。

测试

#

单元测试

#

为您的插件编写单元测试非常重要。这有助于确保插件按预期工作,并在未来的更改中保持稳定性。

集成测试

#

除了单元测试,还应该编写集成测试,以确保插件在实际 Parcel 构建环境中正常工作。

性能考虑

#

缓存

#

设计插件时要考虑缓存。避免在每次构建时重复执行相同的工作。利用 Parcel 的缓存机制来提高构建性能。

异步操作

#

尽可能使用异步操作,并避免阻塞主线程。这有助于提高构建性能,特别是在大型项目中。

常见陷阱

#

避免全局状态

#

不要在插件中使用全局状态。这可能导致不可预测的行为,特别是在并行构建环境中。

配置加载

#

始终使用 Parcel 提供的配置加载机制,而不是直接读取文件系统。这确保了正确的缓存和依赖跟踪。

调试

#

日志记录

#

使用 Parcel 的日志记录机制来输出调试信息。避免使用 console.log(),因为这可能会干扰用户的构建输出。

错误处理

#

提供清晰、有意义的错误消息。帮助用户理解并解决可能出现的问题。

贡献指南

#

代码质量

#

社区协作

#

结论

#

创作 Parcel 插件是一个令人兴奋的过程。通过遵循这些指南,您可以创建高质量、可靠且对社区有用的插件。记住,好的插件不仅仅是技术上的正确,还要考虑用户体验和性能。