Hook 列表

Modern.js 工程体系中包含三类插件:CLI、Runtime、Server,每一类插件可以使用不同的 Hooks。

在本章节中,罗列了所有可用的 Hooks,你可以根据自己的需求来使用对应的 Hook。

CLI Common Hooks

以下是通用的 CLI Hooks,可以在 Modern.js Framework 以及 Modern.js Module 中使用。

beforeConfig

  • 功能:运行收集配置前的任务
  • 执行阶段:收集配置前
  • Hook 模型:AsyncWorkflow
  • 类型:AsyncWorkflow<void, void>
  • 使用示例:
import type { CliPlugin } from '@modern-js/core';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      beforeConfig: () => {
        // do something
      },
    };
  },
});

config

  • 功能:收集配置
  • 执行阶段:解析完 modern.config.ts 中的配置之后
  • Hook 模型:ParallelWorkflow
  • 类型:ParallelWorkflow<void, unknown>
  • 使用示例:
import type { CliPlugin } from '@modern-js/core';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      config: () => {
        return {
          /** some config */
        };
      },
    };
  },
});

如果你需要设置 Modern.js Framework 的配置,请使用 @modern-js/app-tools 导出的 CliPlugin<AppTools> 类型:

import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      config: () => {
        return {
          output: {
            polyfill: 'usage',
          },
        };
      },
    };
  },
});

插件在 config hook 中返回的配置信息,会被 Modern.js 统一收集与合并,最终生成一份 NormalizedConfig。在进行配置合并时,优先级由高到低依次为:

  1. 用户在 modern.config.* 文件里定义的配置
  2. 插件通过 config hook 定义的配置
  3. Modern.js 默认配置。

prepare

  • 功能:运行主流程的前置准备流程
  • 执行阶段:校验完配置之后
  • Hook 模型:AsyncWorkflow
  • 类型:AsyncWorkflow<void, void>
  • 使用示例:
import type { CliPlugin } from '@modern-js/core';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      prepare: () => {
        // do something
      },
    };
  },
});

afterPrepare

  • 功能:运行前置准备流程的之后的任务
  • 执行阶段:前置准备流程之后
  • Hook 模型:AsyncWorkflow
  • 类型:AsyncWorkflow<void, void>
  • 使用示例:
import type { CliPlugin } from '@modern-js/core';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      afterPrepare: () => {
        // do something
      },
    };
  },
});

commands

  • 功能:为 commander 添加新的 CLI 命令
  • 执行阶段:prepare Hook 运行完成之后
  • Hook 模型:AsyncWorkflow
  • 类型:AsyncWorkflow<{ program: Command; }, void>
  • 使用示例:
import type { CliPlugin } from '@modern-js/core';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      commands: ({ program }) => {
        program.command('foo').action(async () => {
          // do something
          console.log('foo');
        });
      },
    };
  },
});

将上面这个插件添加到 modern.config.ts 中:

modern.config.ts
import myPlugin from './config/plugin/myPlugin';

export const myPlugin = defineConfig({
  plugins: [myPlugin()],
});

运行 modern foo 就可以看到控制台输出:

$ modern foo
foo

beforeExit

  • 功能:在退出进程前,重置一些文件状态
  • 执行阶段:进程退出之前
  • Hook 模型:Workflow
  • 类型:Workflow<void, void>
  • 使用示例:
import type { CliPlugin } from '@modern-js/core';

export const myPlugin = (): CliPlugin => ({
  setup(api) {
    return {
      beforeExit: () => {
        // do something
      },
    };
  },
});
TIP

由于 Node.js 中退出进程时的回调函数是同步的,所以 beforeExit Hook 的类型是 Workflow,不能执行异步操作。

CLI Framework Hooks

以下是框架的 CLI Hooks,只能在 Modern.js Framework 中使用,不能在 Modern.js Module 中使用。

你需要从 @modern-js/app-tools 中导入 CliPluginAppTools 类型,以获得准确的 Hooks 类型提示。

beforeDev

  • 功能:运行 dev 主流程的之前的任务
  • 执行阶段:dev 命令运行时,项目开始启动前执行
  • Hook 模型:AsyncWorkflow
  • 类型:AsyncWorkflow<void, unknown>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      beforeDev: () => {
        // do something
      },
    };
  },
});

afterDev

  • 功能:运行 dev 主流程的之后的任务
  • 执行阶段:运行 dev 命令时,每一次编译完成后执行
  • Hook 模型:AsyncWorkflow
  • 类型:AsyncWorkflow<{ isFirstCompile: boolean }, unknown>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      afterDev: () => {
        // do something
      },
    };
  },
});

