Js Plugin Api
Farm Js Plugin has designed a similar rollup style design plugin system and easy to migrate your plugins/projects from Rollup/Vite/Webpack.
Configuring Js Plugins
Adding JS plugins by plugins option:
Writing Js Plugins
A Farm Js Plugin is a plain javascript object which exposes a set of hooks. for example:
- Farm provided
create-farm-plugintool to help you create and develop you js plugin quickly. For more details about writing JS plugins, refer to Writing JS Plugins
Plugin Hook Overview
The Js plugin hook is the same as the Rust plugin, See Rust Plugin Hook Overview.
Not all hooks are exposed to Js Plugins, only hooks listed in this document are available.
hooks
name
- type:
string - required:
true
The name of this plugins, MUST not be empty.
priority
- type:
number - required:
false - default:
100
The priority of this plugins, default to 100. priority controls the execution order of plugins, the larger the value, the earlier the plugin is executed.
Note that the priority of most farm internal plugins like plugin-script, plugin-resolve is 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 priority to a value that smaller than 99, for example: 98. Also the priority value can be negative, you can set it to -9999 to make sure it is always executed at last.
config
- type:
config?: (config: UserConfig) => UserConfig | Promise<UserConfig>; - hook type:
serial - required:
false
Modify Farm config in config hook, return the (partial) 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.
Example:
config hook is called after all user plugins are resolved, so add new plugins into the config has no effect.
configResolved
- type:
configResolved?: (config: ResolvedUserConfig) => void | Promise<void>; - hook type:
serial - required:
false
Called when the config resolved(after all plugin's config hook being called). Useful when you want to get the final resolved config for your plugin.
Example:
configureDevServer
- type:
configureDevServer?: (server: Server) => void | Promise<void>; - hook type:
serial - required:
false
Note that this hook runs in development mode only.
Called when Dev Server is ready, you can get the dev server instance.
Example:
Both config and configResolved hook of js plugin are called before config hook of rust plugin.
configureCompiler
- type:
configureCompiler?: (compiler: Compiler) => void | Promise<void>; - hook type:
serial - required:
false
Called when Rust Compiler is ready, this hook runs in both development and production. You can get Compiler instance here
Example:
buildStart
- type:
buildStart?: { executor: Callback<Record<string, never>, void> }; - hook type:
parallel - required:
false
Called before the compilation starts. You can do some initialization work here.
Example:
buildStart is only called once for the first compile. Later compiling like Lazy Compilation and HMR Update won't trigger buildStart.
resolve
- required:
false - hook type:
first - type:
All filters sources and importers of resolve hook are regex string.
Custom source resolving from importer, for example, resolving ./b from a.ts:
Then the resolve params would be:
The resolve result of default resolver would be:
The HookContext is used to pass status when you can the hooks recursively, for example, your plugin call context.resolve in resolve hook:
In above example, we call context.resolve and pass caller as parameter, then we should add a guard like if (hookContext.caller === 'my-plugin') { to avoid infinite loop.
Note:
- By default, you
resolve hookare executed 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 plugin's priority larger than101. - Usually
resolved_pathis the real absolute path that points to a file. But you can still return avirtual module idlikevirtual:my-module, but for virtual module you need to implementloadhook to custom how to load your virtual module. And in Farm,resolved_path + query = module_id. ResolveKindpresents theimport type, Example values:require(imported by commonjs require),cssImport(imported by css's import statement), etc.metacan be shared between plugins and hooks, you can getmetafrom params ofload,transformandparsehooks in any plugin.
load
- required:
false - hook type:
first - type:
Custom how to load your module from a resolved module path or module id. For example, load a virtual module:
module_type and content is required when loading modules in your load hook. source_map is optional, you can return source map if you do transform in the load hook(which is not recommended, we recommend to use transform hook for this situation) or you load original source map from other locations.
filters.resolvedPath of load hook is resolvedPath + query, for example: /root/src/index.vue?vue&type=style&lang=css. If you want to ignore query when filtering modules, you can use $: src/index\\.vue$; If you want to filter modules by query, for example, filtering lang=css, you can use src/index.vue\\.+\\?vue&.+lang=css.
transform
- required:
false - hook type:
serial - type:
Do transformation based on module content and module type. Example for transforming sass to css:
Normal steps for writing transform hook:
- add a
ifguard basedmoduleTypeorresolvedPathormoduleId - do transformation of the
content - return the transformed
content,sourceMapandmoduleType
For ignorePreviousSourceMap, if you handled param.sourceMapChain and collapsed the source maps of previous plugins in the transform hook. You should set ignorePreviousSourceMap to true to ensure source map is correct. Otherwise, you should always set this option to false and leave source map chain handled by Farm.
For filters:
- When both
resolvedPathsandmoduleTypesare specified, take the union. filters.resolvedPathsisresolvedPath + query, for example:/root/src/index.vue?vue&type=style&lang=css. If you want to ignore query when filtering modules, you can use$:src/index\\.vue$; If you want to filter modules by query, for example, filteringlang=css, you can usesrc/index.vue\\.+\\?vue&.+lang=css.filters.moduleTypesis NOTregex, it must exactly match theModuleTypelikecss,js,tsx, etc.
transform hook is content to content. There is a similar hook called process_module, process_module is ast to ast. Js plugin does not support process_module hook due to performance issues, if you want ast to ast transformations, try Rust Plugin instead.
buildEnd
- type:
buildEnd?: { executor: Callback<Record<string, never>, void> }; - hook type:
parallel - required:
false
Called after the ModuleGraph built, but before the resources render and generation starts. You can do some status updating or finalization work here.
Example:
buildEnd is only called once for the first compile. Later compiling like Lazy Compilation and HMR Update won't trigger buildEnd.
renderStart
- type:
renderStart?: { executor: Callback<Config['config'], void>; }; - hook type:
parallel - required:
false
Called before the resources render starts.
Example:
renderStart is only called once for the first compile. Later compiling like Lazy Compilation and HMR Update won't trigger renderStart.
renderResourcePot
- required:
false - hook type:
serial - type:
Resource Pot is the abstract representation of the final output bundle file, you can return transformed resourcePot content to mutate the final bundle. For example, rendering css:
We transform all <--layer--> in css resource pot and replace them to real css code.
When both filters.moduleIds and filters.resourcePotTypes are specified, take the union.
augmentResourceHash
- required:
false - hook type:
serial - type:
Append resource hash for give Resource Pot. Useful if you want to add additional conditions when generating resource hash.
When both filters.moduleIds and filters.resourcePotTypes are specified, take the union.
finalizeResources
- required:
false - hook type:
serial - type:
Do some transformations for all generated resources, return transformed resourcesMap. You can add, remove, modify final generated resources in this hook.
Note:
bytesis binary of the final output, forjs/css/htmlcode, you can useBuffer.from(bytes).toString()to get the code.nameis the final file name.originrepresent where thisResourceis from,ResourcePotmeans it's generated fromResourcePotwhich is a modules bundle;Modulemeans it's fromModule, for example, static files like.png/.jpgare fromModule.
transformHtml
- required:
false - hook type:
serial - type:
The order is used to configure when to execute transformHtml hook:
0: meanspre, executed before parse and generate resources. You can transform original html in this stage.1and2: meansnormalandpost, executed after parse and generate resources. In this stage, all<script>,<link>tag are injected.
Transform the final generated html(after all <script>, <link> tag are injected).
You should modify bytes field of htmlResource and return the mutated htmlResource, mutate any other fields take no effects
writeResources
- required:
false - hook type:
serial - type:
Called AFTER all resources are written to disk.
pluginCacheLoaded
- required:
false - hook type:
serial - type:
Extend persistent cache loading for your plugin.
When Persistent Cache enabled, load and 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.
Example:
You must decide how to serialize/deserialize cache to bytes in your plugins. For a basic example, you can deserialize data by [...Buffer.from(JSON.stringify(data))]
writePluginCache
- required:
false - hook type:
serial - type:
Extend persistent cache writing for your plugin. writePluginCache is often used together with pluginCaceLoaded to read and write persistent cache for plugin. Return the serialized bytes of your data.
Example:
You must decide how to serialize/deserialize cache to bytes in your plugins. For a basic example, you can deserialize data by [...Buffer.from(JSON.stringify(data))]
finish
- type:
finish?: { executor: Callback<Record<string, never>, void> }; - hook type:
parallel - required:
false
Called before the resources render starts.
Example:
finish is only called once for the first compile. Later compiling like Lazy Compilation and HMR Update won't trigger finish.
updateModules
- required:
false - hook type:
serial - type:
Called when calling compiler.update(module_paths). Useful to do some operations like clearing previous state or ignore some files when performing HMR.
pathsis paths that will be recompiled for this update- return the new
paths, later compilation will update the new returned paths.
