Rust Plugin Api
本文档仅涵盖插件钩子详情。 有关如何创建、构建和发布 rust 插件,请参阅:编写 Rust 插件
配置 Rust 插件
通过 plugins
选项添加 Rust 插件:
import { defineConfig } from "@farmfe/core";
export default defineConfig({
// 在 plugins 中配置
plugins: [
['@farmfe/plugin-sass', { /** 插件选项 */ }]
],
});
在字符串中配置 Rust 插件包名称(或路径),并在对象中配置其选项。
编写 Rust 插件
有关详细信息,请参阅编写 Rust 插件。
插件 Hook 概述
Farm 提供了很多 rollup 风格的 hook,这些 hook 分为 build 阶段和 generate 阶段:
所有插件挂钩都接受一个名为 CompilationContext
的参数。 所有共享的编译信息都存储在 context
中。
Hook 共有三种(与 Rollup 相同):
first
:钩子串行执行,当钩子返回非空值时立即返回。 (null 在 JS 中表示 null 和 undefined,在 Rust 中表示 None)。serial
: 钩子串行执行,每个钩子的结果将传递到下一个钩子,使用最后一个钩子的结果作为最终结果。parallel
:钩子在线程池中并行执行,并且应该被隔离。
有关完整的 Plugin Hooks
定义,请参阅Plugin Trait
name
- required:
true
- default:
fn name(&self) -> &str;
返回此插件的名称。 例子:
impl Plugin for MyPlugin {
fn name(&self) -> &str {
"MyPlugin"
}
}
priority
- required:
false
- default:
fn priority(&self) -> i32 {
100
}
定义该插件的优先级,值越大,该插件越早执行。 当插件具有相同优先级时,它们将按照与plugins
中注册的顺序相同的顺序执行。
默认情况下,所有自定义插件的优先级都是 100。有些内部插件的优先级是 99,比如 plugin-script
、plugin-css
,您可以覆盖默认优先级时内部插件的行为。 但是一些内部插件的优先级是101,比如plugin-resolve
,plugin-html
,如 果你想覆盖默认行为,你应该设置一个更大的优先级。
config
- required:
false
- hook type:
serial
- default:
fn config(&self, _config: &mut Config) -> Result<Option<()>> {
Ok(None)
}
在编译开始之前在config
钩子中修改配置。 Config 结构体的定义请参考Config。 例子:
impl Plugin for MyPlugin {
// 实现 config hook
fn config(&self, config: &mut Config) -> Result<Option<()>> {
// 设置 minify 为 false
config.input.insert("custom-entry", "./custom.html");
Ok(Some(()))
}
}
请注意, Rust Plugin
的 config
钩子是在 JS Plugin
的 config
和 configResolved
钩子之后调用的。
plugin_cache_loaded
- required:
false
- hook type:
serial
- default:
fn plugin_cache_loaded(
&self,
_cache: &Vec<u8>,
_context: &Arc<CompilationContext>,
) -> Result<Option<()>> {
Ok(None)
}
扩展插件的持久缓存加载。
当启用 持久缓存
时,在命中缓存时可能会跳过 加载
和 转换
挂钩。 如果您的插件依赖于以前的编译结果(例如,基于现有模块加载虚拟模块),您可能需要实现此钩子来加载插件的缓存信息,以确保缓存按预期工作。
例子:
#[cache_item]
struct CachedStaticAssets {
list: Vec<Resource>,
}
impl Plugin for StaticAssetsPlugin {
fn plugin_cache_loaded(
&self,
cache: &Vec<u8>,
context: &Arc<CompilationContext>,
) -> farmfe_core::error::Result<Option<()>> {
let cached_static_assets: CachedAssets = deserialize!(cache, CachedStaticAssets);
for asset in cached_static_assets.list {
if let ResourceOrigin::Module(m) = asset.origin {
context.emit_file(EmitFileParams {
resolved_path: m.to_string(),
name: asset.name,
content: asset.bytes,
resource_type: asset.resource_type,
});
}
}
Ok(Some(()))
}
}
注意:
deserialize
由farmfe_core
导出,它可以帮助您反序列化Vec<u8>
中的结构体或枚举。- 缓存的结构体或枚举必须是rkyv可序列化的,您可以使用
farmfe_core
公开的#[cache_item]
快速创建可缓存的结构体。
build_start
- required:
false
- hook type:
parallel
- default:
fn build_start(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
Ok(None)
}
在第一次编译开始之前调用。 您可以使用此挂钩来初始化插件的任何初始状态。
build_start
仅在第一次编译时调用一次。 如果你想在HMR
或Lazy Compilation
中更新ModuleGraph时做一些事情,你应该使用update_modules钩子。
resolve
- required:
false
- hook type:
first
- default:
fn resolve(
&self,
_param: &PluginResolveHookParam,
_context: &Arc<CompilationContext>,
_hook_context: &PluginHookContext,
) -> Result<Option<PluginResolveHookResult>> {
Ok(None)
}
/// 解析钩子的参数
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PluginResolveHookParam {
/// 我们想要解析的源,例如'./index'
pub source: String,
/// 解析 `specifier` 的起始位置,如果解析入口或解析 hmr 更新,则为 [None]。
pub importer: Option<ModuleId>,
/// 例如,[ResolveKind::Import] 用于静态导入 (`import a from './a'`)
pub kind: ResolveKind,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase", default)]
pub struct PluginResolveHookResult {
/// 解析路径,通常是绝对文件路径。
pub resolved_path: String,
/// 该模块是否应该被 external,如果为 true,则该模块不会出现在最终结果中
pub external: bool,
/// 是否具有副作用,影响 tree shake
pub side_effects: bool,
/// 从说明符解析的查询,例如,如果说明符是`./a.png?inline`,查询应该是`{ inline: "" }`
/// 如果你自定义插件,你的插件应该负责解析查询
/// 如果您只想像上面的示例一样进行正常的查询解析, [farmfe_toolkit::resolve::parse_query] 应该会有所帮助
pub query: Vec<(String, String)>,
/// 插件和钩子之间传递的元数据
pub meta: HashMap<String, String>,
}
从 importer
解析自定义 source
,例如从 a.ts
解析 ./b
:
import b from './b?raw';
// ...
那么解析参数将是:
let param = PluginResolveHookParam {
source: "./b",
importer: Some(ModuleId { relative_path: "a.ts", query_string: "" }),
kind: ResolveKind::Import
}
默认解析器的解析结果为:
let resolve_result = PluginResolveHookResult {
resolved_path: "/root/b.ts", // 解析模块的绝对路径
external: false, // 该模块应该包含在最终编译的资源中,并且不应该是外部的
side_effects: false, // 无副作用
query: vec![("raw", "")], // query
meta: HashMap::new()
}