Hook List

In the Modern.js engineering system, there are three types of plugins: CLI, Runtime, and Server. Each type of plugin can utilize different Hooks.

In this chapter, all available Hooks are listed, and you can use the corresponding Hook based on your needs.

CLI Common Hooks

The following are the common CLI Hooks that can be used in both Modern.js Framework and Modern.js Module.

beforeConfig

  • Functionality: Running tasks before the config process
  • Execution phase: Before the config process
  • Hook model: AsyncWorkflow
  • Type: AsyncWorkflow<void, void>
  • Example:
import type { CliPlugin } from '@modern-js/core';

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

config

  • Functionality: Collect configuration
  • Execution phase: After parsing the configuration in modern.config.ts
  • Hook model: ParallelWorkflow
  • Type: ParallelWorkflow<void, unknown>
  • Example:
import type { CliPlugin } from '@modern-js/core';

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

If you need to set the configuration of the Modern.js Framework, please use the CliPlugin<AppTools> type exported by @modern-js/app-tools:

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

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

The configuration returned by the config hook will be collected and merged by Modern.js, resulting in a NormalizedConfig. When merging configurations, the priority is as follows, from high to low:

  1. Configuration defined by the user in the modern.config.* file.
  2. Configuration defined by the plugin through the config hook.
  3. Default configuration of Modern.js.

prepare

  • Functionality: Preparatory process for running the main process.
  • Execution phase: After configuration validation.
  • Hook model: AsyncWorkflow
  • Type: AsyncWorkflow<void, void>
  • Example:
import type { CliPlugin } from '@modern-js/core';

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

afterPrepare

  • function: Running tasks after the prepare process
  • Execution Phase: After the prepare process
  • Hook model: AsyncWorkflow
  • type: AsyncWorkflow<void, void>
  • Usage:
import type { CliPlugin } from '@modern-js/core';

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

commands

  • Functionality: Add new CLI commands for the commander.
  • Execution phase: After the prepare Hook has run.
  • Hook model: AsyncWorkflow
  • Type: AsyncWorkflow<{ program: Command; }, void>
  • Example:
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');
        });
      },
    };
  },
});

Move the plugin to modern.config.ts:

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

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

run modern foo:

$ modern foo
foo

beforeExit

  • Functionality: Reset some file states before exiting the process.
  • Execution phase: Before the process exits.
  • Hook model: Workflow
  • Type: Workflow<void, void>
  • Example:
import type { CliPlugin } from '@modern-js/core';

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

Since the callback function when exiting the process in Node.js is synchronous, the type of beforeExit Hook is Workflow and cannot perform asynchronous operations.

CLI Framework Hooks

The following are the CLI Hooks of the framework, which can only be used in Modern.js Framework and cannot be used in Modern.js Module.

You need to import the CliPlugin and AppTools types from @modern-js/app-tools to get accurate type hints for Hooks.

beforeDev

  • Functionality: Tasks before running the main dev process.
  • Execution phase: Before the project starts when the dev command is run.
  • Hook model: AsyncWorkflow
  • Type: AsyncWorkflow<void, unknown>
  • Example:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

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

afterDev

  • Function: Tasks to be executed after the main process of dev command
  • Execution stage: It is executed after each compilation is completed when running the dev command
  • Hook model: AsyncWorkflow
  • Type: AsyncWorkflow<{ isFirstCompile: boolean }, unknown>
  • Example:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

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

afterDev will be executed after each compilation is completed, you can use the isFirstCompile param to determine whether it is the first compilation:

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

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

beforeBuild

  • Function: A callback function triggered before executing production environment builds. You can access the final configuration array of the underlying bundler through the bundlerConfigs parameter.
    • If the current bundler is webpack, you will get the webpack Compiler object.
    • If the current bundler is Rspack, you will get the Rspack Compiler object.
    • The configuration array may contain one or multiple configurations, depending on whether you have enabled features such as SSR.
  • Execution stage: Executed after running the build command and before the actual build process begins.
  • Hook model: AsyncWorkflow.
  • Type:
type BeforeBuild = AsyncWorkflow<{
  bundlerConfigs: WebpackConfig[] | RspackConfig[];
}>;
  • Example:
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

  • Function: A callback function triggered after running the production build. You can access the build result information through the stats parameter.
    • If the current bundler is webpack, you will get webpack Stats.
    • If the current bundler is Rspack, you will get Rspack Stats.
  • Execution stage: It is executed after running the build command and completing the build.
  • Hook model: AsyncWorkflow.
  • Type:
