---
title: "编写 Rust 插件"
locale: "zh"
version: "1.x"
canonicalUrl: "https://farmfe.org/zh/docs/1.x/plugins/writing-plugins/rust-plugin"
publicationStatus: "prerelease"
---

<a id="编写-rust-插件"></a>
# 编写 Rust 插件


用 Rust 写你的插件是一个推荐的方式，因为 Rust 插件比 JavaScript 插件**更快和富有表现力**。一个 Rust 插件应该是实现了 ` farmfe_core::plugin::Plugin ` trait 的 ` struct `, 例如


```rust
#![deny(clippy::all)]

use farmfe_core::{config::Config, plugin::Plugin};

use farmfe_macro_plugin::farm_plugin;

// define your rust plugins
#[farm_plugin]
pub struct FarmPluginExample {}

impl FarmPluginExample {
  // 一个 Rust 插件必须导出一个名称是 new 的函数 并且初始化的时候接受两个参数
  fn new(config: &Config, options: String) -> Self {
    Self {}
  }
}
// 实现插件的 trait 来定义插件 hooks
impl Plugin for FarmPluginExample {
  fn name(&self) -> &str {
    "FarmPluginExample"
  }

  // more hooks here
}
```


Rust 插件注意事项：


- ` struct ` 必须是 ` pub ` 并且需要有 ` #[farm_plugin] ` 属性
- ` struct ` 必须实现 ` Plugin ` trait, 并且 ` name ` 方法必须要实现
- ` struct ` 必须导出一个 ` new ` 的方法，在初始化的时候接受两个参数 第一个参数是 ` &Config `, 第二个参数是 ` String `。` new ` 方法在插件加载的时候调用。 ` Config ` 是 farm 项目的配置 ` String ` 是插件的选项


