PageSourceSearch

https://farm-nightly.netlify.app/assets/js/2ed7416b.4a96b460.js

js farm-nightly.netlify.app collected 2026-10-03 10:44:57 UTC 49,444 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkfarm_docs=self.webpackChunkfarm_docs||[]).push([[4614],{81051:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>t,contentTitle:()=>l,default:()=>h,frontMatter:()=>i,metadata:()=>d,toc:()=>c});var s=r(49214),o=r(36906);const i={},l="Js Plugin Api",d={id:"api/js-plugin-api",title:"Js Plugin Api",description:"Farm Js Plugin has designed a similar rollup style design plugin system and easy to migrate your plugins/projects from Rollup/Vite/Webpack.",source:"@site/docs/api/js-plugin-api.md",sourceDirName:"api",slug:"/api/js-plugin-api",permalink:"/docs/api/js-plugin-api",draft:!1,unlisted:!1,editUrl:"https://github.com/farm-fe/farm-fe.github.io/tree/main/docs/api/js-plugin-api.md",tags:[],version:"current",frontMatter:{},sidebar:"apiSidebar",previous:{title:"Hmr Api",permalink:"/docs/api/hmr-api"},next:{title:"Rust Plugin Api",permalink:"/docs/api/rust-plugin-api"}},t={},c=[{value:"Configuring Js Plugins",id:"configuring-js-plugins",level:2},{value:"Writing Js Plugins",id:"writing-js-plugins",level:2},{value:"Plugin Hook Overview",id:"plugin-hook-overview",level:2},{value:"hooks",id:"hooks",level:2},{value:"name",id:"name",level:3},{value:"priority",id:"priority",level:3},{value:"config",id:"config",level:3},{value:"configResolved",id:"configresolved",level:3},{value:"configureDevServer",id:"configuredevserver",level:3},{value:"configureCompiler",id:"configurecompiler",level:3},{value:"buildStart",id:"buildstart",level:3},{value:"resolve",id:"resolve",level:3},{value:"load",id:"load",level:3},{value:"transform",id:"transform",level:3},{value:"buildEnd",id:"buildend",level:3},{value:"renderStart",id:"renderstart",level:3},{value:"renderResourcePot",id:"renderresourcepot",level:3},{value:"augmentResourceHash",id:"augmentresourcehash",level:3},{value:"finalizeResources",id:"finalizeresources",level:3},{value:"transformHtml",id:"transformhtml",level:3},{value:"writeResources",id:"writeresources",level:3},{value:"pluginCacheLoaded",id:"plugincacheloaded",level:3},{value:"writePluginCache",id:"writeplugincache",level:3},{value:"finish",id:"finish",level:3},{value:"updateModules",id:"updatemodules",level:3}];function a(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",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:"js-plugin-api",children:"Js Plugin Api"})}),"\n",(0,s.jsx)(n.p,{children:"Farm Js Plugin has designed a similar rollup style design plugin system and easy to migrate your plugins/projects from Rollup/Vite/Webpack."}),"\n",(0,s.jsx)(n.h2,{id:"configuring-js-plugins",children:"Configuring Js Plugins"}),"\n",(0,s.jsxs)(n.p,{children:["Adding JS 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// import a js plugin\nimport farmPluginFoo from "farm-plugin-foo";\n\nexport default defineConfig({\n  // configuring it in plugins\n  plugins: [farmPluginFoo()],\n});\n'})}),"\n",(0,s.jsx)(n.h2,{id:"writing-js-plugins",children:"Writing Js Plugins"}),"\n",(0,s.jsxs)(n.p,{children:["A Farm Js Plugin is a plain javascript object which exposes a set of ",(0,s.jsx)(n.code,{children:"hook"}),"s. for example:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",metastring:'title="my-farm-plugin.ts"',children:"// Create a plugin file that exports a plugin function which returns a `JsPlugin` Object:\nimport type { JsPlugin } from '@farmfe/core';\n\n// Plugin Options\nexport interface PluginOptions {\n  test: boolean;\n}\n// export a Plugin Function\nexport default function MyPlugin(options: PluginOptions): JsPlugin {\n  // reading options\n  const { test } = options;\n\n  // return a object that exposes hook\n  return {\n    name: 'my-farm-plugin',\n    // using load hook to load custom modules\n    load: {\n      filters: {\n        resolvedPaths: ['\\\\.test$'] // filter files to improve performance\n      },\n      async executor({ resolvedPath }) {\n        if (test && resolvedPath.endsWith('.test')) {\n          return {\n            content: 'test file',\n            sourceMap: null\n          }\n        }\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Farm provided ",(0,s.jsx)(n.code,{children:"create-farm-plugin"})," tool to help you create and develop you js plugin quickly. For more details about writing JS plugins, refer to ",(0,s.jsx)(n.a,{href:"/docs/plugins/writing-plugins/js-plugin",children:"Writing JS Plugins"})]}),"\n"]})}),"\n",(0,s.jsx)(n.h2,{id:"plugin-hook-overview",children:"Plugin Hook Overview"}),"\n",(0,s.jsxs)(n.p,{children:["The Js plugin hook is the same as the Rust plugin, See ",(0,s.jsx)(n.a,{href:"/docs/api/rust-plugin-api#plugin-hooks-overview",children:"Rust Plugin Hook Overview"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:"Not all hooks are exposed to Js Plugins, only hooks listed in this document are available."})}),"\n",(0,s.jsx)(n.h2,{id:"hooks",children:"hooks"}),"\n",(0,s.jsx)(n.h3,{id:"name",children:"name"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"string"})]})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"true"})]})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The name of this plugins, MUST not be empty."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"export default function MyPlugin() {\
1n  return {\n    name: 'my-plugin',\n    // ...\n  }\n}\n"})}),"\n",(0,s.jsx)(n.h3,{id:"priority",children:"priority"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"number"})]})}),"\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:["default: ",(0,s.jsx)(n.code,{children:"100"})]})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["The priority of this plugins, default to ",(0,s.jsx)(n.code,{children:"100"}),". ",(0,s.jsx)(n.code,{children:"priority"})," controls the execution order of plugins, the larger the value, the earlier the plugin is executed."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"export default function MyPlugin() {\n  return {\n    name: 'my-plugin',\n    priority: 1000, // make this plugins execute before all other plugins\n    // ...\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["Note that the priority of most farm internal plugins like ",(0,s.jsx)(n.code,{children:"plugin-script"}),", ",(0,s.jsx)(n.code,{children:"plugin-resolve"})," is ",(0,s.jsx)(n.code,{children:"99"}),", which means your plugins is always executed before the internal plugins. If your want to make your plugin executed after farm internal plugins, set ",(0,s.jsx)(n.code,{children:"priority"})," to a value that smaller than ",(0,s.jsx)(n.code,{children:"99"}),", for example: ",(0,s.jsx)(n.code,{children:"98"}),". Also the priority value can be negative, you can set it to ",(0,s.jsx)(n.code,{children:"-9999"})," to make sure it is always executed at last."]})}),"\n",(0,s.jsx)(n.h3,{id:"config",children:"config"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"config?: (config: UserConfig) => UserConfig | Promise<UserConfig>;"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Modify ",(0,s.jsx)(n.a,{href:"/docs/config/configuring-farm",children:"Farm config"})," in ",(0,s.jsx)(n.code,{children:"config"})," hook, return the (partial) ",(0,s.jsx)(n.code,{children:"modified config"}),", the returned config will be deeply merged into the config resolved from cli and config file. You can also directly mutate the config."]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const resolveConfigPlugin = () => ({\n  name: 'return-resolve-config-plugin',\n  config: (_config) => ({\n    compilation: {\n      resolve: {\n        alias: {\n          foo: 'bar'\n        }\n      }\n    }\n  })\n});\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"config"})," hook is called after all ",(0,s.jsx)(n.code,{children:"user plugins"})," are resolved, so add new plugins into the config has no effect."]})}),"\n",(0,s.jsx)(n.h3,{id:"configresolved",children:"configResolved"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"configResolved?: (config: ResolvedUserConfig) => void | Promise<void>;"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Called when the config resolved(after all plugin's ",(0,s.jsx)(n.code,{children:"config"})," hook being called). Useful when you want to get the final resolved config for your plugin."]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  let farmConfig;\n\n  return {\n    name: 'my-plugin',\n    configResolved(resolvedConfig) {\n      // get resolved config\n      resolvedConfig = farmConfig;\n    },\n    transform: {\n      filters: {\n        moduleTypes: ['js']\n      },\n      async executor(param) {\n        if (farmConfig.xxx) {\n          // ...\n        }\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.h3,{id:"configuredevserver",children:"configureDevServer"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"configureDevServer?: (server: Server) => void | Promise<void>;"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:"Note that this hook runs in development mode only."})}),"\n",(0,s.jsxs)(n.p,{children:["Called when ",(0,s.jsx)(n.code,{children:"Dev Server"})," is ready, you can get the dev server instance."]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  let devServer;\n\n  return {\n    name: 'my-plugin',\n    configureDevServer(server) {\n      devServer = server;\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["Both ",(0,s.jsx)(n.code,{children:"config"})," and ",(0,s.jsx)(n.code,{children:"configResolved"})," hook of ",(0,s.jsx)(n.code,{children:"js plugin"})," are called before ",(0,s.jsx)(n.code,{children:"config"})," hook of ",(0,s.jsx)(n.code,{children:"rust plugin"}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"configurecompiler",children:"configureCompiler"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"configureCompiler?: (compiler: Compiler) => void | Promise<void>;"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Called when ",(0,s.jsx)(n.code,{children:"Rust Compiler"})," is ready, this hook runs in both development and production. You can get ",(0,s.jsx)(n.code,{children:"Compiler"})," instance here"]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  let farmCompiler;\n\n  return {\n    name: 'my-plugin',\n    configureCompiler(compiler) {\n      farmCompiler = compiler;\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.h3,{id:"buildstart",children:"buildStart"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"buildStart?: { executor: Callback<Record<string, never>, void> };"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Called before the compilation starts. You can do some initialization work here."}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  // your plugin operations\n  let myPluginContext = createMyPluginContext();\n\n  return {\n    name: 'my-plugin',\n    buildStart: {\n      async executor() {\n        // set up my plugin before compilation.\n        myPluginContext.setup();\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"buildStart"})," is only called once for the first compile. Later compiling like ",(0,s.jsx)(n.code,{children:"Lazy Compilation"})," and ",(0,s.jsx)(n.code,{children:"HMR Update"})," won't trigger ",(0,s.jsx)(n.code,{children:"buildStart"}),"."]})}),"\n",(0,s.jsx)(n.h3,{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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type ResolveHook = { \n  filters: {\n    importers: string[];\n    sources: string[];\n  };\n  executor: Callback<PluginResolveHookParam, PluginResolveHookResult> \n};\n\ntype Callback<P, R> = (\n  param: P,\n  context?: CompilationContext,\n  hookContext?: { caller?: string; meta: Record<string, unknown> }\n) => Promise<R | null | undefined>;\n\n/// Parameter of the resolve hook\nexport interface PluginResolveHookParam {\n  /// the start location to resolve `source`, being [None] if resolving a entry or resolving a hmr update.\n  /// it's id of the parent module, for example: `src/index.ts` or `src/index.vue?vue&type=xxx`\n  importer: string | null;\n  /// for example, [ResolveKind::Import] for static import (`import a from './a'`)\n  kind: ResolveKind;\n  /// source of the import. for example in index.ts (import App from \"./App.vue\")\n  /// source should be './App.vue'\n  source: string;\n}\n/// Resolve result of the resolve hook\nexport interface PluginResolveHookResult {\n  /// resolved path, normally a absolute path. you can also return a virtual path, and use [PluginLoadHookResult] to provide the content of the virtual path\n  resolvedPath: string;\n  /// whether this module should be external, if true, the module won't present in the final result\n  external: boolean;\n  /// whether this module has side effects, affects tree shaking\n  sideEffects: boolean;\n  /// the query parsed from specifier, for example, query should be `{ inline: true }` 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, [crate::utils::parse_query] is for you\n  query: [string, string][] | null;\n  /// meta data of the module, will be passed to [PluginLoadHookParam] and [PluginTransformHookParam]\n  meta: Record<string, string> | null;\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["All filters ",(0,s.jsx)(n.code,{children:"sources"})," and ",(0,s.jsx)(n.code,{children:"importers"})," of resolve hook are ",(0,s.jsx)(n.code,{children:"regex string"}),"."]})}),"\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-ts",children:'const param = {\n  source: "./b",\n  importer: { relative_path: "a.ts", query_string: "" },\n  kind: \'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:'const resolve_result = {\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: [["raw", ""]], // query from the source.\n  meta: {}\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.resolve"})," in ",(0,s.jsx)(n.code,{children:"resolve hook"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => ({\n  name: 'my-plugin',\n  resolve: {\n    filters: {\n      sources: ['^.+foo.+$'],\n      importers: ['^src/index.ts$']\n    },\n    executor: async (param, context, hookContext) => {\n      console.log(param);\n      if (hookContext.caller === 'my-plugin') {\n        return null;\n      }\n      // replace the original source and resolve new source\n      const newSource = param.source.replace('foo', 'bar');\n      return context.resolve({\n        ...param,\n        source: newSource\n      }, {\n        caller: 'my-plugin',\n        meta: {}\n      });\n    }\n  }\n});\n"})}),"\n",(0,s.jsxs)(n.p,{children:["In above example, we call ",(0,s.jsx)(n.code,{children:"context.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 (hookContext.caller === 'my-plugin') {"})," 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:"require"}),"(imported by commonjs require), ",(0,s.jsx)(n.code,{children:"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.h3,{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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type LoadHook = { \n  filters: {\n    importers: string[];\n    sources: string[];\n  };\n  executor: Callback<PluginLoadHookParam, PluginLoadHookResult> \n};\n\ntype Callback<P, R> = (\n  param: P,\n  context?: CompilationContext,\n  hookContext?: { caller?: string; meta: Record<string, unknown> }\n) => Promise<R | null | undefined>;\n\nexport interface PluginLoadHookParam {\n  moduleId: string;\n  resolvedPath: string;\n  query: [string, string][];\n  meta: Record<string, string> | null;\n}\n\nexport interface PluginLoadHookResult {\n  /// the content of the module\n  content: string;
1\n  /// the type of the module, for example [ModuleType::Js] stands for a normal javascript file,\n  /// usually end with `.js` extension\n  moduleType: ModuleType;\n  /// source map of the module\n  sourceMap?: string | null;\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-ts",children:"const myPlugin = () => ({\n  name: 'my-plugin',\n  load: {\n    filters: {\n      resolvedPaths: ['^virtual:my-plugin$'],\n    },\n    executor: async (param, context, hookContext) => {\n      if (param.resolvedPath === 'virutal:my-plugin') {\n        return {\n          content: 'export default \"foo\"',\n          moduleType: 'js'\n        };\n      }\n    }\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.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"filters.resolvedPath"})," of ",(0,s.jsx)(n.code,{children:"load hook"})," is ",(0,s.jsx)(n.code,{children:"resolvedPath + query"}),", for example: ",(0,s.jsx)(n.code,{children:"/root/src/index.vue?vue&type=style&lang=css"}),". If you want to ignore query when filtering modules, you can use ",(0,s.jsx)(n.code,{children:"$"}),": ",(0,s.jsx)(n.code,{children:"src/index\\\\.vue$"}),"; If you want to filter modules by query, for example, filtering ",(0,s.jsx)(n.code,{children:"lang=css"}),", you can use ",(0,s.jsx)(n.code,{children:"src/index.vue\\\\.+\\\\?vue&.+lang=css"}),"."]}),"\n",(0,s.jsx)(n.h3,{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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type TransformHook = { \n  filters: {\n    importers: string[];\n    sources: string[];\n  };\n  executor: Callback<PluginTransformHookParam, PluginTransformHookResult> \n};\n\ntype Callback<P, R> = (\n  param: P,\n  context?: CompilationContext,\n  hookContext?: { caller?: string; meta: Record<string, unknown> }\n) => Promise<R | null | undefined>;\n\nexport interface PluginTransformHookParam {\n  moduleId: string;\n  /// source content after load or transformed result of previous plugin\n  content: string;\n  /// module type after load\n  moduleType: ModuleType; // Module Type is 'js' | 'jsx' | 'ts' | 'tsx' | 'css' | 'html'...\n  resolvedPath: string;\n  query: [string, string][];\n  meta: Record<string, string> | null;\n  sourceMapChain: string[];\n}\n\nexport interface PluginTransformHookResult {\n  /// transformed source content, will be passed to next plugin.\n  content: string;\n  /// you can change the module type after transform.\n  moduleType?: ModuleType;\n  /// transformed source map, all plugins' transformed source map will be stored as a source map chain.\n  sourceMap?: string | null;\n  // ignore previous source map. if true, the source map chain will be cleared. and this result should return a new source map that combines all previous source map.\n  ignorePreviousSourceMap?: boolean;\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-ts",children:"export default function farmSassPlugin(\n  options: SassPluginOptions = {}\n): JsPlugin {\n  return {\n    name: pluginName,\n    load: {\n      filters: { resolvedPaths: ['\\\\.(scss|sass)$'] },\n      async executor(param) {\n        if (param.query.length === 0 && existsSync(param.resolvedPath)) {\n          const data = await readFile(param.resolvedPath);\n          return {\n            content: data,\n            moduleType: 'sass'\n          };\n        }\n\n        return null;\n      }\n    },\n    transform: {\n      filters: {\n        moduleTypes: ['sass']\n      },\n      async executor(param, ctx) {\n        const { css: compiledCss, map } = compileSass(param.content);\n        return {\n          content: compiledCss,\n          moduleType: 'css' // transformed sass to css,\n          sourceMap: JSON.stringify(ma
1p)\n          ignorePreviousSourceMap: false,\n        }\n      }\n    }\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:"moduleType"})," or ",(0,s.jsx)(n.code,{children:"resolvedPath"})," or ",(0,s.jsx)(n.code,{children:"moduleId"})]}),"\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:"sourceMap"})," and ",(0,s.jsx)(n.code,{children:"moduleType"})]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["For ",(0,s.jsx)(n.code,{children:"ignorePreviousSourceMap"}),", if you handled ",(0,s.jsx)(n.code,{children:"param.sourceMapChain"})," and collapsed the source maps of previous plugins in the ",(0,s.jsx)(n.code,{children:"transform hook"}),". You should set ",(0,s.jsx)(n.code,{children:"ignorePreviousSourceMap"})," 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.p,{children:"For filters:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["When both ",(0,s.jsx)(n.code,{children:"resolvedPaths"})," and ",(0,s.jsx)(n.code,{children:"moduleTypes"})," are specified, take the union."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"filters.resolvedPaths"})," is ",(0,s.jsx)(n.code,{children:"resolvedPath + query"}),", for example: ",(0,s.jsx)(n.code,{children:"/root/src/index.vue?vue&type=style&lang=css"}),". If you want to ignore query when filtering modules, you can use ",(0,s.jsx)(n.code,{children:"$"}),": ",(0,s.jsx)(n.code,{children:"src/index\\\\.vue$"}),"; If you want to filter modules by query, for example, filtering ",(0,s.jsx)(n.code,{children:"lang=css"}),", you can use ",(0,s.jsx)(n.code,{children:"src/index.vue\\\\.+\\\\?vue&.+lang=css"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"filters.moduleTypes"})," is ",(0,s.jsx)(n.strong,{children:"NOT"})," ",(0,s.jsx)(n.code,{children:"regex"}),", it must exactly match the ",(0,s.jsx)(n.code,{children:"ModuleType"})," like ",(0,s.jsx)(n.code,{children:"css"}),", ",(0,s.jsx)(n.code,{children:"js"}),", ",(0,s.jsx)(n.code,{children:"tsx"}),", etc."]}),"\n"]}),"\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"}),". Js plugin does not support ",(0,s.jsx)(n.code,{children:"process_module"})," hook due to performance issues, if you want ",(0,s.jsx)(n.strong,{children:"ast to ast"})," transformations, try ",(0,s.jsx)(n.a,{href:"/docs/plugins/writing-plugins/rust-plugin",children:(0,s.jsx)(n.code,{children:"Rust Plugin"})})," instead."]})}),"\n",(0,s.jsx)(n.h3,{id:"buildend",children:"buildEnd"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"buildEnd?: { executor: Callback<Record<string, never>, void> };"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Called after the ",(0,s.jsx)(n.code,{children:"ModuleGraph"})," built, but before the resources render and generation starts. You can do some status updating or finalization work here."]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  // your plugin operations\n  let myPluginContext = createMyPluginContext();\n\n  return {\n    name: 'my-plugin',\n    buildEnd: {\n      async executor() {\n        // update my plugin status\n        myPluginContext.updateStatus('module-graph-built');\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"buildEnd"})," is only called once for the first compile. Later compiling like ",(0,s.jsx)(n.code,{children:"Lazy Compilation"})," and ",(0,s.jsx)(n.code,{children:"HMR Update"})," won't trigger ",(0,s.jsx)(n.code,{children:"buildEnd"}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"renderstart",children:"renderStart"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"renderStart?: { executor: Callback<Config['config'], void>; };"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Called before the resources render starts."}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  // your plugin operations\n  let myPluginContext = createMyPluginContext();\n\n  return {\n    name: 'my-plugin',\n    renderStart: {\n      async executor() {\n        // update my plugin status\n        myPluginContext.updateStatus('render-start');\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"renderStart"})," is only called once for the first compile. Later compiling like ",(0,s.jsx)(n.code,{children:"Lazy Compilation"})," and ",(0,s.jsx)(n.code,{children:"HMR Update"})," won't trigger ",(0,s.jsx)(n.code,{children:"renderStart"}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"renderresourcepot",children:"renderResourcePot"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type RenderResourcePotHook = JsPluginHook<\n  {\n    resourcePotTypes?: ResourcePotType[];\n    moduleIds?: string[];\n  }
1,\n  RenderResourcePotParams,\n  RenderResourcePotResult\n>;\n\ntype Callback<P, R> = (\n  param: P,\n  context?: CompilationContext,\n) => Promise<R | null | undefined>;\ntype JsPluginHook<F, P, R> = { filters: F; executor: Callback<P, R> };\n\nexport interface RenderResourcePotParams {\n  content: string;\n  sourceMapChain: string[];\n  resourcePotInfo: {\n    id: string;\n    name: string;\n    resourcePotType: ResourcePotType;\n    map?: string;\n    modules: Record<ModuleId, RenderedModule>;\n    moduleIds: ModuleId[];\n    data: JsResourcePotInfoData;\n    custom: Record<string, string>;\n  };\n}\nexport interface RenderResourcePotResult {\n  content: string;\n  sourceMap?: string;\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"Resource Pot"})," is the abstract representation of the final output bundle file, you can return transformed ",(0,s.jsx)(n.code,{children:"resourcePot content"})," to mutate the final bundle. For example, rendering css:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => ({\n  name: 'test-render-resource-pot',\n  renderResourcePot: {\n    filters: {\n      moduleIds: ['^index.ts\\\\?foo=bar$'],\n      resourcePotTypes: ['css']\n    },\n    executor: async (param) => {\n      return {\n        content: param.content.replace(\n          '<--layer--\x3e',\n          cssCode\n        ),\n        sourceMap\n      };\n    }\n  }\n})\n"})}),"\n",(0,s.jsxs)(n.p,{children:["We transform all ",(0,s.jsx)(n.code,{children:"<--layer--\x3e"})," in css resource pot and replace them to real ",(0,s.jsx)(n.code,{children:"css code"}),"."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["When both ",(0,s.jsx)(n.code,{children:"filters.moduleIds"})," and ",(0,s.jsx)(n.code,{children:"filters.resourcePotTypes"})," are specified, take the union."]})}),"\n",(0,s.jsx)(n.h3,{id:"augmentresourcehash",children:"augmentResourceHash"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type AugmentResourceHash = JsPluginHook<\n  {\n    resourcePotTypes?: ResourcePotType[];\n    moduleIds?: string[];\n  },\n  {\n    id: string;\n    name: string;\n    resourcePotType: ResourcePotType;\n    map?: string;\n    modules: Record<ModuleId, RenderedModule>;\n    moduleIds: ModuleId[];\n    data: JsResourcePotInfoData;\n    custom: Record<string, string>;\n  },\n  string\n>;\n\ntype Callback<P, R> = (\n  param: P,\n  context?: CompilationContext,\n) => Promise<R | null | undefined>;\ntype JsPluginHook<F, P, R> = { filters: F; executor: Callback<P, R> };\n"})}),"\n",(0,s.jsx)(n.p,{children:"Append resource hash for give Resource Pot. Useful if you want to add additional conditions when generating resource hash."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => ({\n  name: 'test-augment-resource-pot',\n  renderResourcePot: {\n    filters: {\n      moduleIds: ['^index.ts\\\\?foo=bar$'],\n      resourcePotTypes: ['css']\n    },\n    executor: async (param) => {\n      return 'my-hash-args';\n    }\n  }\n})\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["When both ",(0,s.jsx)(n.code,{children:"filters.moduleIds"})," and ",(0,s.jsx)(n.code,{children:"filters.resourcePotTypes"})," are specified, take the union."]})}),"\n",(0,s.jsx)(n.h3,{id:"finalizeresources",children:"finalizeResources"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type FinalizeResourcesHook = {\n  executor: Callback<\n    FinalizeResourcesHookParams,\n    FinalizeResourcesHookParams['resourcesMap']\n  >;\n};\n\nexport type FinalizeResourcesHookParams = {\n  resourcesMap: Record<string, Resource>;\n  config: Config['config'];\n};\n\nexport interface Resource {\n  name: string;\n  bytes: number[];\n  emitted: boolean;\n  resourceType: string;\n  origin: { type: 'ResourcePot' | 'Module'; value: string };\n  info?: ResourcePotInfo;\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Do some transformations for all generated resources, return ",(0,s.jsx)(n.code,{children:"transformed resourcesMap"}),". You can ",(0,s.jsx)(n.code,{children:"add"}),", ",(0,s.jsx)(n.code,{children:"remove"}),", ",(0,s.jsx)(n.code,{children:"modify"})," final generated resources in this hook."]}),"\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:"bytes"})," is binary of the final output, for ",(0,s.jsx)(n.code,{children:"js/css/html"})," code, you can use ",(0,s.jsx)(n.code,{children:"Buffer.from(bytes).toString()"})," to get the code."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"name"})," is the final file name."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"origin"})," represent where this ",(0,s.jsx)(n.code,{children:"Resource"})," is from, ",(0,s.jsx)(n.code,{children:"ResourcePot"})," means it's generated from ",(0,s.jsx)(n.code,{children:"ResourcePot"})," which is a modules bundle; ",(0,s.jsx)(n.code,{children:"Module"})," means it's from ",(0,s.jsx)(n.code,{children:"Module"}),", for example, static files like ",(0,s.jsx)(n.code,{children:".png/.jpg"})," are from ",(0,s.jsx)(n.code,{children:"Module"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"transformhtml",children:"transformHtml"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type TransformHtmlHook = {\n  order?: 0 | 1 | 2;\n  executor: Callback<{ htmlResource: Resource }, Resource>;\n};\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"order"})," is used to configure when to execute ",(0,s.jsx)(n.code,{children:"transformHtml"})," hook:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"0"}),": means ",(0,s.jsx)(n.code,{children:"pre"}),", executed before parse and generate resources. You can transform original html in this stage."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"1"})," and ",(0,s.jsx)(n.code,{children:"2"}),": means ",(0,s.jsx)(n.code,{children:"normal"})," and ",(0,s.jsx)(n.code,{children:"post"}),", executed after parse and generate resources. In this stage, all ",(0,s.jsx)(n.code,{children:"<script>"}),", ",(0,s.jsx)(n.code,{children:"<link>"})," tag are injected."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Transform the final generated html(after all ",(0,s.jsx)(n.code,{children:"<script>"}),", ",(0,s.jsx)(n.code,{children:"<link>"})," tag are injected)."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => ({\n  name: 'my-plugin',\n  transformHtml: {\n    order: 2,\n    async executor({ htmlResource }) {\n      const htmlCode = Buffer.from(htmlResource).toString();\n  \n      const newHtmlCode = htmlCode.replace('my-app-data', data);\n      htmlResource.bytes = [...Buffer.from(newHtmlCode)];\n\n      return htmlResource;\n    }\n  }\n});\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["You should modify ",(0,s.jsx)(n.code,{children:"bytes"})," field of ",(0,s.jsx)(n.code,{children:"htmlResource"})," and return the mutated ",(0,s.jsx)(n.code,{children:"htmlResource"}),", mutate any other fields take no effects"]})}),"\n",(0,s.jsx)(n.h3,{id:"writeresources",children:"writeResources"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type WriteResourcesHook = {\n  executor: (param: FinalizeResourcesHookParams) => void | Promise<void>;\n};\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Called ",(0,s.jsx)(n.strong,{children:"AFTER"})," all resources are written to disk."]}),"\n",(0,s.jsx)(n.h3,{id:"plugincacheloaded",children:"pluginCacheLoaded"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type PluginCacheLoaded
1Hook = {\n  executor: Callback<number[], undefined | null | void>;\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-ts",children:"const myPlugin = () => {\n  let cachedData;\n\n  return {\n    name: 'my-plugin',\n    pluginCacheLoaded: {\n      async executor(bytes) {\n        const str = Buffer.from(bytes).toString();\n        cachedData = JSON.parse(str);\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["You must decide how to ",(0,s.jsx)(n.code,{children:"serialize/deserialize"})," cache to ",(0,s.jsx)(n.code,{children:"bytes"})," in your plugins. For a basic example, you can deserialize data by ",(0,s.jsx)(n.code,{children:"[...Buffer.from(JSON.stringify(data))]"})]})}),"\n",(0,s.jsx)(n.h3,{id:"writeplugincache",children:"writePluginCache"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type WritePluginCacheHook = {\n  executor: Callback<undefined, number[]>;\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:"writePluginCache"})," is often used together with ",(0,s.jsx)(n.a,{href:"#plugincacheloaded",children:"pluginCaceLoaded"})," to read and write persistent cache for plugin. Return the serialized bytes of your data."]}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  let cachedData = { foo: 'bar' };\n\n  return {\n    name: 'my-plugin',\n    writePluginCache: {\n      async executor() {\n        const bytes = [...Buffer.from(JSON.stringify(data))];\n        return bytes;\n      }\n    }\n  }\n}\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["You must decide how to ",(0,s.jsx)(n.code,{children:"serialize/deserialize"})," cache to ",(0,s.jsx)(n.code,{children:"bytes"})," in your plugins. For a basic example, you can deserialize data by ",(0,s.jsx)(n.code,{children:"[...Buffer.from(JSON.stringify(data))]"})]})}),"\n",(0,s.jsx)(n.h3,{id:"finish",children:"finish"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsxs)(n.strong,{children:["type: ",(0,s.jsx)(n.code,{children:"finish?: { executor: Callback<Record<string, never>, void> };"})]})}),"\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.jsxs)(n.strong,{children:["required: ",(0,s.jsx)(n.code,{children:"false"})]})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Called before the resources render starts."}),"\n",(0,s.jsx)(n.p,{children:"Example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"const myPlugin = () => {\n  // your plugin operations\n  let myPluginContext = createMyPluginContext();\n\n  return {\n    name: 'my-plugin',\n    finish: {\n      async executor() {\n        // update my plugin status\n        myPluginContext.updateStatus('finish');\n      }\n    }\n  }\n}\n"})}),"\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 compile. Later compiling like ",(0,s.jsx)(n.code,{children:"Lazy Compilation"})," and ",(0,s.jsx)(n.code,{children:"HMR Update"})," won't trigger ",(0,s.jsx)(n.code,{children:"finish"}),"."]})}),"\n",(0,s.jsx)(n.h3,{id:"updatemodules",children:"updateModules"}),"\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:"type:"})}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"type UpdateModulesHook = {\n  executor: Callback<\n    { paths: [string, string][] },\n    string[] | undefined | null | void\n  >;\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"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(a,{...e})}):a(e)}}}]);

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.