type AfterBuild = AsyncWorkflow<{
  stats?: Stats | MultiStats;
}>;
  • Example:
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

  • Function: A callback function triggered before creating the underlying Compiler instance, and you can get the final configuration array of the underlying bundler through the bundlerConfigs parameter:
    • If the current bundler is webpack, you will get the webpack Compiler object.
    • If the current bundler is Rspack, you will get the Rspack Compiler object.
    • The configuration array may contain one or multiple configurations, depending on whether you have enabled features such as SSR.
  • Execution stage: Executed before creating the Compiler instance when running the dev or build command.
  • Hook model: AsyncWorkflow.
  • Type:
type BeforeCreateCompiler = AsyncWorkflow<
  { bundlerConfigs: Configuration[] },
  unknown
>;
  • Example:
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

  • Function: A callback function triggered after creating a Compiler instance and before executing the build. You can access the Compiler instance object through the compiler parameter:
    • If the current bundler is webpack, you will get the webpack Compiler object.
    • If the current bundler is Rspack, you will get the Rspack Compiler object.
  • Execution stage: Executed after creating the Compiler object.
  • Hook model: AsyncWorkflow.
  • Type:
type AfterCreateCompiler = AsyncWorkflow<
  { compiler: Compiler | MultiCompiler | undefined },
  unknown
>;
  • Example:
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

  • Function: Provides access to the log information that will be printed within middleware functions and allows modification of the log information.
  • Execution stage: Executed before printing the log information.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ instructions: string }>
  • Example:
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

  • Function: Used for modifying or adding import statements in the generated entry files.
  • Execution stage: Executed before generating the entry files, triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ imports: ImportStatement[]; entrypoint: Entrypoint; }>
  • Example:
import type { CliPlugin, AppTools } from '@modern-js/app-tools';

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

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

modifyEntryExport

  • Function: used to modify the export statement in the generated entry file
  • Execution stage: Before the entry file is generated, the prepare phase triggers
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ entrypoint: Entrypoint; exportStatement: string; }>
  • Example:
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

  • Function: Used for adding or modifying Runtime plugins in the generated entry files.
  • Execution stage: Executed before generating the entry files, triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ entrypoint: Entrypoint; plugins: RuntimePlugin[]; }>
  • Example:
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 = {
          /** serializable content */
        };

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

modifyEntryRenderFunction

  • Function: Used for modifying the render function in the generated entry files.
  • Execution stage: Executed before generating the entry files, triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ entrypoint: Entrypoint; code: string; }>
  • Example:
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

  • Function: Used for modifying the content of the generated front-end page routing files, which must be serializable.
  • Execution stage: Executed before generating the front-end routing files, triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ entrypoint: Entrypoint; routes: Route[]; }>
  • Example:
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,
            },
          ],
        };
      },
    };
  },
});

This adds a new page route for the front-end.

modifyServerRoutes

  • Function: Used for modifying the content of the generated server routes.
  • Execution stage: Executed before generating the server routing files, triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ routes: ServerRoute[]; }>
  • Example:
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

  • Function: Used for modifying the asynchronous module that wraps the entry file, see source.enableAsyncEntry.
  • Execution stage: Executed before generating the entry files, triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ entrypoint: Entrypoint; code: string; }>
  • Example:
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

  • Function: Used for customizing the generated HTML page template.
  • Execution stage: Triggered during the prepare stage.
  • Hook model: AsyncWaterfall
  • Type: AsyncWaterfall<{ entrypoint: Entrypoint; partials: HtmlPartials; }>
  • Example:
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,
        };
      },
    };
  },
});

This adds a new Script tag to the HTML template.

Runtime

NOTE

The Runtime plugin is currently not fully opened, and the API is not guaranteed to be stable. Use with caution.

The Runtime plugin is mainly used for developers to modify the component that need to be rendered.

init

  • Function: Executes App.init.
  • Execution stage: Rendering (SSR/CSR).
  • Hook model: AsyncPipeline
  • Type: AsyncPipeline<{ context: RuntimeContext; }, unknown>
  • Example:
import type { Plugin } from '@modern-js/runtime';

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

hoc

  • Function: Modifies the components that need to be rendered.
  • Execution stage: Rendering (SSR/CSR).
  • Hook model: Pipeline
  • Type: Pipeline<{ App: React.ComponentType<any>; }, React.ComponentType<any>>
  • Example:
NOTE

When using the hoc hook, you need to copy the static properties of the original App component to the new component and pass through the 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),
        });
      },
    };
  },
});