插件编写
编写插件时需要注意的事项
插件 API
#插件有几种不同的类型。它们看起来很相似,但为了严格限制每个插件可以执行的操作,它们被分开。
所有类型的插件都应该遵循一些规则
- 无状态 — 避免任何类型的状态,这很可能是用户出现错误的根源。例如,相同的转换可能存在于多个独立的 worker 中,这些 worker 不允许相互通信,状态将无法按预期工作。
- 纯净 — 给定相同的输入,插件必须产生相同的输出,并且您不能有任何可观察到的副作用或隐式依赖关系。否则,Parcel 的缓存将失效,您的用户会感到沮丧。您永远不应该告诉用户删除他们的缓存。
插件 API 都遵循一个通用的形状
import { NameOfPluginType } from "@parcel/plugin";
export default new NameOfPluginType({
async methodName(opts: JSONObject): Promise<JSONObject> {
return result;
},
});
插件的每个方法都是一个异步函数,它
- 接受一个严格验证的
opts
对象。 - 返回一个严格验证的
result
对象。
如果您需要的东西没有通过 opts
传递,请与 Parcel 团队联系。避免尝试从其他来源获取信息,尤其是从文件系统获取信息。
模块格式
#Parcel 支持以 CommonJS 或 ES 模块形式编写的插件。插件的模块格式由解析器模块的文件扩展名或其 package.json
确定。对于 ES 模块插件,使用 .mjs
扩展名,对于 CommonJS,使用 .cjs
或 .js
扩展名。如果在插件的 package.json
中声明了 "type": "module"
,则 .js
扩展名将被视为 ESM 而不是 CommonJS。此行为与 Node.js 加载模块的方式一致。
加载配置
#许多插件需要从用户的项目中加载某种配置。在某些情况下,插件包装的编译器或工具将内置配置加载机制。在其他情况下,您需要为您的插件创建一个配置文件格式。
配置加载是在 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}
必须具有描述性,并且与包的用途直接相关。人们应该能够仅通过在 .parcelrc
或 package.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
来匹配版本范围。