afterDev 会在每一次编译完成后执行,你可以通过 isFirstCompile 参数来判断是否为首次编译:

import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      afterDev: ({ isFirstCompile }) => {
        if (isFirstCompile) {
          // do something
        }
      },
    };
  },
});

beforeBuild

  • 功能:在执行生产环境构建前触发的回调函数,你可以通过 bundlerConfigs 参数获取到底层打包工具的最终配置数组。
    • 如果当前打包工具为 webpack,则获取到的是 webpack 配置数组。
    • 如果当前打包工具为 Rspack,则获取到的是 Rspack 配置数组。
    • 配置数组中可能包含一份或多份配置,这取决于你是否开启了 SSR 等功能。
  • 执行阶段:运行 build 命令后,在开始构建前执行
  • Hook 模型:AsyncWorkflow
  • 类型:
type BeforeBuild = AsyncWorkflow<{
  bundlerConfigs: WebpackConfig[] | RspackConfig[];
}>;
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      beforeBuild: ({ bundlerConfigs }) => {
        console.log('Before build.');
        console.log(bundlerConfigs);
      },
    };
  },
});

afterBuild

  • 功能:在执行生产环境构建后触发的回调函数,你可以通过 stats 参数获取到构建结果信息。
    • 如果当前打包工具为 webpack,则获取到的是 webpack Stats。
    • 如果当前打包工具为 Rspack,则获取到的是 Rspack Stats。
  • 执行阶段:运行 build 命令运行后,在项目构建完成之后执行
  • Hook 模型:AsyncWorkflow
  • 类型:
type AfterBuild = AsyncWorkflow<{
  stats?: Stats | MultiStats;
}>;
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      afterBuild: ({ stats }) => {
        console.log('After build.');
        console.log(stats);
      },
    };
  },
});

beforeCreateCompiler

  • 功能:在创建底层 Compiler 实例前触发的回调函数,并且你可以通过 bundlerConfigs 参数获取到底层打包工具的最终配置数组:
    • 如果当前打包工具为 webpack,则获取到的是 webpack 配置数组。
    • 如果当前打包工具为 Rspack,则获取到的是 Rspack 配置数组。
    • 配置数组中可能包含一份或多份配置,这取决于你是否开启了 SSR 等功能。
  • 执行阶段:在执行 dev 或 build 命令时,创建 Compiler 实例之前执行
  • Hook 模型:AsyncWorkflow
  • 类型:
type BeforeCreateCompiler = AsyncWorkflow<
  { bundlerConfigs: Configuration[] },
  unknown
>;
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      beforeCreateCompiler: ({ bundlerConfigs }) => {
        console.log('Before create compiler.');
        console.log(bundlerConfigs);
      },
    };
  },
});

afterCreateCompiler

  • 功能:在创建 Compiler 实例后、执行构建前触发的回调函数,并且你可以通过 compiler 参数获取到 Compiler 实例对象:
    • 如果当前打包工具为 webpack,则获取到的是 webpack Compiler 对象。
    • 如果当前打包工具为 Rspack,则获取到的是 Rspack Compiler 对象。
  • 执行阶段:创建 Compiler 对象之后执行
  • Hook 模型:AsyncWorkflow
  • 类型:
type AfterCreateCompiler = AsyncWorkflow<
  { compiler: Compiler | MultiCompiler | undefined },
  unknown
>;
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      afterCreateCompiler: ({ compiler }) => {
        console.log('After create compiler.');
        console.log(compiler);
      },
    };
  },
});

beforePrintInstructions

  • 功能:在中间件函数中可以拿到即将打印的日志信息,并对其进行修改
  • 执行阶段:打印日志信息之前执行
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ instructions: string }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      beforePrintInstructions: ({ instructions }) => {
        // do something
        return {
          instructions: [...instructions, 'some new message'],
        };
      },
    };
  },
});

modifyEntryImports

  • 功能:用于修改、添加生成入口文件中的 import 语句
  • 执行阶段:生成入口文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ imports: ImportStatement[]; entrypoint: Entrypoint; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyEntryImports({ entrypoint, imports }) {
        // 添加 `import React from 'React'`
        imports.push({
          value: 'react',
          specifiers: [
            {
              imported: 'unmountComponentAtNode',
            },
          ],
        });

        return { entrypoint, imports };
      },
    };
  },
});

modifyEntryExport

  • 功能:用于修改生成入口文件中的 export 语句
  • 执行阶段:生成入口文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ entrypoint: Entrypoint; exportStatement: string; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyEntryExport({ entrypoint, exportStatement }) {
        return {
          entrypoint,
          exportStatement: [`export const foo = 'test'`, exportStatement].join(
            '\n',
          ),
        };
      },
    };
  },
});