我们同时提供了 Rust 插件示例代码仓库：[farm-rust-plugin-example](<https://github.com/farm-fe/rust-plugin-example>)



> [!NOTE]
> 备注
> 
> 本文章仅仅涵盖如何创建，开发和发布一个 Rust 插件，更多的细节参考 [插件 Hooks](</zh/docs/api/rust-plugin-api>)



<a id="约定"></a>
## 约定


对于特定的 Farm 插件


- 一个 Farm 的 Rust 插件应该有一个 ` farm-plugin- ` 前缀的名称并且语义清晰
- package.json 里面有 ` farm-plugin- ` 关键字


如果你的插件仅仅适配特定框架，其名称应遵循以下前缀格式:


- ` farm-plugin-vue- `: 作为 Vue 插件前缀
- ` farm-plugin-react- `: 作为 React 插件前缀
- ` farm-plugin-svelte- `: 作为 Svelte 插件前缀
- ...


<a id="概念"></a>
## 概念


在开始编写 Rust 插件之前，你应该了解以下概念:


- **module\_type**：模块的类型，他可能是 ` js `, ` ts `, ` css `, ` sass `, ` json ` 等等。Farm 原生支持 ` js/ts/jsx/tsx `, ` css `, ` html `, ` json `, ` static asserts(png, svg等等) `。` module_type ` 会被 ` load ` 或者 ` transform ` 钩子返回
- **resolved\_path 和 module\_id**：` resolved_path ` 是一个模块的绝对路径，` module_id `是一个模块的唯一 id，通常是` 模块对于项目根目录的相对路径 ` + ` query `。例如 我们引用了一个模块 ` import './a?query' ` resolved\_path 是 ` /project/src/a.ts ` module\_id 是 ` src/a.ts?query `
- **context**: 所有的插件都会接受一个 ` context ` 参数，它有 Farm 项目的整个编译上下文，你可以从里面拿到 ModuleGraph，Module，Resources等等
- **Resource and Resource Pot**：` Resource ` 是输出出来的最终打包产物，`  Resource Pot `是资源的抽象表示，类似于其他打包器的 ` Chunk `。在 Farm 项目中，我们首先从 ` ModuleGraph ` 生成 ` Resource Pots `, 渲染 ` Resource Pots `，最终从 ` Resource Pots ` 生成 \`Resource


<a id="模块类型"></a>
### 模块类型


在 Farm 中，一切都被认为是“一等公民”，因此 Farm 设计 ` module_type ` 来标识模块类型，并在用不同的插件处理不同的模块类型
` Module_type  `由 ` load ` 钩子返回，并且可以由 ` transform ` 钩子转换。Farm 原生支持 ` js/ts/jsx/tsx `、` css `、` html `、` json `、` static assets(png、svg等) `。对于这些模块类型，你可以直接在 ` load ` 或 ` transform ` hook 中返回。但是如果你想处理自定义模块类型，你需要实现其他钩子例如 ` parse `， ` render_resource_pot_modules `， ` generate resources ` 等来控制如何对自定义模块进行类型解析，渲染和生成资源。


<a id="创建插件"></a>
## 创建插件


Farm 提供了官方模板来帮助你快速创建 Rust 插件:


**pnpm**

```javascript
pnpm create farm-plugin
```


**npm**

```javascript
npm create farm-plugin@latest
```


**yarn**

```javascript
yarn create farm-plugin
```



然后按照提示创建插件


或者直接运行以下命令创建插件:


**pnpm**

```javascript
pnpm create farm-plugin my-farm-plugin --type rust
```


**npm**

```javascript
npm create my-farm-plugin --type rust
```


**yarn**

```javascript
yarn create my-farm-plugin --type rust
```



上面的命令会在当前目录中创建一个名为 ` my-farm-plugin ` 的js插件。` ——type ` 可以是 ` rust ` 或者 ` js `


<a id="插件项目结构"></a>
## 插件项目结构


一个插件项目结构如下：


```plaintext
my-farm-plugin
├── .github
│   └── workflows
|       ├── release.yml
|       ├── build.yml
│       └── ci.yml
├── Cargo.toml
|── .gitignore
├── npm
│   ├── darwin-x64
│   ├── linux-x64-gnu
|   ├── win32-x64-msvc
│   └── ...
├── package.json
├── src
│   └── lib.rs
└── rust-toolchain.toml
```


值得注意的文件和目录:


- ` src/lib.rs `: 插件的主要文件，你在这里定义你的插件
- ` Cargo.toml `: Rust 项目的清单
- ` package.json `: npm 项目清单
- ` npm `: 平台特定的二进制包所在的位置。在发布插件之前，这些包应该发布到 npm registry
- ` .github/workflows `: 用于在 github actions 中交叉构建和发布插件
- ` rust-toolchain.toml `: rust 工具链文件，它不应该 **被手动修改**，它应该始终使用 **与 farm core相同的版本**。


Farm 提供了一个工具 (` @farmfe/plugin-tools `) 来帮助你构建和发布插件，参考 ` package.json `:


```json
{
  // ...
  "scripts": {
    // build your plugin for current platform
    "build": "farm-plugin-tools build --platform -p my_farm_plugin --release",
    // publish all platform packages under npm directory to npm registry
    "prepublishOnly": "farm-plugin-tools prepublish"
  },
  // ...
}
```


更多关于构建和发布的细节参考 [构建](</zh/docs/1.x/plugins/writing-plugins/rust-plugin#cross-build>) 和 [发布](</zh/docs/1.x/plugins/writing-plugins/rust-plugin#publish>)章节：


<a id="开发插件"></a>
## 开发插件


为了在本地开发和测试你的插件 你首先依据你平台构建插件，运行：


```bash
pnpm build
```


然后你可以使用你构建好的插件，在 ` frame.config.ts ` 的 ` plugins `添加你的插件：


```javascript
import { defineConfig } from '@farmfe/core';

export default defineConfig({
  plugins: [
    'my-farm-plugin'
  ]
});
```


在你的 farm 项目中运行 ` pnpm i ` 并且运行 ` farm start ` 来运行你的带有你插件的 farm 项目
当对插件进行更改时，应该重新构建插件并重启 farm 项目以查看更改。例如，在你的插件中添加 ` load ` 钩子:


```rust
// ... ignore other code

impl Plugin for FarmPluginExample {
  fn name(&self) -> &str {
    "FarmPluginExample"
  }

  fn load(
    &self,
    param: &farmfe_core::plugin::PluginLoadHookParam,
    _context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
    _hook_context: &farmfe_core::plugin::PluginHookContext,
  ) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginLoadHookResult>> {
    println!(
      "load path: {:?}, id: {:?}",
      param.resolved_path, param.module_id
    );
    Ok(None)
  }
}
```


然后用 ` pnpm build ` 重新构建你的插件，用 ` farm start ` 重新启动你的 farm 项目，你会看到 ` load ` 钩子在编译你的 farm 项目时被调用。



> [!NOTE]
> 备注
> 
> 想了解更多关于插件 hooks , 参阅 [插件钩子](</zh/docs/api/rust-plugin-api>).



<a id="处理-moduletype"></a>
### 处理 ModuleType


` module_type ` 由 ` load ` hook 或 ` transform ` hook 返回。你在 ` load ` hook 中可以给 module 设置任意的 module type，该模块将由支持该模块类型的相应插件处理。
对于原生支持的模块类型，你可以在 ` load ` 钩子中返回模块类型:


```rust
// ... ignore other code

impl Plugin for FarmPluginExample {
  fn name(&self) -> &str {
    "FarmPluginExample"
  }

  fn load(
    &self,
    param: &farmfe_core::plugin::PluginLoadHookParam,
    _context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
    _hook_context: &farmfe_core::plugin::PluginHookContext,
  ) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginLoadHookResult>> {
    // handle virtual module
    if param.module_id.starts_with("virtual:my-css:css") {
      // return module type and content
      Ok(Some(farmfe_core::plugin::PluginLoadHookResult {
        module_type: "css".to_string(),
        content: ".red { color: red; }".to_string(),
        ..Default::default()
      }))
    } else {
      Ok(None)
    }
  }
}
```


对于原生支持的模块类型，你应该使用 ` transform ` hook 将模块类型转换为原生支持的模块类型，否则你需要实现 ` parse `、` renderResourcePot ` hook 来处理你的自定义模块类型:


```rust
// ... ignore other code

impl Plugin for FarmPluginExample {
  fn name(&self) -> &str {
    "FarmPluginExample"
  }

  fn transform(
    &self,
    param: &farmfe_core::plugin::PluginTransformHookParam,
    _context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
    _hook_context: &farmfe_core::plugin::PluginHookContext,
  ) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginTransformHookResult>> {
    // module type guard is required
    if matches!(param.module_type, ModuleType::Custom("sass")) {
      // compile sass and transform the module type from sass to css
      Ok(Some(farmfe_core::plugin::PluginTransformHookResult {
        module_type: "css".to_string(),
        content: compileSass(param.content),
        ..Default::default()
      }))
    } else {
      Ok(None)
    }
  }
}
```



> [!NOTE]
> 备注
> 
> 模块类型保护，如 ` matches!(module_type, ModuleType::Custom("sass")) ` ` transform ` 钩子是必需的，因为所有模块类型都会调用 ` transform ` 钩子，并且你应该只在 ` transform ` hook 中处理你的自定义模块类型。` parse `和其他 hook 也是如此。



或者实现 ` parse `，` render_resource_pot_modules ` 钩子来处理你的自定义模块类型 参考 farm 如何原生处理 css 插件如何处理 ` css ` 模块类型的 [farm-plugin-css](<https://github.com/farm-fe/farm/blob/main/crates/plugin_html/src/lib.rs#L159>)


<a id="处理插件选项"></a>
### 处理插件选项


rust 插件选项可以在` farm.config.ts `中配置:


```javascript
import { defineConfig } from '@farmfe/core';

export default defineConfig({
  plugins: [
    ['my-farm-plugin', {
      // plugin options
      myOption: 'myOption'
    }]
  ]
});
```


该选项将被 json 序列化并传递给插件的` new `方法，你可以在` new `方法中处理该选项:


```rust
// ... ignore other code

// define your rust plugin options
#[derive(serde::Deserialize)]
pub struct Options {
  pub my_option: Option<String>,
}

impl FarmPluginExample {
  fn new(config: &Config, options: String) -> Self {
    // deserialize the options
    let my_option: Options = serde_json::from_str(&options).unwrap();
    // handle the options...
    Self {}
  }
}
```


请注意，你应该将依赖 ` serde ` 和 ` serde_json ` 添加到你的` Cargo.toml `中。来支持反序列化:


```toml
[dependencies]
# ... ignore other code
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```



> [!NOTE]
> 备注
> 
> 不能被 json 序列化选项不被支持。这意味着你只能使用字符串、数字、布尔值、数组、对象等类型。不支持` 函数选项 `。



<a id="在插件里面使用-farm_core"></a>
### 在插件里面使用 farm\_core


Farm 在 [` farmfe_core `](<https://docs.rs/farmfe_core>) crate 中暴露所有核心结构和工具函数。更多细节请参阅[` farmfe_core `](<https://docs.rs/farmfe_core>)文档。



> [!NOTE]
> 备注
> 
> 如果你想在插件中使用 swc 中的 ` Module `、` Program ` 等结构，你应该使用由 farm\_core 重新暴露的 ` farmfe_core::swc_ast `。因为 farm\_core 使用的 swc 版本可能与你在插件中使用的 swc 版本不同，并且 farm\_core 使用的 swc 版本保证与 farm\_core 兼容。



<a id="警告"></a>
### 警告


<a id="在插件中使用-swc"></a>
#### 在插件中使用 SWC


请注意，你的 rust 插件不应该使用任何与 SWC 相关的包，如` swc_common `、` swc_transforms `等。SWC 在进程中存储全局状态，当你在插件中使用 SWC 时，它可能会导致**死锁**。


如果你想要修改你的 Farm 项目的AST，建议写\[SWC Plugin\](/zh/docs/using-plugins\#using-swc plugins)。关于如何编写SWC插件，请参阅[编写SWC插件](<https://swc.rs/docs/plugin/ecmascript/getting-started>)。


<a id="选择-rust-工具链"></a>
#### 选择 Rust 工具链


因为 Farm 的 Rust 插件是一个动态链接库，你应该始终使用和 farm core 相同版本的 Rust 工具链。rust 工具链定义在`  rust-toolchain.toml `中。它不应该**被手动修改**。
并且应该始终使用 Rust 构建插件，因为Farm Core 不支持 FFI，也不承诺 ABI 稳定性以提供最佳性能。


<a id="插件的兼容性"></a>
### 插件的兼容性


Farm core 维护了一个向插件暴漏出来一个 API 版本。如果你遇到类似` Incompatible Rust Plugin: Current core's version… `，这意味着你的插件与当前 farm core版本不兼容。你应该更新你的插件到最新版本来解决这个问题。


对于插件作者来说，你应该重新用最新的 farm core 版本构建和发布插件，来让你的插件与最新的 farm core版本兼容。



> [!NOTE]
> 备注
> 
> Farm 承诺与相同主版本的 AP I兼容，例如，如果你的插件兼容 Farm core 1.0.0，那么它也应该兼容 Farm core 1.1.0、1.2.0等，这意味着你的插件将始终适用于相同的 Farm 主版本。



<a id="cross-build"></a>
## 交叉构建


一个 Farm Rust 插件是一个**特定于平台的动态链接库**，你应该为你想要支持的所有平台构建插件。
Farm 提供了一个使用 github actions 构建插件的示例，请参阅[.github/workflows/build.yml](<https://github.com/farm-fe/rust-plugin-example/blob/main/.github/workflows/build.yaml>)


默认情况下，farm rust 插件应该针对以下平台构建:


- ` linux-x64-gnu  `
- ` linux-x64-musl `
- ` darwin-x64 `
- ` win32-x64-msvc `
- ` linux-arm64-musl `
- ` linux-arm64-gnu `
- ` darwin-arm64 `
- ` win32-ia32-msvc `
- ` win32-arm64-msvc `


对于公开发布到 npm registry 的插件，建议发布支持上面所有平台的插件。对于私有 rust 插件，你可以为任何你想支持的平台构建插件。



> [!TIP]
> 提示
> 
> 因为 rust 插件是一个纯动态链接库，如果您有关于如何为特定平台构建插件的问题，只需谷歌如何在 rust 中为该平台构建动态链接库。



<a id="publish"></a>
## 发布


发布 Rust插件的有以下的步骤:


1. 交叉构建动态链接库的 Rust 插件，详情请参阅[交叉构建](</zh/docs/1.x/plugins/writing-plugins/rust-plugin#cross-build>)
2. 将二进制文件复制到 npm 目录下，例如:复制到 ` npm/linux-x64-gnu/index.farm `
3. 在 npm 目录下发布平台特定的包，你可以使用` farm-plugin-tool prepublish `在 npm 目录下发布包
4. 发布包本身


参见示例[github actions publish workflow](<https://github.com/farm-fe/rust-plugin-example/blob/main/.github/workflows/release.yml>)


<a id="例子"></a>
## 例子


我们将使用 ` @farmfe/plugin-sass ` 作为一个真正的 Rust 插件示例。这个插件将支持在你的项目中编译` .scss ` 和 ` .sass ` 文件


<a id="定义一个插件"></a>
### 定义一个插件


导出一个名为 ` FarmPluginSass ` 的 Rust struct


```rust
use farmfe_macro_plugin::farm_plugin;

// 1. define a struct with #[farm_plugin] attribute
#[farm_plugin]
pub struct FarmPluginSass {
  sass_options: String,
  regex: Regex,
}

impl FarmPluginSass {
  // 2. define a new method with 2 arguments
  pub fn new(_config: &Config, options: String) -> Self {
    Self {
      sass_options: options,
      regex: Regex::new(r#"\.(sass|scss)$"#).unwrap(),
    }
  }
}
```


- struct 必须是` pub `并且必须有 ` #[farm_plugin] ` 属性。
- 结构体必须导出一个` new `方法，该方法接受两个参数作为初始化参数，第一个参数是` &Config `，第二个参数是` String `。


<a id="实现插件-trait"></a>
### 实现插件 Trait


` Plugin ` trait 用于定义可以挂接到 farm compiler 的 ` hooks `


```rust
use farmfe_core::plugin::Plugin;
use farmfe_macro_plugin::farm_plugin;

// 1. define a struct with #[farm_plugin] attribute
#[farm_plugin]
pub struct FarmPluginSass {
  sass_options: String,
  regex: Regex,
}

impl FarmPluginSass {
  // 2. define a new method with 2 arguments
  pub fn new(_config: &Config, options: String) -> Self {
    Self {
      sass_options: options,
      regex: Regex::new(r#"\.(sass|scss)$"#).unwrap(),
    }
  }
}
// Implement Plugin Trait
impl Plugin for FarmPluginSass {
  fn name(&self) -> &str {
    "FarmPluginSass"
  }

  // this plugin should be executed before internal plugins
  fn priority(&self) -> i32 {
    101
  }
}
```


<a id="加载-scss-文件"></a>
### 加载 ` .scss ` 文件


实现 ` load ` 钩子以支持加载`  .scss ` 文件


```rust
// ignore other code ...

// Implement Plugin Trait
impl Plugin for FarmPluginSass {
  fn name(&self) -> &str {
    "FarmPluginSass"
  }

  // this plugin should be executed before internal plugins
  fn priority(&self) -> i32 {
    101
  }

  fn load(
    &self,
    param: &farmfe_core::plugin::PluginLoadHookParam,
    _context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
    _hook_context: &farmfe_core::plugin::PluginHookContext,
  ) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginLoadHookResult>> {
    if param.query.is_empty() && self.regex.is_match(param.resolved_path) {
      let content = fs::read_file_utf8(param.resolved_path);

      if let Ok(content) = content {
        return Ok(Some(farmfe_core::plugin::PluginLoadHookResult {
          content,
          module_type: ModuleType::Custom(String::from("sass")),
        }));
      }
    }

    Ok(None)
  }
}
```


在 ` load ` 钩子中，我们只读取以` .scss ` 或者 ` .sass `结尾的文件,返回文件内容并将其 module\_type 设置为` ModuleType::Custom(String::from("sass")) `。


<a id="转化-sass-文件"></a>
### 转化 ` sass ` 文件


加载 ` .scss ` 文件之后，我们需要在 ` transform ` hook 中将其转换为 ` css `，然后 Farm 将在接下来的过程中将其视为 css


```rust
// ignore other code ...
fn transform(
  &self,
  param: &farmfe_core::plugin::PluginTransformHookParam,
  context: &std::sync::Arc<farmfe_core::context::CompilationContext>,
) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginTransformHookResult>> {
  // module type guard is neccessary
  if param.module_type == ModuleType::Custom(String::from("sass")) {
    // ... ignore other code

    // parse options
    const options = parse_options(&self.options, param.module_id);
    // compile sass to css
    let compile_result = compileSass(&param.content, options);

    return Ok(Some(farmfe_core::plugin::PluginTransformHookResult {
      content: compile_result.css,
      source_map: compile_result.source_map,
      // tell farm compiler that we have transformed this module to css
      module_type: Some(farmfe_core::module::ModuleType::Css),
      ignore_previous_source_map: false,
    }));
  }

  Ok(None)
}
```



> [!TIP]
> 提示
> 
> 这个例子只介绍了如何实现转换器。有关 Farm 支持的更多能力，请参阅[插件钩子](</zh/docs/api/rust-plugin-api>)。
