Rust Plugin Api
This document only covers the details of the plugin hooks. For how to create, build and publish a rust plugin see: Writing Rust Plugins
Configuring Rust Plugins
Adding Rust plugins by plugins
option:
Configuring the Rust plugin package name(or path) in string and its options in object.
Writing Rust Plugin
See Writing Rust Plugins for details.
Plugin Hooks Overview
Farm provides a lot of rollup-style hooks, these hooks are divided into build stage and generate stage:
All plugin hooks accept a parameter called CompilationContext
. All of the shared compilation info are stored in the context
.
There are three kinds of hooks (the same as Rollup):
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).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.parallel
: The hooks execute in parallel in a thread pool and should be isolated.
For full Plugin Hooks
signature, see Plugin Trait
name
- required:
true
- default:
Returns the name of this plugin. Example:
priority
- required:
false
- default:
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 plugins
.
By default, all custom plugin's priority is 100. And some internal plugins' order is 99, like plugin-script
, plugin-css
, you can override the internal plugin's behavior when default priority. But some internal plugins' priority is 101, like plugin-resolve
, plugin-html
, you should setup a larger priority if you want override the default behavior.
config
- required:
false
- hook type:
serial
- default:
Modify the config before compilation start in config
hook. Refer to Config for definition of Config struct. Example:
Note that the Rust Plugin
's config
hook are called after JS Plugin
's config
and configResolved
hook.
plugin_cache_loaded
- required:
false
- hook type:
serial
- default:
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:
Note:
deserialize
is exposed byfarmfe_core
, it can help you deserialize your structs or enums fromVec<u8>
.- The cached structs or enums must be rkyv serializable, you can use
#[cache_item]
exposed byfarmfe_core
create a cacheable struct quickly.
build_start
- required:
false
- hook type:
parallel
- default:
Called before the first compilation starts. You can use this hook to initialize any initial status of your plugins.
build_start
is only called once for the first compilation. If you want to do something when ModuleGraph is updated in HMR
or Lazy Compilation
, you should use update_modules hook.
resolve
- required:
false
- hook type:
first
- default:
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.plugin_driver.resolve
in resolve hook
:
In above example, we call context.plugin_driver.resolve
and pass caller
as parameter, then we should add a guard like if caller.as_str() == "FarmPluginCss"
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:ResolveKind::Require
(imported by commonjs require),ResolveKind::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
- default:
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.
transform
- required:
false
- hook type:
serial
- default:
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 basedmodule_type
orresolved_path
ormodule_id
- do transformation of the
content
- return the transformed
content
,source_map
andmodule_type
For ignore_previous_source_map
, if you handled param.source_map_chain
and collapsed the source maps of previous plugins in the transform hook
. You should set ignore_previous_source_map 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.
transform
hook is content to content. There is a similar hook called process_module
, process_module
is ast to ast. So if you want to transform the loaded content string, you need to use transform
hook, and if you want to transform the ast
, you should use process_module
hook.
parse
- required:
false
- hook type:
first
- default:
Parse the transformed module content
to ast
. Js/Jsx/Ts/Tsx
, css
and html
are supported natively by Farm. Normally you do not need to implement this hook unless you want to support a new module_type
other than Js/Jsx/Ts/Tsx
, css
and html
, use ModuleMetaData::Custom
for this scenario.
process_module
- required:
false
- hook type:
serial
- default:
Do transformation of the parsed result
, usually do ast transformation
. For example, Farm strip typescript in process_module
hook:
In above example, we ignore non-script modules and strip type annotation of the ast for ts/tsx modules.
analyze_deps
- required:
false
- hook type:
serial
- default:
Analyze dependencies of the module. For example, we have a.ts
:
then normally this hook should push 2 entries to params.deps
:
param.deps
will be passed to resolve
hook later. You can also add new deps that is not related to the ast of your module as you wish, Farm will resolve
, load
these unrelated modules and add them to the module graph too.
finalize_modules
- required:
false
- hook type:
serial
- default:
Do any thing you want before seal the module. Note that you can only modify param.module
.
build_end
- required:
false
- hook type:
parallel
- default:
Called when all dependencies starting from config.input
are handled and ModuleGraph
is successfully constructed, you can get the full resolved ModuleGraph
here by context.module_graph
.
build_end
is only called once for the first compilation. If you want to do something when ModuleGraph is updated in HMR
or Lazy Compilation
, you should use module_graph_updated hook.
generate_start
- required:
false
- hook type:
parallel
- default:
Called before generate stage start.
optimize_module_graph
- required:
false
- hook type:
serial
- default:
You can do optimization of the module_graph
here. For internal plugins, Farm does tree shaking, minification in this hook.
analyze_module_graph
- required:
false
- hook type:
first
- default:
Analyze dynamic import
of the module_graph
, and groups modules based on dynamic import
, return the grouped modules.
Normally you should not implement this hook, unless you want to implement a full new bundling algorithm in Farm.
partial_bundling
- required:
false
- hook type:
first
- default:
Bundle the modules
to Vec<ResourcePot>
based on module_group_graph
and module_graph
. A ResourcePot
is a structure that Farm uses to hold bundled modules, it will be emitted to final resources in generate_resources hook, you can treat a ResourcePot
as Chunk
of other tools.
Note:
- This hook will be called in both
first compilation
,HMR
andLazy Compilation
, make sure this hook does not contains side effects(for the same modules, always returns the sameVec<ResourcePot>
). - You should set
module.resource_pot
in this hook.
Refer to the internal implementation of partial bundling in Farm for best practice. Refer to RFC-003 Partial Bundling for how Farm designs bundling.
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, config.partial_bundling
may not work unless you follow the same bundling spec as Farm.
process_resource_pots
- required:
false
- hook type:
serial
- default:
Do some transformation of the ResourcePots
. Note that ResourcePots are not rendered at this time, which means you can not get the rendered code of the Resource Pot
, instead, you can only add, remove, transform the modules inside the ResourcePot
render_start
- required:
false
- hook type:
serial
- default:
Called before resource pots render. After rendering resource pots, executable html
, css
, js
, etc files will be emitted.
render_start
is only called once for the first compilation. HMR
or Lazy Compilation
won't trigger render_start
hook.
render_resource_pot_modules
- required:
false
- hook type:
first
- default:
Render the given ResourcePot
to rendered_content
and rendered_source_map_chain
. This hook is used to render module's ast
to bundled code. If you just want to modify the bundled code, use render_resource_pot instead.
If you really need to use this hook, refer to plugin_runtime for best practice.
Normally you should not override this hook for natively supported module types like 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 support custom module types.
render_resource_pot
- required:
false
- hook type:
serial
- default:
Transform the rendered bundled code for the given ResourcePot
. Return rendered content
and source map
.
In above example, we transformed the content of a css Resource Pot
, replaced all <--layer-->
to replaced_code
.
augment_resource_hash
- required:
false
- hook type:
serial
- default:
Append additional hash when generating resource from given resource pot.
optimize_resource_pot
- required:
false
- hook type:
serial
- default:
Do some optimizations for the rendered resource pot. For example, minification. If you want to modify the rendered content of this hook, just modify resource_pot.meta.rendered_content
and append sourcemap of this transformation in resource_pot.meta.rendered_source_map_chain
.
Optimizations like minification is handled internally by Farm, make sure that you really need to use this hook.
generate_resources
- required:
false
- hook type:
first
- default:
Generate final resource for the give rendered resource pot. return generated resource
and optional source map resource
.
For natively supported ModuleTypes
like 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.
finalize_resources
- required:
false
- hook type:
serial
- default:
Do some finalization work on the generated resources, for example, transform html based on the generated resources(insert <script>
, <link>
tags).
You can also add
or remove
resources here.
generate_end
- required:
false
- hook type:
parallel
- default:
Called when all generate stage done(including finalize_resources
). You can do some cleanup work here.
finish
- required:
false
- hook type:
parallel
- default:
Called when all compilation work done(including build stage
and generate stage
). You can do some cleanup work here.
finish
is only called once for the first compilation. HMR
or Lazy Compilation
won't trigger finish
hook. You should use update_finished hook instead.
write_plugin_cache
- required:
false
- hook type:
serial
- default:
Extend persistent cache
writing for your plugin. write_plugin_cache
is often used together with plugin_cache_loaded to read and write persistent cache for plugin. Return the serialized bytes of your data.
Example, writing cache for static assets:
update_modules
- required:
false
- hook type:
serial
- default:
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.
module_graph_updated
- required:
false
- hook type:
serial
- default:
Called when calling compiler.update(module_paths). Useful to do some operations like modifying the module graph.
update_finished
- required:
false
- hook type:
serial
- default:
Called when calling compiler.update(module_paths). This hook is called after all compilation work is done, including the resources regeneration and finalization.
handle_persistent_cached_module
- required:
false
- hook type:
serial
- default:
Called when persistent cache is enabled and the cache hit for the module. Return true
to skip loading cache for this module.