modifyEntryRuntimePlugins

  • 功能:用于添加、修改生成入口文件中的 Runtime 插件
  • 执行阶段:生成入口文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ entrypoint: Entrypoint; plugins: RuntimePlugin[]; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyEntryRuntimePlugins({ entrypoint, plugins }) {
        const name = 'customPlugin';
        const options = {
          /** 可序列化的内容 */
        };

        return {
          plugins: [
            ...plugins,
            {
              name,
              options: JSON.stringify(options),
            },
          ],
        };
      },
    };
  },
});

modifyEntryRenderFunction

  • 功能:用于修改生成入口文件中 render 函数
  • 执行阶段:生成入口文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ entrypoint: Entrypoint; code: string; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyEntryRenderFunction({ entrypoint, code }) {
        const customRender = `/** render function body */`;
        return {
          entrypoint,
          code: customRender,
        };
      },
    };
  },
});

modifyFileSystemRoutes

  • 功能:用于修改生成前端页面路由文件中的内容,内容都是需要可序列化的
  • 执行阶段:生成前端路由文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ entrypoint: Entrypoint; routes: Route[]; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyFileSystemRoutes({ entrypoint, routes }) {
        return {
          entrypoint,
          routes: [
            ...routes,
            {
              path: '/custom_page',
              component: require.resolve('./Component'),
              exact: true,
            },
          ],
        };
      },
    };
  },
});

这样就为前端新增了一个页面路由。

modifyServerRoutes

  • 功能:用于修改生成服务器路由中的内容
  • 执行阶段:生成 Server 路由文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ routes: ServerRoute[]; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyServerRoutes({ routes }) {
        return {
          routes: [
            ...routes,
            {
              urlPath: '/api/foo',
              isApi: true,
              entryPath: '',
              isSPA: false,
              isSSR: false,
            },
          ],
        };
      },
    };
  },
});

modifyAsyncEntry

  • 功能:用于修改包裹入口文件的异步模块,参见 source.enableAsyncEntry
  • 执行阶段:生成入口文件之前,prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ entrypoint: Entrypoint; code: string; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      modifyAsyncEntry({ entrypoint, code }) {
        const customCode = `console.log('hello');`;
        return {
          entrypoint,
          code: `${customCode}${code}`,
        };
      },
    };
  },
});

htmlPartials

  • 功能:用于定制生成的 HTML 页面模版
  • 执行阶段:prepare 阶段触发
  • Hook 模型:AsyncWaterfall
  • 类型:AsyncWaterfall<{ entrypoint: Entrypoint; partials: HtmlPartials; }>
  • 使用示例:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

export const myPlugin = (): CliPlugin<AppTools> => ({
  setup(api) {
    return {
      async htmlPartials({ entrypoint, partials }) {
        partials.head.push('<script>console.log("test")</script>');
        return {
          entrypoint,
          partials,
        };
      },
    };
  },
});

这样就为 HTML 模版中新增了一个 Script 标签。

Runtime

NOTE

目前 Runtime 插件还未完全开放,API 不保证稳定,使用需谨慎。

Runtime 插件主要用于开发者修改需要渲染的组件。

init

  • 功能:执行 App.init
  • 执行阶段:渲染(SSR/CSR)
  • Hook 模型:AsyncPipeline
  • 类型:AsyncPipeline<{ context: RuntimeContext; }, unknown>
  • 使用示例:
import type { Plugin } from '@modern-js/runtime';

export const myPlugin = (): Plugin => ({
  setup(api) {
    return {
      init({ context }, next) {
        // do something
        return next({ context });
      },
    };
  },
});

hoc

  • 功能:修改需要渲染的组件
  • 执行阶段:渲染(SSR/CSR)
  • Hook 模型:Pipeline
  • 类型:Pipeline<{ App: React.ComponentType<any>; }, React.ComponentType<any>>
  • 使用示例:
NOTE

使用 hoc 钩子时,需要把原来的 App 组件的静态属性拷贝到新的组件上,并透传 props.

import { createContext } from 'react';
import type { Plugin } from '@modern-js/runtime';
import hoistNonReactStatics from 'hoist-non-react-statics';

export const myPlugin = (): Plugin => ({
  setup(api) {
    const FooContext = createContext('');
    return {
      hoc({ App }, next) {
        const AppWrapper = (props: any) => {
          return (
            <FooContext.Provider store={'test'}>
              <App {...props} />
            </FooContext.Provider>
          );
        };
        return next({
          App: hoistNonReactStatics(AppWrapper, App),
        });
      },
    };
  },
});