PageSourceSearch

https://farm-nightly.netlify.app/assets/js/294a6a71.216efd8a.js

js farm-nightly.netlify.app collected 2026-10-03 10:45:04 UTC 65,819 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkfarm_docs=self.webpackChunkfarm_docs||[]).push([[5238],{17632:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>d,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>t,toc:()=>a});var s=r(49214),o=r(36906);const l={},i="Rust Plugin Api",t={id:"api/rust-plugin-api",title:"Rust Plugin Api",description:"This document only covers the details of the plugin hooks. For how to create, build and publish a rust plugin see: Writing Rust Plugins",source:"@site/docs/api/rust-plugin-api.md",sourceDirName:"api",slug:"/api/rust-plugin-api",permalink:"/docs/api/rust-plugin-api",draft:!1,unlisted:!1,editUrl:"https://github.com/farm-fe/farm-fe.github.io/tree/main/docs/api/rust-plugin-api.md",tags:[],version:"current",frontMatter:{},sidebar:"apiSidebar",previous:{title:"Js Plugin Api",permalink:"/docs/api/js-plugin-api"},next:{title:"Runtime Plugin API",permalink:"/docs/api/runtime-plugin-api"}},d={},a=[{value:"Configuring Rust Plugins",id:"configuring-rust-plugins",level:2},{value:"Writing Rust Plugin",id:"writing-rust-plugin",level:2},{value:"Plugin Hooks Overview",id:"plugin-hooks-overview",level:2},{value:"name",id:"name",level:2},{value:"priority",id:"priority",level:2},{value:"config",id:"config",level:2},{value:"plugin_cache_loaded",id:"plugin_cache_loaded",level:2},{value:"build_start",id:"build_start",level:2},{value:"resolve",id:"resolve",level:2},{value:"load",id:"load",level:2},{value:"transform",id:"transform",level:2},{value:"parse",id:"parse",level:2},{value:"process_module",id:"process_module",level:2},{value:"analyze_deps",id:"analyze_deps",level:2},{value:"finalize_modules",id:"finalize_modules",level:2},{value:"build_end",id:"build_end",level:2},{value:"generate_start",id:"generate_start",level:2},{value:"optimize_module_graph",id:"optimize_module_graph",level:2},{value:"analyze_module_graph",id:"analyze_module_graph",level:2},{value:"partial_bundling",id:"partial_bundling",level:2},{value:"process_resource_pots",id:"process_resource_pots",level:2},{value:"render_start",id:"render_start",level:2},{value:"render_resource_pot_modules",id:"render_resource_pot_modules",level:2},{value:"render_resource_pot",id:"render_resource_pot",level:2},{value:"augment_resource_hash",id:"augment_resource_hash",level:2},{value:"optimize_resource_pot",id:"optimize_resource_pot",level:2},{value:"generate_resources",id:"generate_resources",level:2},{value:"finalize_resources",id:"finalize_resources",level:2},{value:"generate_end",id:"generate_end",level:2},{value:"finish",id:"finish",level:2},{value:"write_plugin_cache",id:"write_plugin_cache",level:2},{value:"update_modules",id:"update_modules",level:2},{value:"module_graph_updated",id:"module_graph_updated",level:2},{value:"update_finished",id:"update_finished",level:2},{value:"handle_persistent_cached_module",id:"handle_persistent_cached_module",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"rust-plugin-api",children:"Rust Plugin Api"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["This document only covers the details of the plugin hooks. For how to create, build and publish a rust plugin see: ",(0,s.jsx)(n.a,{href:"/docs/plugins/writing-plugins/rust-plugin",children:"Writing Rust Plugins"})]})}),"\n",(0,s.jsx)(n.h2,{id:"configuring-rust-plugins",children:"Configuring Rust Plugins"}),"\n",(0,s.jsxs)(n.p,{children:["Adding Rust plugins by ",(0,s.jsx)(n.code,{children:"plugins"})," option:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",metastring:'title="farm.config.ts" {3,7}',children:"import { defineConfig } from \"@farmfe/core\";\n\nexport default defineConfig({\n  // configuring it in plugins\n  plugins: [\n    ['@farmfe/plugin-sass', { /** plugin options here */ }]\n  ],\n});\n"})}),"\n",(0,s.jsx)(n.p,{children:"Configuring the Rust plugin package name(or path) in string and its options in object."}),"\n",(0,s.jsx)(n.h2,{id:"writing-rust-plugin",children:"Writing Rust Plugin"}),"\n",(0,s.jsxs)(n.p,{children:["See ",(0,s.jsx)(n.a,{href:"/docs/plugins/writing-plugins/rust-plugin",children:"Writing Rust Plugins"})," for details."]}),"\n",(0,s.jsx)(n.h2,{id:"plugin-hooks-overview",children:"Plugin Hooks Overview"}),"\n",(0,s.jsxs)(n.p,{children:["Farm provides a lot of rollup-style hooks, these hooks are divided into build stage and generate stage:\n",(0,s.jsx)(n.img,{alt:"Farm Plugin Hooks",src:r(59694).A+"",width:"1698",height:"1411"})]}),"\n",(0,s.jsxs)(n.p,{children:["All plugin hooks accept a parameter called ",(0,s.jsx)(n.a,{href:"https://docs.rs/farmfe_core/latest/farmfe_core/context/struct.CompilationContext.html",children:(0,s.jsx)(n.code,{children:"CompilationContext"})}),". All of the shared compilation info are stored in the ",(0,s.jsx)(n.code,{children:"context"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"There are three kinds of hooks (the same as Rollup):"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"first"}),": The hooks execute in serial and return immediately when a hook returns a non-null value. (The null means null and undefined in JS, None in Rust)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"serial"}),": The hooks execute in serial, and every hook's result will pass to the next hook, using the last hook's result as the final result."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"parallel"}),": The hooks execute in parallel in a thread pool and should be isolated."]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["For full ",(0,s.jsx)(n.code,{children:"Plugin Hooks"})," signature, see ",(0,s.jsx)(n.a,{href:"https://docs.rs/farmfe_core/latest/farmfe_core/plugin/trait.Plugin.html",children:"Plugin Trait"})]})}),"\n",(0,s.jsx)(n.h2,{id:"name",children:"name"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"true"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn name(&self) -> &str;\n"})}),"\n",(0,s.jsx)(n.p,{children:"Returns the name of this plugin. Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'impl Plugin for MyPlugin {\n  fn n
1ame(&self) -> &str {\n    "MyPlugin"\n  }\n}\n'})}),"\n",(0,s.jsx)(n.h2,{id:"priority",children:"priority"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn priority(&self) -> i32 {\n  100\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Define the priority of this plugin, the larger the value, the earlier this plugin execute. When plugins has same priority, they will be executed as the same order as the registered order in ",(0,s.jsx)(n.code,{children:"plugins"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["By default, all custom plugin's priority is 100. And some internal plugins' order is 99, like ",(0,s.jsx)(n.code,{children:"plugin-script"}),", ",(0,s.jsx)(n.code,{children:"plugin-css"}),", you can override the internal plugin's behavior when default priority. But some internal plugins' priority is 101, like ",(0,s.jsx)(n.code,{children:"plugin-resolve"}),", ",(0,s.jsx)(n.code,{children:"plugin-html"}),", you should setup a larger priority if you want override the default behavior."]})}),"\n",(0,s.jsx)(n.h2,{id:"config",children:"config"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn config(&self, _config: &mut Config) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Modify the config before compilation start in ",(0,s.jsx)(n.code,{children:"config"})," hook. Refer to ",(0,s.jsx)(n.a,{href:"https://docs.rs/farmfe_core/latest/farmfe_core/config/struct.Config.html",children:"Config"})," for definition of Config struct. Example:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'impl Plugin for MyPlugin {\n  // implement config hook\n  fn config(&self, config: &mut Config) -> Result<Option<()>> {\n    // set minify to false \n    config.input.insert("custom-entry", "./custom.html");\n    Ok(Some(()))\n  }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Note that the ",(0,s.jsx)(n.code,{children:"Rust Plugin"}),"'s ",(0,s.jsx)(n.code,{children:"config"})," hook are called after ",(0,s.jsx)(n.code,{children:"JS Plugin"}),"'s ",(0,s.jsx)(n.code,{children:"config"})," and ",(0,s.jsx)(n.code,{children:"configResolved"})," hook."]}),"\n",(0,s.jsx)(n.h2,{id:"plugin_cache_loaded",children:"plugin_cache_loaded"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn plugin_cache_loaded(\n  &self,\n  _cache: &Vec<u8>,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Extend ",(0,s.jsx)(n.a,{href:"/docs/advanced/persistent-cache",children:(0,s.jsx)(n.code,{children:"persistent cache"})})," loading for your plugin."]}),"\n",(0,s.jsxs)(n.p,{children:["When ",(0,s.jsx)(n.code,{children:"Persistent Cache"})," enabled, ",(0,s.jsx)(n.code,{children:"load"})," and ",(0,s.jsx)(n.code,{children:"transform"})," hook may be skipped when hitting cache. If your plugin relies on previous compilation result(for example, load a virtual module based on existing modules), you may need to implement this hook to load cached infos of your plugin to ensure cache work as expected."]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"#[cache_item]\nstruct CachedStaticAssets {\n  list: Vec<Res
1ource>,\n}\n\nimpl Plugin for StaticAssetsPlugin {\n  fn plugin_cache_loaded(\n    &self,\n    cache: &Vec<u8>,\n    context: &Arc<CompilationContext>,\n  ) -> farmfe_core::error::Result<Option<()>> {\n    let cached_static_assets: CachedAssets = deserialize!(cache, CachedStaticAssets);\n\n    for asset in cached_static_assets.list {\n      if let ResourceOrigin::Module(m) = asset.origin {\n        context.emit_file(EmitFileParams {\n          resolved_path: m.to_string(),\n          name: asset.name,\n          content: asset.bytes,\n          resource_type: asset.resource_type,\n        });\n      }\n    }\n\n    Ok(Some(()))\n  }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Note:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"deserialize"})," is exposed by ",(0,s.jsx)(n.code,{children:"farmfe_core"}),", it can help you deserialize your structs or enums from ",(0,s.jsx)(n.code,{children:"Vec<u8>"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["The cached structs or enums ",(0,s.jsx)(n.strong,{children:"must be rkyv serializable"}),", you can use ",(0,s.jsx)(n.code,{children:"#[cache_item]"})," exposed by ",(0,s.jsx)(n.code,{children:"farmfe_core"})," create a cacheable struct quickly."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"build_start",children:"build_start"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"parallel"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn build_start(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Called before the first compilation starts. You can use this hook to initialize any initial status of your plugins."}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"build_start"})," is only called once for the first compilation. If you want to do something when ModuleGraph is updated in ",(0,s.jsx)(n.code,{children:"HMR"})," or ",(0,s.jsx)(n.code,{children:"Lazy Compilation"}),", you should use ",(0,s.jsx)(n.a,{href:"#update_modules",children:"update_modules"})," hook."]})}),"\n",(0,s.jsx)(n.h2,{id:"resolve",children:"resolve"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'fn resolve(\n  &self,\n  _param: &PluginResolveHookParam,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<PluginResolveHookResult>> {\n  Ok(None)\n}\n\n/// Parameter of the resolve hook\n#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]\n#[serde(rename_all = "camelCase")]\npub struct PluginResolveHookParam {\n  /// the source would like to resolve, for example, \'./index\'\n  pub source: String,\n  /// the start location to resolve `specifier`, being [None] if resolving a entry or resolving a hmr update.\n  pub importer: Option<ModuleId>,\n  /// for example, [ResolveKind::Import] for static import (`import a from \'./a\'`)\n  pub kind: ResolveKind,\n}\n\n#[derive(Debug, Default, Serialize, Deserialize, Clone)]\n#[serde(rename_all = "camelCase", default)]\npub struct PluginResolveHookResult {\n  /// resolved path, normally a absolute file path.\n  pub resolved_path: String,\n  /// whether this module should be external, if true, the module won\'t present in the final result\n  pub external: bool,\n  /// whether this module has side effects, affects tree shaking\n  pub side_effects: bool,\n  /// the query parsed from specifier, for example, query should be `{ inline: "" }` if specifier is `./a.png?inline`\n  /// if you custom plugins, your plugin should be responsible for parsing query\n  /// if you just want a normal query parsing like the example above, [farmfe_toolkit::resolve::parse_query] should be helpful\n  pub query: Vec<(String, String)>,\n  /// the meta data passed between plugins and hooks\n  pub meta: HashMap<String, String>,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Custom ",(0,s.jsx)(n.code,{children:"source"})," resolving from ",(0,s.jsx)(n.code,{children:"importer"}),", for example, resolving ",(0,s.jsx)(n.code,{children:"./b"})," from ",(0,s.jsx)(n.code,{children:"a.ts"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",metastring:'title="a.ts"',children:"import b from './b?raw';\n// ...\n"})}
1),"\n",(0,s.jsx)(n.p,{children:"Then the resolve params would be:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'let param = PluginResolveHookParam {\n  source: "./b",\n  importer: Some(ModuleId { relative_path: "a.ts", query_string: "" }),\n  kind: ResolveKind::Import\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"The resolve result of default resolver would be:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'let resolve_result = PluginResolveHookResult {\n  resolved_path: "/root/b.ts",   // resolved absolute path of the module\n  external: false, // this module should be included in the final compiled resources and should not be external\n  side_effects: false, // this module may be tree shaken as it does not contains side effects\n  query: vec![("raw", "")], // query from the source.\n  meta: HashMap::new()\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"HookContext"})," is used to pass status when you can the hooks recursively, for example, your plugin call ",(0,s.jsx)(n.code,{children:"context.plugin_driver.resolve"})," in ",(0,s.jsx)(n.code,{children:"resolve hook"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"impl Plugin for MyPlugin {\n  fn resolve(\n    &self,\n    param: &farmfe_core::plugin::PluginResolveHookParam,\n    context: &Arc<CompilationContext>,\n    hook_context: &PluginHookContext,\n  ) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginResolveHookResult>> {\n    // add a guard to avoid infinite loop\n    if let Some(caller) = &hook_context.caller {\n      if caller.as_str() == \"FarmPluginCss\" {\n        return Ok(None);\n      }\n    }\n\n    if matches!(param.kind, ResolveKind::CssAtImport | ResolveKind::CssUrl) {\n      // if dep starts with '~', means it's from node_modules.\n      // otherwise it's always relative\n      let source = if let Some(striped_source) = param.source.strip_suffix('~') {\n        striped_source.to_string()\n      } else if !param.source.starts_with('.') {\n        format!(\"./{}\", param.source)\n      } else {\n        param.source.clone()\n      };\n\n      // call resolve recursively\n      return context.plugin_driver.resolve(\n        &PluginResolveHookParam {\n          source,\n          ..param.clone()\n        },\n        context,\n        &PluginHookContext {\n          // pass caller we call resolve recursively\n          caller: Some(\"FarmPluginCss\".to_string()),\n          meta: Default::default(),\n        },\n      );\n    }\n\n    Ok(None)\n  }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["In above example, we call ",(0,s.jsx)(n.code,{children:"context.plugin_driver.resolve"})," and pass ",(0,s.jsx)(n.code,{children:"caller"})," as parameter, then we should add a guard like ",(0,s.jsx)(n.code,{children:'if caller.as_str() == "FarmPluginCss"'})," to avoid infinite loop."]}),"\n",(0,s.jsx)(n.p,{children:"Note:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["By default, you ",(0,s.jsx)(n.code,{children:"resolve hook"})," are executed ",(0,s.jsx)(n.strong,{children:"after"})," the default resolver inside Farm, only the sources that can not be resolved by internal resolver will be passed to your plugin, which means if you want to override the default resolve, you need to set your ",(0,s.jsx)(n.strong,{children:"plugin's priority larger"})," than ",(0,s.jsx)(n.code,{children:"101"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["Usually ",(0,s.jsx)(n.code,{children:"resolved_path"})," is the real absolute path that points to a file. But you can still return a ",(0,s.jsx)(n.code,{children:"virtual module id"})," like ",(0,s.jsx)(n.code,{children:"virtual:my-module"}),", but for virtual module you need to implement ",(0,s.jsx)(n.code,{children:"load"})," hook to custom how to load your virtual module. And in Farm, ",(0,s.jsx)(n.code,{children:"resolved_path + query = module_id"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"ResolveKind"})," presents the ",(0,s.jsx)(n.code,{children:"import type"}),", Example values: ",(0,s.jsx)(n.code,{children:"ResolveKind::Require"}),"(imported by commonjs require), ",(0,s.jsx)(n.code,{children:"ResolveKind::CssImport"}),"(imported by css's import statement), etc."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"meta"})," can be shared between plugins and hooks, you can get ",(0,s.jsx)(n.code,{children:"meta"})," from params of ",(0,s.jsx)(n.code,{children:"load"}),", ",(0,s.jsx)(n.code,{children:"transform"})," and ",(0,s.jsx)(n.code,{children:"parse"})," hooks in any plugin."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"load",children:"load"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'fn load(\n  &self,\n  _param: &PluginLoadHookParam,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<PluginLoadHookResult>> {\n  Ok(None)\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct PluginLoadHookParam<\'a> {\n  /// the module id string\n  pub module_id: String,\n  /// the resolved path from resolve hook\n  pub resolved_path: &\'a str,\n  /// the query map\n  pub query: Vec<(String, String)>,\n  /// the meta data passed between plugins and hooks\n  pub meta: HashMap<String, String>,\n}\n\n\n#[derive(Debug, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct PluginLoadHookResult {\n  /// the source content of the module\n  pub content: String,\n  /// the type of the module, for example [ModuleType::J
1s] stands for a normal javascript file,\n  /// usually end with `.js` extension\n  pub module_type: ModuleType,\n  /// source map of the module\n  pub source_map: Option<String>,\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Custom how to load your module from a resolved module path or module id. For example, load a virtual module:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'impl Plugin for MyPlugin {\n  fn load(\n    &self,\n    param: &PluginLoadHookParam,\n    _context: &Arc<CompilationContext>,\n    _hook_context: &PluginHookContext,\n  ) -> Result<Option<PluginLoadHookResult>> {\n    // only handle the specified path\n    if param.resolved_path == "virtual:my-plugin" {\n      return Ok(Some(\n        PluginLoadHookResult {\n          content: "import real from \'./real-path\';",\n          module_type: ModuleType::Js\n          source_map: None,\n        }\n      ))\n    }\n\n    Ok(None)\n  }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"module_type"})," and ",(0,s.jsx)(n.code,{children:"content"})," is required when loading modules in your ",(0,s.jsx)(n.code,{children:"load"})," hook. ",(0,s.jsx)(n.code,{children:"source_map"})," is optional, you can return source map if you do transform in the ",(0,s.jsx)(n.code,{children:"load"})," hook(which is not recommended, we recommend to use ",(0,s.jsx)(n.code,{children:"transform"})," hook for this situation) or you load original source map from other locations."]}),"\n",(0,s.jsx)(n.h2,{id:"transform",children:"transform"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'fn transform(\n  &self,\n  _param: &PluginTransformHookParam,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<PluginTransformHookResult>> {\n  Ok(None)\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct PluginTransformHookParam<\'a> {\n  /// the module id string\n  pub module_id: String,\n  /// source content after load or transformed result of previous plugin\n  pub content: String,\n  /// module type after load\n  pub module_type: ModuleType,\n  /// resolved path from resolve hook\n  pub resolved_path: &\'a str,\n  /// query from resolve hook\n  pub query: Vec<(String, String)>,\n  /// the meta data passed between plugins and hooks\n  pub meta: HashMap<String, String>,\n  /// source map chain of previous plugins\n  pub source_map_chain: Vec<Arc<String>>,\n}\n\n\n#[derive(Debug, Default, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase", default)]\npub struct PluginTransformHookResult {\n  /// transformed source content, will be passed to next plugin.\n  pub content: String,\n  /// you can change the module type after transform.\n  pub module_type: Option<ModuleType>,\n  /// transformed source map, all plugins\' transformed source map will be stored as a source map chain.\n  pub source_map: Option<String>,\n  /// if true, the previous source map chain will be ignored, and the source map chain will be reset to [source_map] returned by this plugin.\n  pub ignore_previous_source_map: bool,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Do transformation based on ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"module content"})})," and ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"module type"})}),". Example for transforming ",(0,s.jsx)(n.code,{children:"sass"})," to ",(0,s.jsx)(n.code,{children:"css"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'impl Plugin for MyPlugin {\n  // ignore other code ...\n  fn transform(\n    &self,\n    param: &farmfe_core::plugin::PluginTransformHookParam,\n    context: &std::sync::Arc<farmfe_core::context::CompilationContext>,\n  ) -> farmfe_core::error::Result<Option<farmfe_core::plugin::PluginTransformHookResult>> {\n    // module type guard is neccessary\n    if param.module_type == ModuleType::Custom(String::from("sass")) {\n      // parse options\n      const options = parse_options(&self.options, param.module_id);\n      // compile sass to css\n      let compile_result = compileSass(&param.content, options);\n\n      return Ok(Some(farmfe_core::plugin::PluginTransformHookResult {\n        content: compile_result.css,\n        source_map: compile_result.source_map,\n        // tell farm compiler that we have transformed this module to css\n        module_type: Some(farmfe_core::module::ModuleType::Css),\n        ignore_previous_source_map: false,\n      }));\n    }\n\n    Ok(None)\n  }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Normal steps for writing ",(0,s.jsx)(n.code,{children:"transform hook"}),":"]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["add a ",(0,s.jsx)(n.code,{children:"if"})," guard based ",(0,s.jsx)(n.code,{children:"module_type"})," or ",(0,s.jsx)(n.code,{children:"resolved_path"})," or ",(0,s.jsx)(n.code,{children:"module_id"})]}),"\n",(0,s.jsxs)(n.li,{children:["do transformation of the ",(0,s.jsx)(n.code,{children:"content"})]}),"\n",(0,s.jsxs)(n.li,{children:["return the transformed ",(0,s.jsx)(n.code,{children:"content"}),", ",(0,s.jsx)(n.code,{children:"source_map"})," and ",(0,s.jsx)(n.code,{children:"module_type"})]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["For ",(0,s.jsx)(n.code,{children:"ignore_previous_source_map"}),", if you handled ",(0,s.jsx)(n.code,{children:"param.source_map_chain"})," and collapsed the source maps of previous plugins in the ",(0,s.jsx)(n.code,{children:"transform hook"}),". You should set ignore_previous_source_map to ",(0,s.jsx)(n.code,{children:"true"})," to ensure source map is correct. Otherwise, you should always set this option to ",(0,s.jsx)(n.code,{children:"false"})," and leave source map chain handled by Farm."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"transform"})," hook is ",(0,s.jsx)(n.strong,{children:"content to content"}),". There is a similar hook called ",(0,s.jsx)(n.code,{children:"process_module"}),", ",(0,s.jsx)(n.code,{children:"process_module"})," is ",(0,s.jsx)(n.strong,{children:"ast to ast"}),". So if you want to transform the loaded content string, you need to use ",(0,s.jsx)(n.code,{children:"transform"})," hook, and if you want to transform the ",(0,s.jsx)(n.code,{children:"ast"}),", you should use ",(0,s.jsx)(n.code,{children:"process_module"})," hook."]})}),"\n",(0,s.jsx)(n.h2,{id:"parse",children:"parse"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn parse(\n  &self,\n  _param: &PluginParseHookParam,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<ModuleMetaData>> {\n  Ok(None)\n}\n\n#[derive(Debug)]\npub struct PluginParseHookParam {\n  /// module id\n  pub module_id: ModuleId,\n  /// resolved path\n  pub resolved_path: String,\n  /// resolved query\n  pub query: Vec<(String, String)>,\n  pub module_type: ModuleType,\n  /// source content(after transform)\n  pub content: Arc<String>,\n}\n\n\n/// Module meta data shared by core plugins through the compilation\n/// Meta data which is not shared by core plugins should be stored in [ModuleMetaData::Custom]\n#[cache_item]\npub enum ModuleMetaData {\n  Script(ScriptModuleMetaData),\n  Css(CssModuleMetaData),\n  Html(HtmlModuleMetaData),\n  Custom(Box<dyn SerializeCustomModuleMetaData>),\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Parse the ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"transformed module content"})})," to ",(0,s.jsx)(n.code,{children:"ast"}),". ",(0,s.jsx)(n.code,{children:"Js/Jsx/Ts/Tsx"}),", ",(0,s.jsx)(n.code,{children:"css"})," and ",(0,s.jsx)(n.code,{children:"html"})," are supported natively by Farm. Normally you do not need to implement this hook unless you want to supp
1ort a new ",(0,s.jsx)(n.code,{children:"module_type"})," other than ",(0,s.jsx)(n.code,{children:"Js/Jsx/Ts/Tsx"}),", ",(0,s.jsx)(n.code,{children:"css"})," and ",(0,s.jsx)(n.code,{children:"html"}),", use ",(0,s.jsx)(n.code,{children:"ModuleMetaData::Custom"})," for this scenario."]}),"\n",(0,s.jsx)(n.h2,{id:"process_module",children:"process_module"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn process_module(\n  &self,\n  _param: &mut PluginProcessModuleHookParam,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\npub struct PluginProcessModuleHookParam<'a> {\n  pub module_id: &'a ModuleId,\
1n  pub module_type: &'a ModuleType,\n  pub content: Arc<String>,\n  pub meta: &'a mut ModuleMetaData,\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Do transformation of the ",(0,s.jsx)(n.code,{children:"parsed result"}),", usually do ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"ast transformation"})}),". For example, Farm strip typescript in ",(0,s.jsx)(n.code,{children:"process_module"})," hook:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"impl Plugin for MyPlugin {\n   fn process_module(\n    &self,\n    param: &mut PluginProcessModuleHookParam,\n    context: &Arc<CompilationContext>,\n  ) -> Result<Option<()>> {\n    if !param.module_type.is_script() {\n      return Ok(None);\n    }\n    // strip typescript\n    if param.module_type.is_typescript() {\n      swc_script_transforms::strip_typescript(param, &cm, context)?;\n    }\n    // ...ignore other code\n\n    Ok(Some(()))\n  }\n\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"In above example, we ignore non-script modules and strip type annotation of the ast for ts/tsx modules."}),"\n",(0,s.jsx)(n.h2,{id:"analyze_deps",children:"analyze_deps"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn analyze_deps(\n  &self,\n  _param: &mut PluginAnalyzeDepsHookParam,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\npub struct PluginAnalyzeDepsHookParam<'a> {\n  pub module: &'a Module,\n  /// analyzed deps from previous plugins, you can push new entries to it for your plugin.\n  pub deps: Vec<PluginAnalyzeDepsHookResultEntry>,\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Analyze dependencies of the module. For example, we have ",(0,s.jsx)(n.code,{children:"a.ts"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",metastring:'title="a.ts"',children:"import b from './b';\nconst c = require('./c');\n"})}),"\n",(0,s.jsxs)(n.p,{children:["then normally this hook should push ",(0,s.jsx)(n.strong,{children:"2 entries"})," to ",(0,s.jsx)(n.code,{children:"params.deps"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'param.deps.push(PluginAnalyzeDepsHookResultEntry {\n  source: "./b".to_string(),\n  kind: ResolveKind::Import\n});\nparam.deps.push(PluginAnalyzeDepsHookResultEntry {\n  source: "./c".to_string(),\n  kind: ResolveKind::Require\n});\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"param.deps"})," will be passed to ",(0,s.jsx)(n.code,{children:"resolve"})," hook later. You can also add new deps that is not related to the ast of your module as you wish, Farm will ",(0,s.jsx)(n.code,{children:"resolve"}),", ",(0,s.jsx)(n.code,{children:"load"})," these unrelated modules and add them to the module graph too."]}),"\n",(0,s.jsx)(n.h2,{id:"finalize_modules",children:"finalize_modules"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn finalize_module(\n  &self,\n  _param: &mut PluginFinalizeModuleHookParam,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\npub struct PluginFinalizeModuleHookParam<'a> {\n  pub module: &'a mut Module,\n  pub deps: &'a Vec<PluginAnalyzeDepsHookResultEntry>,\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Do any thing you want before seal the module. Note that you can only modify ",(0,s.jsx)(n.code,{children:"param.module"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"build_end",children:"build_end"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"parallel"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"/// The module graph should be constructed and finalized here\nfn build_end(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Called when all dependencies starting from ",(0,s.jsx)(n.code,{children:"config.input"})," are handled and ",(0,s.jsx)(n.code,{children:"ModuleGraph"})," is successfully constructed, you can get the full resolved ",(0,s.jsx)(n.code,{children:"ModuleGraph"})," here by ",(0,s.jsx)(n.code,{children:"context.module_graph"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"build_end"})," is only called once for the first compilation. If you want to do something when ModuleGraph is updated in ",(0,s.jsx)(n.code,{children:"HMR"})," or ",(0,s.jsx)(n.code,{children:"Lazy Compilation"}),", you should use ",(0,s.jsx)(n.a,{href:"#module_graph_updated",children:"module_graph_updated"})," hook."]})}),"\n",(0,s.jsx)(n.h2,{id:"generate_start",children:"generate_start"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"parallel"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn generate_start(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Called before generate stage start."}),"\n",(0,s.jsx)(n.h2,{id:"optimize_module_graph",children:"optimize_module_graph"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"/// Some optimization of the module graph should be performed here, for example, tree shaking\nfn optimize_module_graph(\n  &self,\n  _module_graph: &mut ModuleGraph,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You can do optimization of the ",(0,s.jsx)(n.code,{children:"module_graph"})," here. For internal plugins, Farm does tree shaking, minification in this hook."]}),"\n",(0,s.jsx)(n.h2,{id:"analyze_module_graph",children:"analyze_module_graph"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"/// Analyze module group based on module graph\nfn analyze_module_graph(\n  &self,\n  _module_graph: &mut ModuleGraph,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<ModuleGroupGraph>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Analyze ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"dynamic import"})})," of the ",(0,s.jsx)(n.code,{children:"module_graph"}),", and groups modules based on ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"dynamic import"})}),", return the grouped modules."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsx)(n.p,{children:"Normally you should not implement this hook, unless you want to implement a full new bundling algorithm in Farm."})}),"\n",(0,s.jsx)(n.h2,{id:"partial_bundling",children:"partial_bundling"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'/// partial bundling modules to [Vec<Res
1ourcePot>]\nfn partial_bundling(\n  &self,\n  _modules: &Vec<ModuleId>,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<Vec<ResourcePot>>> {\n  Ok(None)\n}\n\n#[cache_item]\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct ResourcePot {\n  pub id: ResourcePotId,\n  pub name: String,\n  pub resource_pot_type: ResourcePotType,\n  modules: HashSet<ModuleId>,\n  pub meta: ResourcePotMetaData,\n  /// [None] if this [ResourcePot] does not contain entry module.\n  /// [Some(entry_id)] otherwise\n  pub entry_module: Option<ModuleId>,\n  /// the resources generated in this [ResourcePot]\n  resources: HashSet<String>,\n\n  /// This field should be filled in partial_bundling_hooks.\n  /// the module groups that this [ResourcePot] belongs to.\n  /// A [ResourcePot] can belong to multiple module groups.\n  pub module_groups: HashSet<ModuleGroupId>,\n  pub immutable: bool,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Bundle the ",(0,s.jsx)(n.code,{children:"modules"})," to ",(0,s.jsx)(n.code,{children:"Vec<ResourcePot>"})," based on ",(0,s.jsx)(n.code,{children:"module_group_graph"})," and ",(0,s.jsx)(n.code,{children:"module_graph"}),". A ",(0,s.jsx)(n.code,{children:"ResourcePot"})," is a structure that Farm uses to hold bundled modules, it will be emitted to final resources in ",(0,s.jsx)(n.a,{href:"#generate_resources",children:"generate_resources"})," hook, you can treat a ",(0,s.jsx)(n.code,{children:"ResourcePot"})," as ",(0,s.jsx)(n.code,{children:"Chunk"})," of other tools."]}),"\n",(0,s.jsx)(n.p,{children:"Note:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["This hook will be called in both ",(0,s.jsx)(n.code,{children:"first compilation"}),", ",(0,s.jsx)(n.code,{children:"HMR"})," and ",(0,s.jsx)(n.code,{children:"Lazy Compilation"}),", make sure this hook does not contains side effects(for the same modules, always returns the same ",(0,s.jsx)(n.code,{children:"Vec<ResourcePot>"}),")."]}),"\n",(0,s.jsxs)(n.li,{children:["You should set ",(0,s.jsx)(n.code,{children:"module.resource_pot"})," in this hook."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Refer to the ",(0,s.jsx)(n.a,{href:"https://github.com/farm-fe/farm/blob/main/crates/plugin_partial_bundling/src/lib.rs",children:"internal implementation"})," of partial bundling in Farm for best practice. Refer to ",(0,s.jsx)(n.a,{href:"https://github.com/farm-fe/farm/blob/main/crates/plugin_partial_bundling/src/lib.rs",children:"RFC-003 Partial Bundling"})," for how Farm designs bundling."]}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["Normally you should not implement this hook, unless you want to implement a full new bundling algorithm in Farm. And If you override this hook, ",(0,s.jsx)(n.code,{children:"config.partial_bundling"})," may not work unless you follow the same bundling spec as Farm."]})}),"\n",(0,s.jsx)(n.h2,{id:"process_resource_pots",children:"process_resource_pots"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"/// process resource pots before render and generating each resource\nfn process_resource_pots(\n  &self,\n  _resource_pots: &mut Vec<&mut ResourcePot>,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Do some ",(0,s.jsx)(n.strong,{children:"transformation"})," of the ",(0,s.jsx)(n.code,{children:"ResourcePots"}),". Note that ResourcePots are not rendered at this time, which means you can not get the rendered code of the ",(0,s.jsx)(n.code,{children:"Resource Pot"}),", instead, you can only add, remove, transform the modules inside the ",(0,s.jsx)(n.code,{children:"ResourcePot"})]}),"\n",(0,s.jsx)(n.h2,{id:"render_start",children:"render_start"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn render_start(\n  &self,\n  _config: &Config,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Called before resource pots render. After rendering resource pots, executable ",(0,s.jsx)(n.code,{children:"html"}),", ",(0,s.jsx)(n.code,{children:"css"}),", ",(0,s.jsx)(n.code,{children:"js"}),", etc files will be emitted."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"render_start"})," is only called once for the first compilation. ",(0,s.jsx)(n.code,{children:"HMR"})," or ",(0,s.jsx)(n.code,{children:"Lazy Compilation"})," won't trigger ",(0,s.jsx)(n.code,{children:"render_start"})," hook."]})}),"\n",(0,s.jsx)(n.h2,{id:"render_resource_pot_modules",children:"render_resource_pot_modules"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'fn render_resource_pot_modules(\n  &self,\n  _resource_pot: &ResourcePot,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<ResourcePotMetaData>> {\n  Ok(None)\n}\n\n#[cache_item]\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\npub struct RenderedModule {\n  pub id: ModuleId,\n  pub rendered_content: Arc<String>,\n  pub rendered_map: Option<Arc<String>>,\n  pub rendered_length: usize,\n  pub original_length: usize,\n}\n\n#[cache_item]\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct ResourcePotMetaData {\n  pub rendered_modules: HashMap<ModuleId, RenderedModule>,\n  pub rendered_content: Arc<String>,\n  pub rendered_map_chain: Vec<Arc<String>>,\n  pub custom_data: HashMap<String, String>,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Render the given ",(0,s.jsx)(n.code,{children:"ResourcePot"})," to ",(0,s.jsx)(n.code,{children:"rendered_content"})," and ",(0,s.jsx)(n.code,{children:"rendered_source_map_chain"}),". This hook is used to render ",(0,s.jsx)(n.code,{children:"module's ast"})," to bundled code. If you just want to modify the bundled code, use ",(0,s.jsx)(n.a,{href:"#render_resource_pot",children:"render_resource_pot"})," instead."]}),"\n",(0,s.jsxs)(n.p,{children:["If you really need to use this hook, refer to ",(0,s.jsx)(n.a,{href:"https://github.com/farm-fe/farm/blob/main/crates/plugin_runtime/src/lib.rs#L320",children:"plugin_runtime"})," for best practice."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["Normally you should not override this hook for natively supported module types like ",(0,s.jsx)(n.code,{children:"js/jsx/ts/tsx/css/html"}),", you should only use this hook when you ensure you want to override the default behavior for internal module types in Farm, or you want to supp
1ort ",(0,s.jsx)(n.strong,{children:"custom module types"}),"."]})}),"\n",(0,s.jsx)(n.h2,{id:"render_resource_pot",children:"render_resource_pot"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'fn render_resource_pot(\n  &self,\n  _param: &PluginRenderResourcePotHookParam,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<PluginRenderResourcePotHookResult>> {\n  Ok(None)\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct PluginRenderResourcePotHookParam {\n  pub content: Arc<String>,\n  pub source_map_chain: Vec<Arc<String>>,\n  pub resource_pot_info: ResourcePotInfo,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct PluginRenderResourcePotHookResult {\n  pub content: String,\n  pub source_map: Option<String>,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Transform the rendered bundled code for the given ",(0,s.jsx)(n.code,{children:"ResourcePot"}),". Return ",(0,s.jsx)(n.code,{children:"rendered content"})," and ",(0,s.jsx)(n.code,{children:"source map"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'impl Plugin for MyPlugin {\n  fn render_resource_pot(\n    &self,\n    param: &PluginRenderResourcePotHookParam,\n    context: &Arc<CompilationContext>,\n  ) -> Result<Option<PluginRenderResourcePotHookResult>> {\n    if (param.resource_pot_info.resource_pot_type == ResourcePotType::Css) {\n      return Ok(Some(PluginRenderResourcePotHookResult {\n        content: param.content.replaceAll("<--layer--\x3e", replaced_code),\n        source_map: replaced_map,\n      }))\n    }\n\n    Ok(None)\n  }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["In above example, we transformed the content of a css ",(0,s.jsx)(n.code,{children:"Resource Pot"}),", replaced all ",(0,s.jsx)(n.code,{children:"<--layer--\x3e"})," to ",(0,s.jsx)(n.code,{children:"replaced_code"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"augment_resource_hash",children:"augment_resource_hash"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'fn augment_resource_hash(\n  &self,\n  _render_pot_info: &ResourcePotInfo,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<String>> {\n  Ok(None)\n}\n\n#[cache_item]\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct ResourcePotInfo {\n  pub id: ResourcePotId,\n  pub name: String,\n  pub resource_pot_type: ResourcePotType,\n  pub module_ids: Vec<ModuleId>,\n  pub map: Option<Arc<String>>,\n  pub modules: HashMap<ModuleId, RenderedModule>,\n  pub data: ResourcePotInfoData,\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Append additional hash when generating resource from given resource pot."}),"\n",(0,s.jsx)(n.h2,{id:"optimize_resource_pot",children:"optimize_resource_pot"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'/// Optimize the resource pot, for example, minimize\nfn optimize_resource_pot(\n  &self,\n  _resource_pot: &mut ResourcePot,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\n#[cache_item]\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct ResourcePot {\n  pub id: ResourcePotId,\n  pub name: String,\n  pub resource_pot_type: ResourcePotType,\n  modules: HashSet<Mo
1duleId>,\n  pub meta: ResourcePotMetaData,\n  /// [None] if this [ResourcePot] does not contain entry module.\n  /// [Some(entry_id)] otherwise\n  pub entry_module: Option<ModuleId>,\n  /// the resources generated in this [ResourcePot]\n  resources: HashSet<String>,\n\n  /// This field should be filled in partial_bundling_hooks.\n  /// the module groups that this [ResourcePot] belongs to.\n  /// A [ResourcePot] can belong to multiple module groups.\n  pub module_groups: HashSet<ModuleGroupId>,\n  pub immutable: bool,\n  pub info: Box<ResourcePotInfo>,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Do some optimizations for the rendered resource pot. For example, minification. If you want to modify the rendered content of this hook, just modify ",(0,s.jsx)(n.code,{children:"resource_pot.meta.rendered_content"})," and append sourcemap of this transformation in ",(0,s.jsx)(n.code,{children:"resource_pot.meta.rendered_source_map_chain"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:"Optimizations like minification is handled internally by Farm, make sure that you really need to use this hook."})}),"\n",(0,s.jsx)(n.h2,{id:"generate_resources",children:"generate_resources"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"first"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'/// Generate resources based on the [ResourcePot], return [Vec<Resource>] represents the final generated files.\n/// For example, a .js file and its corresponding source map file\nfn generate_resources(\n  &self,\n  _resource_pot: &mut ResourcePot,\n  _context: &Arc<CompilationContext>,\n  _hook_context: &PluginHookContext,\n) -> Result<Option<PluginGenerateResourcesHookResult>> {\n  Ok(None)\n}\n\n#[cache_item]\n#[derive(Debug, Clone, Deserialize)]\n#[serde(rename_all = "camelCase")]\npub struct PluginGenerateResourcesHookResult {\n  pub resource: Resource,\n  pub source_map: Option<Resource>,\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Generate final resource for the give rendered resource pot. return ",(0,s.jsx)(n.code,{children:"generated resource"})," and ",(0,s.jsx)(n.code,{children:"optional source map resource"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["For natively supported ",(0,s.jsx)(n.code,{children:"ModuleTypes"})," like ",(0,s.jsx)(n.code,{children:"js/ts/jsx/tsx/css/html/static assets"}),", normally you do not need to implement this hook. Use this hook when you want to support a new type of resource that not natively supported by Farm."]})}),"\n",(0,s.jsx)(n.h2,{id:"finalize_resources",children:"finalize_resources"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"/// Do some finalization work on the generated resources, for example, transform html based on the generated resources\nfn finalize_resources(\n  &self,\n  _param: &mut PluginFinalizeResourcesHookParams,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\npub struct PluginFinalizeResourcesHookParams<'a> {\n  pub resources_map: &'a mut HashMap<String, Resource>,\n  pub config: &'a Config,\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Do some finalization work on the generated resources, for example, transform html based on the generated resources(insert ",(0,s.jsx)(n.code,{children:"<script>"}),", ",(0,s.jsx)(n.code,{children:"<link>"})," tags)."]}),"\n",(0,s.jsxs)(n.p,{children:["You can also ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"add"})})," or ",(0,s.jsx)(n.strong,{children:(0,s.jsx)(n.code,{children:"remove"})})," resources here."]}),"\n",(0,s.jsx)(n.h2,{id:"generate_end",children:"generate_end"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"parallel"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn generate_end(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Called when all generate stage done(including ",(0,s.jsx)(n.code,{children:"finalize_resources"}),"). You can do some cleanup work here."]}),"\n",(0,s.jsx)(n.h2,{id:"finish",children:"finish"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"parallel"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn finish(&self, _stat: &Stats, _context: &Arc<CompilationContext>) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Called when all compilation work done(including ",(0,s.jsx)(n.code,{children:"build stage"})," and ",(0,s.jsx)(n.code,{children:"generate stage"}),"). You can do some cleanup work here."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"finish"})," is only called once for the first compilation. ",(0,s.jsx)(n.code,{children:"HMR"})," or ",(0,s.jsx)(n.code,{children:"Lazy Compilation"})," won't trigger ",(0,s.jsx)(n.code,{children:"finish"})," hook. You should use ",(0,s.jsx)(n.a,{href:"#update_finished",children:"update_finished"})," hook instead."]})}),"\n",(0,s.jsx)(n.h2,{id:"write_plugin_cache",children:"write_plugin_cache"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn write_plugin_cache(&self, _context: &Arc<CompilationContext>) -> Result<Option<Vec<u8>>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Extend ",(0,s.jsx)(n.a,{href:"/docs/advanced/persistent-cache",children:(0,s.jsx)(n.code,{children:"persistent cache"})})," writing for your plugin. ",(0,s.jsx)(n.code,{children:"write_plugin_cache"})," is often used together with ",(0,s.jsx)(n.a,{href:"#plugin_cache_loaded",children:"plugin_cache_loaded"})," to read and write persistent cache for plugin. Return the serialized bytes of your data."]}),"\n",(0,s.jsx)(n.p,{children:"Example, writing cache for static assets:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"impl Plugin for MyPlugin {\n  fn write_plugin_cache(\n    &self,\n    context: &Arc<CompilationContext>,\n  ) -> farmfe_core::error::Result<Option<Vec<u8>>> {\n    let mut list = vec![];\n    let resources_map = context.resources_map.lock();\n\n    for (_, resource) in resources_map.iter() {\n      if let ResourceOrigin::Module(m) = &resource.origin {\n        if context.module_graph.read().has_module(m) {\n          list.push(resource.clone());\n        }\n      }\n    }\n\n    if !list.is_empty() {\n      let cached_static_assets = CachedStaticAssets { list };\n\n      Ok(Some(serialize!(&cached_static_assets)))\n    } else {\n      Ok(None)\n    }\n  }\n}
1\n\n#[cache_item]\nstruct CachedStaticAssets {\n  list: Vec<Resource>,\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"update_modules",children:"update_modules"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'/// Called when calling compiler.update(module_paths).\n/// Useful to do some operations like clearing previous state or ignore some files when performing HMR\nfn update_modules(\n  &self,\n  _params: &mut PluginUpdateModulesHookParams,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase", default)]\npub struct PluginUpdateModulesHookParams {\n  pub paths: Vec<(String, UpdateType)>,\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Called when calling compiler.update(module_paths). Useful to do some operations like clearing previous state or ignore some files when performing HMR"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"paths"})," is paths that will be recompiled for this update"]}),"\n",(0,s.jsxs)(n.li,{children:["return the new ",(0,s.jsx)(n.code,{children:"paths"}),", later compilation will update the new returned paths."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"module_graph_updated",children:"module_graph_updated"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:'/// Called when calling compiler.update(module_paths).\n/// Useful to do some operations like modifying the module graph\nfn module_graph_updated(\n  &self,\n  _param: &PluginModuleGraphUpdatedHookParams,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[serde(rename_all = "camelCase", default)]\npub struct PluginModuleGraphUpdatedHookParams {\n  pub added_modules_ids: Vec<ModuleId>,\n  pub removed_modules_ids: Vec<ModuleId>,\n  pub updated_modules_ids: Vec<ModuleId>,\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Called when calling compiler.update(module_paths). Useful to do some operations like modifying the module graph."}),"\n",(0,s.jsx)(n.h2,{id:"update_finished",children:"update_finished"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"/// Called when calling compiler.update(module_paths).\n/// This hook is called after all compilation work is done, including the resources regeneration and finalization.\nfn update_finished(\n  &self,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<()>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Called when calling compiler.update(module_paths). This hook is called after all compilation work is done, including the resources regeneration and finalization."}),"\n",(0,s.jsx)(n.h2,{id:"handle_persistent_cached_module",children:"handle_persistent_cached_module"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["hook type: ",(0,s.jsx)(n.code,{children:"serial"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"default:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rust",children:"fn handle_persistent_cached_module(\n  &self,\n  _module: &farmfe_core::module::Module,\n  _context: &Arc<CompilationContext>,\n) -> Result<Option<bool>> {\n  Ok(None)\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Called when persistent cache is enabled and the cache hit for the module. Return ",(0,s.jsx)(n.code,{children:"true"})," to ",(0,s.jsx)(n.strong,{children:"skip loading cache for this module"}),"."]})]})}function u(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},59694:(e,n,r)=>{r.d(n,{A:()=>s});const s=r.p+"assets/images/farm-plugin-hooks-cd6dfe727da737695a51cefe7c30e610.png"}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.