Writing JavaScript Plugins
A JavaScript plugin is simply a pure JavaScript object that defines a set of property hooks:
// farm.config.ts
import { defineConfig } from "@farmfe/core";
export default defineConfig({
// ...
plugins: [
// a plugin object
{
name: "my-resolve-plugin",
priority: 1000, // the priority of this plugin, the larger the value, the earlier the execution. Normally internal plugins is 100.
resolve: {
filters: {
// Only execute the hook when following conditions satisfied
sources: ["\\./index.ts"], // a regex array
importers: ["None"],
},
executor: async (param) => {
// this hook executor
console.log(param); // resolve params
// return the resolve result
return {
resolvedPath: "virtual:my-module",
query: [],
sideEffects: false,
external: false,
};
},
},
},
// load, transform are similar to resolve, refer to their types
],
});
If you want to pass args to your plugins,you can use a closure.
// my-resolve-plugin.ts
export function myResolvePlugin(options: Options) {
const { xx } = options;
return {
name: "my-resolve-plugin",
resolve: {
// ...
},
};
}
// farm.config.ts
import { defineConfig } from "@farmfe/core";
import { myResolvePlugin } from "./myResolvePlugin.ts";
export default defineConfig({
// ...
plugins: [myResolvePlugin({ xx: "xx" })],
});
Available Hooks (v2)
The following hooks are available in Farm v2's JsPlugin interface. See Js Plugin API for full details on each hook.
Config & Server Hooks:
config— Modify the resolved config before compilationconfigResolved— Called after the config is fully resolvedconfigureServer— Configure the dev server (add middlewares, etc.)configureCompiler— Access and configure the compiler instance
Build Hooks:
buildStart— Called when the build startsresolve— Custom module resolutionload— Custom module loadingtransform— Transform module contentprocessModule— Process a module after transformationfreezeModule— Called after a module is frozen (finalized during build)buildEnd— Called when the build ends
Generate Hooks:
renderStart— Called when resource generation startsprocessRenderedResourcePot— Process a resource pot after renderingaugmentResourcePotHash— Augment the hash of a resource pot. This is the TypeScript authoring name; the lower-level bridge still uses the historicalaugmentResourceHashname internally.finalizeResources— Finalize all resources before outputtransformHtml— Transform the generated HTMLwriteResources— Called when resources are written to disk
Cache Hooks:
pluginCacheLoaded— Called when the plugin cache is loaded from diskwritePluginCache— Called when the plugin cache is written to disk
Lifecycle Hooks:
finish— Called when the compilation is finishedupdateModules— Called during HMR to determine which modules to updateupdateFinished— Called when HMR update is finished
Conventions
For farm specific js plugins:
- The Farm Js plugin should have a name with a
farm-plugin-prefix and clear semantics. - Include the
farm-plugin-keyword in package.json.
If your plugin is only applicable to a specific framework, its name should follow the following prefix format:
farm-plugin-vue-: Prefix as a Vue pluginfarm-plugin-react-: Prefix as a React pluginfarm-plugin-svelte-: Prefix as a svelte plugin- ...
Concepts
Before you start to write your js plugin, you should know the following concepts:
- filters: Cause Js Plugins are much slower than Rust Plugins, your js plugin need to set explicit filters to avoid unnecessary call for js plugins hook. For example, you should set
transform.filters.moduleTypes = ['js']to make sure that the transform hook of your js plugin only runs for.js/mjs/cjsfiles. - module_type: The type of the module, it can be
js,ts,css,sass,json, etc. Farm supportsjs/ts/jsx/tsx,css,html,json,static assets(png, svg, etc)natively.module_typeis returned byloadhook ortransformhook. - resolved_path and module_id:
resolved_pathis the absolute path of the module, andmodule_idis the unique id of the module, it's usuallyrelative path of the module from the project root+query. For example, we import a module asimport './a?query', the resolved_path is/project/src/a.tsand the module_id issrc/a.ts?query. - context: All the hooks in the plugin accept a
contextargument, it's the compilation context of the farm project, you can use it to get the ModuleGraph, Module, Resources, etc. - Resource and Resource Pot:
Resourceis the final output bundle file, andResource Potis the abstract representation of the resource, similar toChunkof other bundlers. Inside Farm, first we will generateResource PotsfromModuleGraph, renderResource Potsand finally generateResourcesfromResource Pots.
Filters
Cause Js Plugins are much slower than Rust Plugins, Farm use filters to control the execution of js plugin hooks. The plugin hook executes only when given filters matched to improve performance. filters is necessary for some commonly used hooks, such as resolve, load, transform, etc.
For example, if you want to transform css files, you can use transform.filters.moduleTypes = ['css'] to make sure that the transform hook of your js plugin only runs for .css files:
const myCssPlugin = {
name: "my-css-plugin",
transform: {
filters: {
// Only execute the hook when following conditions satisfied
// resolvedPaths: ["\\./index.ts"], // a regex array to match the resolvedPaths
moduleTypes: ["css"],
},
executor: async (param) => {
// transform css
},
},
};
Module Type
In Farm, every thing is First Class Citizens, so Farm designs module_type to identify the type of a module and handle different kinds of ModuleTypes in different plugins.
module_type returned by load hook, and can be transformed by transform hook. Farm supports js/ts/jsx/tsx, css, html, json, static assets(png, svg, etc) natively. For these module types, you can return them directly in load or transform hook directly. JavaScript plugins can use processModule and freezeModule for later module-level processing, but custom parsing, rendering, and resource generation hooks are Rust-only.
Create Plugin
Farm provides official templates to help your create your js plugins quickly:
pnpm create farm-pluginnpm create farm-plugin@latestyarn create farm-pluginthen follow the prompts to create your plugin.
or you can create a plugin directly by running the following command:
pnpm create farm-plugin my-farm-plugin --type jsnpm create farm-plugin@latest my-farm-plugin -- --type jsyarn create farm-plugin my-farm-plugin --type jsAbove command will create new js plugin with name my-farm-plugin in the current directory. --type can be rust or js
Develop Plugin
After creating the plugin, you can start to develop your plugin. The plugin is a pure JavaScript object that defines a set of property hooks:
// import { readFileSync } from 'node:fs';
import type { JsPlugin } from '@farmfe/core';
interface Options {
/* Your options here */
}
export default function farmPlugin(options: Options): JsPlugin {
return {
name: '<FARM-JS-PLUGIN-NPM-NAME>',
/* Your plugin hooks here: */
// transform: {
// filters: {
// moduleTypes: ['js']
// },
// async executor(params) {
// const { content } = params;
// return {
// content,
// moduleType: 'js'
// };
// }
// },
// finish: {
// executor() {}
// }
};
}
Run npm run dev to compile the plugin and watch for changes. Run npm run build to build the plugin.
Publish Plugin
A js plugin package is a normal npm package, you can publish it to npm registry by running npm publish.
Farm