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 hook
s. for example:
- Farm provided
create-farm-plugin
tool 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 hook
are 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_path
is the real absolute path that points to a file. But you can still return avirtual module id
likevirtual:my-module
, but for virtual module you need to implementload
hook to custom how to load your virtual module. And in Farm,resolved_path + query = module_id
. ResolveKind
presents theimport type
, Example values:require
(imported by commonjs require),cssImport
(imported by css's import statement), etc.meta
can be shared between plugins and hooks, you can getmeta
from params ofload
,transform
andparse
hooks 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
if
guard basedmoduleType
orresolvedPath
ormoduleId
- do transformation of the
content
- return the transformed
content
,sourceMap
andmoduleType
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
resolvedPaths
andmoduleTypes
are specified, take the union. filters.resolvedPaths
isresolvedPath + 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.moduleTypes
is NOTregex
, it must exactly match theModuleType
likecss
,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:
bytes
is binary of the final output, forjs/css/html
code, you can useBuffer.from(bytes).toString()
to get the code.name
is the final file name.origin
represent where thisResource
is from,ResourcePot
means it's generated fromResourcePot
which is a modules bundle;Module
means it's fromModule
, for example, static files like.png/.jpg
are 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.1
and2
: meansnormal
andpost
, 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.
paths
is paths that will be recompiled for this update- return the new
paths
, later compilation will update the new returned paths.