diff --git a/e2e/cases/plugin-api/plugin-hooks/environments.test.ts b/e2e/cases/plugin-api/plugin-hooks/environments.test.ts new file mode 100644 index 0000000000..9f18b7b700 --- /dev/null +++ b/e2e/cases/plugin-api/plugin-hooks/environments.test.ts @@ -0,0 +1,192 @@ +import { getRandomPort, gotoPage, rspackOnlyTest } from '@e2e/helper'; +import { expect } from '@playwright/test'; +import { type RsbuildPlugin, createRsbuild } from '@rsbuild/core'; + +const createPlugin = () => { + const names: string[] = []; + + const plugin: RsbuildPlugin = { + name: 'test-plugin', + setup(api) { + api.modifyRspackConfig((_config, { environment }) => { + names.push(`ModifyBundlerConfig ${environment.name}`); + }); + api.modifyWebpackChain((_config, { environment }) => { + names.push(`ModifyBundlerConfig ${environment.name}`); + }); + api.modifyRsbuildConfig(() => { + names.push('ModifyRsbuildConfig'); + }); + api.modifyEnvironmentConfig((_config, { name }) => { + names.push(`ModifyEnvironmentConfig ${name}`); + }); + api.modifyBundlerChain((_chain, { environment }) => { + names.push(`ModifyBundlerChain ${environment.name}`); + }); + api.modifyHTMLTags((tags, { environment }) => { + names.push(`ModifyHTMLTags ${environment.name}`); + return tags; + }); + api.onBeforeStartDevServer(() => { + names.push('BeforeStartDevServer'); + }); + api.onAfterStartDevServer(() => { + names.push('AfterStartDevServer'); + }); + api.onBeforeCreateCompiler(() => { + names.push('BeforeCreateCompiler'); + }); + api.onAfterCreateCompiler(() => { + names.push('AfterCreateCompiler'); + }); + api.onBeforeBuild(() => { + names.push('BeforeBuild'); + }); + api.onAfterBuild(() => { + names.push('AfterBuild'); + }); + api.onBeforeEnvironmentCompile(({ environment }) => { + names.push(`BeforeEnvironmentCompile ${environment.name}`); + }); + api.onAfterEnvironmentCompile(({ stats, environment }) => { + expect(stats?.compilation.name).toBe(environment.name); + names.push(`AfterEnvironmentCompile ${environment.name}`); + }); + api.onBeforeStartProdServer(() => { + names.push('BeforeStartProdServer'); + }); + api.onCloseDevServer(() => { + names.push('OnCloseDevServer'); + }); + api.onAfterStartProdServer(() => { + names.push('AfterStartProdServer'); + }); + api.onDevCompileDone(() => { + names.push('OnDevCompileDone'); + }); + }, + }; + + return { plugin, names }; +}; + +rspackOnlyTest( + 'should run plugin hooks correctly when running build with multiple environments', + async () => { + const { plugin, names } = createPlugin(); + const rsbuild = await createRsbuild({ + cwd: __dirname, + rsbuildConfig: { + plugins: [plugin], + environments: { + web: {}, + node: {}, + }, + }, + }); + + await rsbuild.build(); + + // Test environment hook is always called twice + expect(names.filter((name) => name.includes(' web')).length).toBe( + names.filter((name) => name.includes(' node')).length, + ); + + // The execution order between different Environments of the same hook is not fixed + // Therefore, we only test the execution order of a single Environment + expect(names.filter((name) => !name.includes(' node'))).toEqual([ + 'ModifyRsbuildConfig', + 'ModifyEnvironmentConfig web', + 'ModifyBundlerChain web', + 'ModifyBundlerConfig web', + 'BeforeCreateCompiler', + 'AfterCreateCompiler', + 'BeforeEnvironmentCompile web', + 'BeforeBuild', + 'ModifyHTMLTags web', + 'AfterEnvironmentCompile web', + 'AfterBuild', + ]); + + expect(names.filter((name) => !name.includes(' web'))).toEqual([ + 'ModifyRsbuildConfig', + 'ModifyEnvironmentConfig node', + 'ModifyBundlerChain node', + 'ModifyBundlerConfig node', + 'BeforeCreateCompiler', + 'AfterCreateCompiler', + 'BeforeEnvironmentCompile node', + 'BeforeBuild', + 'ModifyHTMLTags node', + 'AfterEnvironmentCompile node', + 'AfterBuild', + ]); + }, +); + +rspackOnlyTest( + 'should run plugin hooks correctly when running startDevServer with multiple environments', + async ({ page }) => { + process.env.NODE_ENV = 'development'; + const port = await getRandomPort(); + + const { plugin, names } = createPlugin(); + const rsbuild = await createRsbuild({ + cwd: __dirname, + rsbuildConfig: { + plugins: [plugin], + server: { + port, + }, + environments: { + web: {}, + node: {}, + }, + }, + }); + + const result = await rsbuild.startDevServer(); + + await gotoPage(page, result); + + await result.server.close(); + + expect(names.filter((name) => name.includes(' web')).length).toBe( + names.filter((name) => name.includes(' node')).length, + ); + + expect(names.filter((name) => !name.includes(' node'))).toEqual([ + 'ModifyRsbuildConfig', + 'ModifyEnvironmentConfig web', + 'BeforeStartDevServer', + 'ModifyBundlerChain web', + 'ModifyBundlerConfig web', + 'BeforeCreateCompiler', + 'AfterCreateCompiler', + 'BeforeEnvironmentCompile web', + 'AfterStartDevServer', + 'ModifyHTMLTags web', + 'AfterEnvironmentCompile web', + 'OnDevCompileDone', + 'OnCloseDevServer', + ]); + + expect(names.filter((name) => !name.includes(' web'))).toEqual([ + 'ModifyRsbuildConfig', + 'ModifyEnvironmentConfig node', + 'BeforeStartDevServer', + 'ModifyBundlerChain node', + 'ModifyBundlerConfig node', + 'BeforeCreateCompiler', + 'AfterCreateCompiler', + 'BeforeEnvironmentCompile node', + 'AfterStartDevServer', + 'ModifyHTMLTags node', + 'AfterEnvironmentCompile node', + 'OnDevCompileDone', + 'OnCloseDevServer', + ]); + + process.env.NODE_ENV = 'test'; + }, +); diff --git a/e2e/cases/plugin-api/plugin-hooks/index.test.ts b/e2e/cases/plugin-api/plugin-hooks/index.test.ts index 39f495d7f8..b380038b74 100644 --- a/e2e/cases/plugin-api/plugin-hooks/index.test.ts +++ b/e2e/cases/plugin-api/plugin-hooks/index.test.ts @@ -1,4 +1,4 @@ -import { gotoPage, rspackOnlyTest } from '@e2e/helper'; +import { getRandomPort, gotoPage, rspackOnlyTest } from '@e2e/helper'; import { expect } from '@playwright/test'; import { type RsbuildPlugin, createRsbuild } from '@rsbuild/core'; @@ -45,6 +45,12 @@ const createPlugin = () => { api.onAfterBuild(() => { names.push('AfterBuild'); }); + api.onBeforeEnvironmentCompile(() => { + names.push('BeforeEnvironmentCompile'); + }); + api.onAfterEnvironmentCompile(() => { + names.push('AfterEnvironmentCompile'); + }); api.onBeforeStartProdServer(() => { names.push('BeforeStartProdServer'); }); @@ -83,8 +89,10 @@ rspackOnlyTest( 'ModifyBundlerConfig', 'BeforeCreateCompiler', 'AfterCreateCompiler', + 'BeforeEnvironmentCompile', 'BeforeBuild', 'ModifyHTMLTags', + 'AfterEnvironmentCompile', 'AfterBuild', ]); }, @@ -94,12 +102,16 @@ rspackOnlyTest( 'should run plugin hooks correctly when running startDevServer', async ({ page }) => { process.env.NODE_ENV = 'development'; + const port = await getRandomPort(); const { plugin, names } = createPlugin(); const rsbuild = await createRsbuild({ cwd: __dirname, rsbuildConfig: { plugins: [plugin], + server: { + port, + }, }, }); @@ -117,8 +129,10 @@ rspackOnlyTest( 'ModifyBundlerConfig', 'BeforeCreateCompiler', 'AfterCreateCompiler', + 'BeforeEnvironmentCompile', 'AfterStartDevServer', 'ModifyHTMLTags', + 'AfterEnvironmentCompile', 'OnDevCompileDone', 'OnCloseDevServer', ]); diff --git a/packages/compat/webpack/src/build.ts b/packages/compat/webpack/src/build.ts index 3ea278d813..2b088d9a0d 100644 --- a/packages/compat/webpack/src/build.ts +++ b/packages/compat/webpack/src/build.ts @@ -4,7 +4,7 @@ import type { Configuration as WebpackConfig } from 'webpack'; import WebpackMultiStats from 'webpack/lib/MultiStats.js'; import { createCompiler } from './createCompiler'; import { type InitConfigsOptions, initConfigs } from './initConfigs'; -import { onBeforeBuild, onCompileDone } from './shared'; +import { registerBuildHook } from './shared'; export const build = async ( initOptions: InitConfigsOptions, @@ -34,29 +34,13 @@ export const build = async ( bundlerConfigs = webpackConfigs; } - let isFirstCompile = true; - const beforeBuild = async () => - await context.hooks.onBeforeBuild.call({ - bundlerConfigs: bundlerConfigs as Rspack.Configuration[], - environments: context.environments, - isWatch: Boolean(watch), - isFirstCompile, - }); - - const onDone = async (stats: Rspack.Stats | Rspack.MultiStats) => { - const p = context.hooks.onAfterBuild.call({ - isFirstCompile, - stats, - environments: context.environments, - isWatch: Boolean(watch), - }); - isFirstCompile = false; - await p; - }; - - onBeforeBuild(compiler, beforeBuild, watch); - - onCompileDone(compiler, onDone, WebpackMultiStats); + registerBuildHook({ + context, + bundlerConfigs: bundlerConfigs as any, + compiler, + isWatch: Boolean(watch), + MultiStatsCtor: WebpackMultiStats, + }); if (watch) { const watching = compiler.watch({}, (err) => { diff --git a/packages/compat/webpack/src/createCompiler.ts b/packages/compat/webpack/src/createCompiler.ts index a26134e7b5..6de3f1758b 100644 --- a/packages/compat/webpack/src/createCompiler.ts +++ b/packages/compat/webpack/src/createCompiler.ts @@ -6,8 +6,8 @@ import { formatStats, getDevMiddleware, getStatsOptions, + registerDevHook, } from './shared'; -import { onCompileDone } from './shared'; import type { WebpackConfig } from './types'; export async function createCompiler({ @@ -31,7 +31,7 @@ export async function createCompiler({ | Rspack.Compiler | Rspack.MultiCompiler; - const done = async (stats: unknown) => { + const done = (stats: unknown) => { const { message, level } = formatStats( stats as Rspack.Stats, getStatsOptions(compiler), @@ -43,21 +43,20 @@ export async function createCompiler({ if (level === 'warning') { logger.warn(message); } - - if (process.env.NODE_ENV === 'development') { - await context.hooks.onDevCompileDone.call({ - isFirstCompile, - stats: stats as Rspack.Stats, - environments: context.environments, - }); - } - - isFirstCompile = false; }; - let isFirstCompile = true; + compiler.hooks.done.tap('rsbuild:done', (stats: unknown) => { + done(stats); + }); - onCompileDone(compiler, done, WebpackMultiStats); + if (process.env.NODE_ENV === 'development') { + registerDevHook({ + compiler, + context, + bundlerConfigs: webpackConfigs as any, + MultiStatsCtor: WebpackMultiStats, + }); + } await context.hooks.onAfterCreateCompiler.call({ compiler, diff --git a/packages/compat/webpack/src/shared.ts b/packages/compat/webpack/src/shared.ts index ae5cd3a525..37e1e7404d 100644 --- a/packages/compat/webpack/src/shared.ts +++ b/packages/compat/webpack/src/shared.ts @@ -12,8 +12,8 @@ const { getRsbuildInspectConfig, chainToConfig, modifyBundlerChain, - onBeforeBuild, - onCompileDone, + registerDevHook, + registerBuildHook, prettyTime, } = __internalHelper; @@ -28,8 +28,8 @@ export { outputInspectConfigFiles, chainToConfig, modifyBundlerChain, - onCompileDone, - onBeforeBuild, + registerDevHook, + registerBuildHook, prettyTime, getRsbuildInspectConfig, }; diff --git a/packages/core/src/createContext.ts b/packages/core/src/createContext.ts index bd3cb7c72a..11bdb0c37a 100644 --- a/packages/core/src/createContext.ts +++ b/packages/core/src/createContext.ts @@ -3,7 +3,7 @@ import browserslist from 'browserslist'; import { withDefaultConfig } from './config'; import { DEFAULT_BROWSERSLIST, ROOT_DIST_DIR } from './constants'; import { getCommonParentPath } from './helpers/path'; -import { initHooks } from './initHooks'; +import { initHooks } from './hooks'; import { getHTMLPathByEntry } from './initPlugins'; import { logger } from './logger'; import type { diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 7bf3d3d062..bfa49f39c4 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -565,93 +565,6 @@ export const isMultiCompiler = < return compiler.constructor.name === 'MultiCompiler'; }; -export const onBeforeBuild = ( - compiler: Rspack.Compiler | Rspack.MultiCompiler, - onBefore: () => Promise, - isWatch?: boolean, -): void => { - const name = 'rsbuild:beforeCompile'; - - if (isMultiCompiler(compiler)) { - const { compilers } = compiler; - - let doneCompilers = 0; - - for (let index = 0; index < compilers.length; index++) { - const compiler = compilers[index]; - let compilerDone = false; - - (isWatch ? compiler.hooks.watchRun : compiler.hooks.run).tapPromise( - name, - async () => { - if (!compilerDone) { - compilerDone = true; - doneCompilers++; - } - - if (doneCompilers === compilers.length) { - await onBefore(); - } - }, - ); - - compiler.hooks.invalid.tap(name, () => { - if (compilerDone) { - compilerDone = false; - doneCompilers--; - } - }); - } - } else { - (isWatch ? compiler.hooks.watchRun : compiler.hooks.run).tapPromise( - name, - onBefore, - ); - } -}; - -export const onCompileDone = ( - compiler: Rspack.Compiler | Rspack.MultiCompiler, - onDone: (stats: Rspack.Stats | Rspack.MultiStats) => Promise, - MultiStatsCtor: new (stats: Rspack.Stats[]) => Rspack.MultiStats, -): void => { - // The MultiCompiler of Rspack does not supports `done.tapPromise`, - // so we need to use the `done` hook of `MultiCompiler.compilers` to implement it. - if (isMultiCompiler(compiler)) { - const { compilers } = compiler; - const compilerStats: Rspack.Stats[] = []; - let doneCompilers = 0; - - for (let index = 0; index < compilers.length; index++) { - const compiler = compilers[index]; - const compilerIndex = index; - let compilerDone = false; - - compiler.hooks.done.tapPromise('rsbuild:done', async (stats) => { - if (!compilerDone) { - compilerDone = true; - doneCompilers++; - } - - compilerStats[compilerIndex] = stats; - - if (doneCompilers === compilers.length) { - await onDone(new MultiStatsCtor(compilerStats)); - } - }); - - compiler.hooks.invalid.tap('rsbuild:done', () => { - if (compilerDone) { - compilerDone = false; - doneCompilers--; - } - }); - } - } else { - compiler.hooks.done.tapPromise('rsbuild:done', onDone); - } -}; - export function pick( obj: T, keys: ReadonlyArray, diff --git a/packages/core/src/hooks.ts b/packages/core/src/hooks.ts new file mode 100644 index 0000000000..88312850d3 --- /dev/null +++ b/packages/core/src/hooks.ts @@ -0,0 +1,471 @@ +import { isFunction, isMultiCompiler } from './helpers'; +import { isPluginMatchEnvironment } from './pluginManager'; +import type { + AsyncHook, + EnvironmentAsyncHook, + EnvironmentContext, + HookDescriptor, + InternalContext, + ModifyBundlerChainFn, + ModifyEnvironmentConfigFn, + ModifyHTMLTagsFn, + ModifyRsbuildConfigFn, + ModifyRspackConfigFn, + ModifyWebpackChainFn, + ModifyWebpackConfigFn, + MultiStats, + OnAfterBuildFn, + OnAfterCreateCompilerFn, + OnAfterEnvironmentCompileFn, + OnAfterStartDevServerFn, + OnAfterStartProdServerFn, + OnBeforeBuildFn, + OnBeforeCreateCompilerFn, + OnBeforeEnvironmentCompile, + OnBeforeStartDevServerFn, + OnBeforeStartProdServerFn, + OnCloseDevServerFn, + OnDevCompileDoneFn, + OnExitFn, + Rspack, + RspackConfig, + Stats, +} from './types'; + +export function createEnvironmentAsyncHook< + Callback extends (...args: any[]) => any, +>(): EnvironmentAsyncHook { + type Hook = { + environment?: string; + handler: Callback; + }; + const preGroup: Hook[] = []; + const postGroup: Hook[] = []; + const defaultGroup: Hook[] = []; + + const tapEnvironment = ({ + environment, + handler: cb, + }: { + environment?: string; + handler: Callback | HookDescriptor; + }) => { + if (isFunction(cb)) { + defaultGroup.push({ + environment, + handler: cb, + }); + } else if (cb.order === 'pre') { + preGroup.push({ + environment, + handler: cb.handler, + }); + } else if (cb.order === 'post') { + postGroup.push({ + environment, + handler: cb.handler, + }); + } else { + defaultGroup.push({ + environment, + handler: cb.handler, + }); + } + }; + + const callInEnvironment = async ({ + environment, + args: params, + }: { + environment?: string; + args: Parameters; + }) => { + const callbacks = [...preGroup, ...defaultGroup, ...postGroup]; + + for (const callback of callbacks) { + // If this callback is not a global callback, the environment info should match + if ( + callback.environment && + environment && + !isPluginMatchEnvironment(callback.environment, environment) + ) { + continue; + } + + const result = await callback.handler(...params); + + if (result !== undefined) { + params[0] = result; + } + } + + return params; + }; + + return { + tapEnvironment, + tap: (handler: Callback | HookDescriptor) => + tapEnvironment({ handler }), + callInEnvironment, + }; +} + +export function createAsyncHook< + Callback extends (...args: any[]) => any, +>(): AsyncHook { + const preGroup: Callback[] = []; + const postGroup: Callback[] = []; + const defaultGroup: Callback[] = []; + + const tap = (cb: Callback | HookDescriptor) => { + if (isFunction(cb)) { + defaultGroup.push(cb); + } else if (cb.order === 'pre') { + preGroup.push(cb.handler); + } else if (cb.order === 'post') { + postGroup.push(cb.handler); + } else { + defaultGroup.push(cb.handler); + } + }; + + const call = async (...params: Parameters) => { + const callbacks = [...preGroup, ...defaultGroup, ...postGroup]; + + for (const callback of callbacks) { + const result = await callback(...params); + + if (result !== undefined) { + params[0] = result; + } + } + + return params; + }; + + return { + tap, + call, + }; +} + +export function initHooks(): { + /** The following hooks are global hooks */ + onExit: AsyncHook; + onAfterBuild: AsyncHook; + onBeforeBuild: AsyncHook; + onDevCompileDone: AsyncHook; + onCloseDevServer: AsyncHook; + onAfterStartDevServer: AsyncHook; + onBeforeStartDevServer: AsyncHook; + onAfterStartProdServer: AsyncHook; + onBeforeStartProdServer: AsyncHook; + onAfterCreateCompiler: AsyncHook; + onBeforeCreateCompiler: AsyncHook; + /** The following hooks are related to the environment */ + modifyHTMLTags: EnvironmentAsyncHook; + modifyRspackConfig: EnvironmentAsyncHook; + modifyBundlerChain: EnvironmentAsyncHook; + modifyWebpackChain: EnvironmentAsyncHook; + modifyWebpackConfig: EnvironmentAsyncHook; + modifyRsbuildConfig: AsyncHook; + modifyEnvironmentConfig: EnvironmentAsyncHook; + onBeforeEnvironmentCompile: EnvironmentAsyncHook; + onAfterEnvironmentCompile: EnvironmentAsyncHook; +} { + return { + onExit: createAsyncHook(), + onAfterBuild: createAsyncHook(), + onBeforeBuild: createAsyncHook(), + onDevCompileDone: createAsyncHook(), + onCloseDevServer: createAsyncHook(), + onAfterStartDevServer: createAsyncHook(), + onBeforeStartDevServer: createAsyncHook(), + onAfterStartProdServer: createAsyncHook(), + onBeforeStartProdServer: createAsyncHook(), + onAfterCreateCompiler: createAsyncHook(), + onBeforeCreateCompiler: createAsyncHook(), + modifyHTMLTags: createEnvironmentAsyncHook(), + modifyRspackConfig: createEnvironmentAsyncHook(), + modifyBundlerChain: createEnvironmentAsyncHook(), + modifyWebpackChain: createEnvironmentAsyncHook(), + modifyWebpackConfig: createEnvironmentAsyncHook(), + modifyRsbuildConfig: createAsyncHook(), + modifyEnvironmentConfig: + createEnvironmentAsyncHook(), + onBeforeEnvironmentCompile: + createEnvironmentAsyncHook(), + onAfterEnvironmentCompile: + createEnvironmentAsyncHook(), + }; +} + +export type Hooks = ReturnType; + +const onBeforeCompile = ({ + compiler, + beforeCompile, + beforeEnvironmentCompiler, + isWatch, +}: { + compiler: Rspack.Compiler | Rspack.MultiCompiler; + beforeCompile?: () => Promise; + beforeEnvironmentCompiler: (buildIndex: number) => Promise; + isWatch?: boolean; +}): void => { + const name = 'rsbuild:beforeCompile'; + + if (isMultiCompiler(compiler)) { + const { compilers } = compiler; + + let doneCompilers = 0; + + for (let index = 0; index < compilers.length; index++) { + const compiler = compilers[index]; + let compilerDone = false; + + (isWatch ? compiler.hooks.watchRun : compiler.hooks.run).tapPromise( + name, + async () => { + if (!compilerDone) { + compilerDone = true; + doneCompilers++; + } + // ensure only last compiler done will trigger beforeCompile hook + // avoid other compiler done triggers when executing async beforeEnvironmentCompiler hook + const lastCompilerDone = doneCompilers === compilers.length; + + await beforeEnvironmentCompiler(index); + + if (lastCompilerDone) { + await beforeCompile?.(); + } + }, + ); + + compiler.hooks.invalid.tap(name, () => { + if (compilerDone) { + compilerDone = false; + doneCompilers--; + } + }); + } + } else { + (isWatch ? compiler.hooks.watchRun : compiler.hooks.run).tapPromise( + name, + async () => { + await beforeEnvironmentCompiler(0); + await beforeCompile?.(); + }, + ); + } +}; + +export const onCompileDone = ({ + compiler, + onDone, + onEnvironmentDone, + MultiStatsCtor, +}: { + compiler: Rspack.Compiler | Rspack.MultiCompiler; + onDone: (stats: Rspack.Stats | Rspack.MultiStats) => Promise; + onEnvironmentDone: (buildIndex: number, stats: Rspack.Stats) => Promise; + MultiStatsCtor: new (stats: Rspack.Stats[]) => Rspack.MultiStats; +}): void => { + // The MultiCompiler of Rspack does not supports `done.tapPromise`, + // so we need to use the `done` hook of `MultiCompiler.compilers` to implement it. + if (isMultiCompiler(compiler)) { + const { compilers } = compiler; + const compilerStats: Rspack.Stats[] = []; + let doneCompilers = 0; + + for (let index = 0; index < compilers.length; index++) { + const compiler = compilers[index]; + const compilerIndex = index; + let compilerDone = false; + + compiler.hooks.done.tapPromise('rsbuild:done', async (stats) => { + if (!compilerDone) { + compilerDone = true; + doneCompilers++; + } + + compilerStats[compilerIndex] = stats; + + const lastCompilerDone = doneCompilers === compilers.length; + + await onEnvironmentDone(index, stats); + + if (lastCompilerDone) { + await onDone(new MultiStatsCtor(compilerStats)); + } + }); + + compiler.hooks.invalid.tap('rsbuild:done', () => { + if (compilerDone) { + compilerDone = false; + doneCompilers--; + } + }); + } + } else { + compiler.hooks.done.tapPromise('rsbuild:done', async (stats) => { + await onEnvironmentDone(0, stats); + await onDone(stats); + }); + } +}; + +export const registerBuildHook = ({ + context, + isWatch, + compiler, + bundlerConfigs, + MultiStatsCtor, +}: { + bundlerConfigs?: RspackConfig[]; + context: InternalContext; + compiler: Rspack.Compiler | Rspack.MultiCompiler; + isWatch: boolean; + MultiStatsCtor: new (stats: Rspack.Stats[]) => Rspack.MultiStats; +}): void => { + let isFirstCompile = true; + + const environmentList = Object.values(context.environments).reduce< + EnvironmentContext[] + >((prev, curr) => { + prev[curr.index] = curr; + return prev; + }, []); + + const beforeCompile = async () => + await context.hooks.onBeforeBuild.call({ + bundlerConfigs, + environments: context.environments, + isWatch, + isFirstCompile, + }); + + const beforeEnvironmentCompiler = async (buildIndex: number) => + await context.hooks.onBeforeEnvironmentCompile.callInEnvironment({ + environment: environmentList[buildIndex].name, + args: [ + { + bundlerConfig: bundlerConfigs?.[buildIndex] as Rspack.Configuration, + environment: environmentList[buildIndex], + isWatch, + isFirstCompile, + }, + ], + }); + + const onDone = async (stats: Stats | MultiStats) => { + const p = context.hooks.onAfterBuild.call({ + isFirstCompile, + stats, + environments: context.environments, + isWatch, + }); + isFirstCompile = false; + await p; + }; + + const onEnvironmentDone = async (buildIndex: number, stats: Stats) => { + await context.hooks.onAfterEnvironmentCompile.callInEnvironment({ + environment: environmentList[buildIndex].name, + args: [ + { + isFirstCompile, + stats, + environment: environmentList[buildIndex], + isWatch, + }, + ], + }); + }; + + onBeforeCompile({ + compiler, + beforeCompile, + beforeEnvironmentCompiler, + isWatch, + }); + + onCompileDone({ + compiler, + onDone, + onEnvironmentDone, + MultiStatsCtor, + }); +}; + +export const registerDevHook = ({ + context, + compiler, + bundlerConfigs, + MultiStatsCtor, +}: { + bundlerConfigs?: RspackConfig[]; + context: InternalContext; + compiler: Rspack.Compiler | Rspack.MultiCompiler; + MultiStatsCtor: new (stats: Rspack.Stats[]) => Rspack.MultiStats; +}): void => { + let isFirstCompile = true; + + const environmentList = Object.values(context.environments).reduce< + EnvironmentContext[] + >((prev, curr) => { + prev[curr.index] = curr; + return prev; + }, []); + + const beforeEnvironmentCompiler = async (buildIndex: number) => + await context.hooks.onBeforeEnvironmentCompile.callInEnvironment({ + environment: environmentList[buildIndex].name, + args: [ + { + bundlerConfig: bundlerConfigs?.[buildIndex] as Rspack.Configuration, + environment: environmentList[buildIndex], + isWatch: true, + isFirstCompile, + }, + ], + }); + + const onDone = async (stats: Stats | MultiStats) => { + const p = context.hooks.onDevCompileDone.call({ + isFirstCompile, + stats, + environments: context.environments, + }); + isFirstCompile = false; + await p; + }; + + const onEnvironmentDone = async (buildIndex: number, stats: Stats) => { + await context.hooks.onAfterEnvironmentCompile.callInEnvironment({ + environment: environmentList[buildIndex].name, + args: [ + { + isFirstCompile, + stats, + environment: environmentList[buildIndex], + isWatch: true, + }, + ], + }); + }; + + onBeforeCompile({ + compiler, + beforeEnvironmentCompiler, + isWatch: true, + }); + + onCompileDone({ + compiler, + onDone, + onEnvironmentDone, + MultiStatsCtor, + }); +}; diff --git a/packages/core/src/initHooks.ts b/packages/core/src/initHooks.ts deleted file mode 100644 index 5776b66013..0000000000 --- a/packages/core/src/initHooks.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { isFunction } from './helpers'; -import { isPluginMatchEnvironment } from './pluginManager'; -import type { - AsyncHook, - EnvironmentAsyncHook, - HookDescriptor, - ModifyBundlerChainFn, - ModifyEnvironmentConfigFn, - ModifyHTMLTagsFn, - ModifyRsbuildConfigFn, - ModifyRspackConfigFn, - ModifyWebpackChainFn, - ModifyWebpackConfigFn, - OnAfterBuildFn, - OnAfterCreateCompilerFn, - OnAfterStartDevServerFn, - OnAfterStartProdServerFn, - OnBeforeBuildFn, - OnBeforeCreateCompilerFn, - OnBeforeStartDevServerFn, - OnBeforeStartProdServerFn, - OnCloseDevServerFn, - OnDevCompileDoneFn, - OnExitFn, -} from './types'; - -export function createEnvironmentAsyncHook< - Callback extends (...args: any[]) => any, ->(): EnvironmentAsyncHook { - type Hook = { - environment?: string; - handler: Callback; - }; - const preGroup: Hook[] = []; - const postGroup: Hook[] = []; - const defaultGroup: Hook[] = []; - - const tapEnvironment = ({ - environment, - handler: cb, - }: { - environment?: string; - handler: Callback | HookDescriptor; - }) => { - if (isFunction(cb)) { - defaultGroup.push({ - environment, - handler: cb, - }); - } else if (cb.order === 'pre') { - preGroup.push({ - environment, - handler: cb.handler, - }); - } else if (cb.order === 'post') { - postGroup.push({ - environment, - handler: cb.handler, - }); - } else { - defaultGroup.push({ - environment, - handler: cb.handler, - }); - } - }; - - const callInEnvironment = async ({ - environment, - args: params, - }: { - environment?: string; - args: Parameters; - }) => { - const callbacks = [...preGroup, ...defaultGroup, ...postGroup]; - - for (const callback of callbacks) { - // If this callback is not a global callback, the environment info should match - if ( - callback.environment && - environment && - !isPluginMatchEnvironment(callback.environment, environment) - ) { - continue; - } - - const result = await callback.handler(...params); - - if (result !== undefined) { - params[0] = result; - } - } - - return params; - }; - - return { - tapEnvironment, - tap: (handler: Callback | HookDescriptor) => - tapEnvironment({ handler }), - callInEnvironment, - }; -} - -export function createAsyncHook< - Callback extends (...args: any[]) => any, ->(): AsyncHook { - const preGroup: Callback[] = []; - const postGroup: Callback[] = []; - const defaultGroup: Callback[] = []; - - const tap = (cb: Callback | HookDescriptor) => { - if (isFunction(cb)) { - defaultGroup.push(cb); - } else if (cb.order === 'pre') { - preGroup.push(cb.handler); - } else if (cb.order === 'post') { - postGroup.push(cb.handler); - } else { - defaultGroup.push(cb.handler); - } - }; - - const call = async (...params: Parameters) => { - const callbacks = [...preGroup, ...defaultGroup, ...postGroup]; - - for (const callback of callbacks) { - const result = await callback(...params); - - if (result !== undefined) { - params[0] = result; - } - } - - return params; - }; - - return { - tap, - call, - }; -} - -export function initHooks(): { - /** The following hooks are global hooks */ - onExit: AsyncHook; - onAfterBuild: AsyncHook; - onBeforeBuild: AsyncHook; - onDevCompileDone: AsyncHook; - onCloseDevServer: AsyncHook; - onAfterStartDevServer: AsyncHook; - onBeforeStartDevServer: AsyncHook; - onAfterStartProdServer: AsyncHook; - onBeforeStartProdServer: AsyncHook; - onAfterCreateCompiler: AsyncHook; - onBeforeCreateCompiler: AsyncHook; - /** The following hooks are related to the environment */ - modifyHTMLTags: EnvironmentAsyncHook; - modifyRspackConfig: EnvironmentAsyncHook; - modifyBundlerChain: EnvironmentAsyncHook; - modifyWebpackChain: EnvironmentAsyncHook; - modifyWebpackConfig: EnvironmentAsyncHook; - modifyRsbuildConfig: AsyncHook; - modifyEnvironmentConfig: EnvironmentAsyncHook; -} { - return { - onExit: createAsyncHook(), - onAfterBuild: createAsyncHook(), - onBeforeBuild: createAsyncHook(), - onDevCompileDone: createAsyncHook(), - onCloseDevServer: createAsyncHook(), - onAfterStartDevServer: createAsyncHook(), - onBeforeStartDevServer: createAsyncHook(), - onAfterStartProdServer: createAsyncHook(), - onBeforeStartProdServer: createAsyncHook(), - onAfterCreateCompiler: createAsyncHook(), - onBeforeCreateCompiler: createAsyncHook(), - modifyHTMLTags: createEnvironmentAsyncHook(), - modifyRspackConfig: createEnvironmentAsyncHook(), - modifyBundlerChain: createEnvironmentAsyncHook(), - modifyWebpackChain: createEnvironmentAsyncHook(), - modifyWebpackConfig: createEnvironmentAsyncHook(), - modifyRsbuildConfig: createAsyncHook(), - modifyEnvironmentConfig: - createEnvironmentAsyncHook(), - }; -} - -export type Hooks = ReturnType; diff --git a/packages/core/src/initPlugins.ts b/packages/core/src/initPlugins.ts index 7b2ca009b4..e03441c944 100644 --- a/packages/core/src/initPlugins.ts +++ b/packages/core/src/initPlugins.ts @@ -319,5 +319,15 @@ export function initPluginAPI({ environment, handler, }), + onAfterEnvironmentCompile: (handler) => + hooks.onAfterEnvironmentCompile.tapEnvironment({ + environment, + handler, + }), + onBeforeEnvironmentCompile: (handler) => + hooks.onBeforeEnvironmentCompile.tapEnvironment({ + environment, + handler, + }), }); } diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index cdbc10a663..954fd4d298 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -7,7 +7,7 @@ export { runCli } from './cli/commands'; export { prepareCli } from './cli/prepare'; export { initPlugins, createPluginManager } from './pluginManager'; -export { initHooks, type Hooks } from './initHooks'; +export { initHooks, type Hooks } from './hooks'; export { initRsbuildConfig } from './provider/initConfigs'; export { stringifyConfig, @@ -16,13 +16,8 @@ export { } from './config'; export type { InternalContext } from './types'; export { setHTMLPlugin, getHTMLPlugin } from './pluginHelper'; -export { - formatStats, - getStatsOptions, - onCompileDone, - onBeforeBuild, - prettyTime, -} from './helpers'; +export { formatStats, getStatsOptions, prettyTime } from './helpers'; +export { registerBuildHook, registerDevHook, onCompileDone } from './hooks'; export { getChainUtils, getConfigUtils } from './provider/rspackConfig'; export { chainToConfig, modifyBundlerChain } from './configChain'; export { applySwcDecoratorConfig } from './plugins/swc'; diff --git a/packages/core/src/provider/build.ts b/packages/core/src/provider/build.ts index d578202df5..dad9cfcf95 100644 --- a/packages/core/src/provider/build.ts +++ b/packages/core/src/provider/build.ts @@ -1,10 +1,6 @@ import { rspack } from '@rspack/core'; -import { - getNodeEnv, - onBeforeBuild, - onCompileDone, - setNodeEnv, -} from '../helpers'; +import { getNodeEnv, setNodeEnv } from '../helpers'; +import { registerBuildHook } from '../hooks'; import { logger } from '../logger'; import type { BuildOptions, @@ -42,30 +38,13 @@ export const build = async ( bundlerConfigs = rspackConfigs; } - let isFirstCompile = true; - - const beforeBuild = async () => - await context.hooks.onBeforeBuild.call({ - bundlerConfigs, - environments: context.environments, - isWatch: Boolean(watch), - isFirstCompile, - }); - - const onDone = async (stats: Stats | MultiStats) => { - const p = context.hooks.onAfterBuild.call({ - isFirstCompile, - stats, - environments: context.environments, - isWatch: Boolean(watch), - }); - isFirstCompile = false; - await p; - }; - - onBeforeBuild(compiler, beforeBuild, watch); - - onCompileDone(compiler, onDone, rspack.MultiStats); + registerBuildHook({ + context, + bundlerConfigs, + compiler, + isWatch: Boolean(watch), + MultiStatsCtor: rspack.MultiStats, + }); if (watch) { const watching = compiler.watch({}, (err) => { diff --git a/packages/core/src/provider/createCompiler.ts b/packages/core/src/provider/createCompiler.ts index 852bcae294..8e02f6d6e3 100644 --- a/packages/core/src/provider/createCompiler.ts +++ b/packages/core/src/provider/createCompiler.ts @@ -7,10 +7,10 @@ import { isDev, isProd, isSatisfyRspackVersion, - onCompileDone, prettyTime, rspackMinVersion, } from '../helpers'; +import { registerDevHook } from '../hooks'; import { logger } from '../logger'; import type { DevMiddlewareAPI } from '../server/devMiddleware'; import type { @@ -49,7 +49,6 @@ export async function createCompiler({ ? rspack(rspackConfigs[0]) : rspack(rspackConfigs); - let isFirstCompile = true; let isVersionLogged = false; let isCompiling = false; @@ -72,7 +71,7 @@ export async function createCompiler({ compiler.hooks.run.tap('rsbuild:run', logRspackVersion); } - const done = async (stats: Stats | MultiStats) => { + const done = (stats: Stats | MultiStats) => { const statsJson = stats.toJson({ all: false, timings: true, @@ -106,19 +105,21 @@ export async function createCompiler({ logger.warn(message); } - if (isDev()) { - await context.hooks.onDevCompileDone.call({ - isFirstCompile, - stats: stats, - environments: context.environments, - }); - } - isCompiling = false; - isFirstCompile = false; }; - onCompileDone(compiler, done, rspack.MultiStats); + compiler.hooks.done.tap('rsbuild:done', (stats: Stats | MultiStats) => { + done(stats); + }); + + if (isDev()) { + registerDevHook({ + context, + compiler, + bundlerConfigs: rspackConfigs, + MultiStatsCtor: rspack.MultiStats, + }); + } await context.hooks.onAfterCreateCompiler.call({ compiler, diff --git a/packages/core/src/server/devServer.ts b/packages/core/src/server/devServer.ts index a6050eb223..6e99a7dd34 100644 --- a/packages/core/src/server/devServer.ts +++ b/packages/core/src/server/devServer.ts @@ -233,38 +233,34 @@ export async function createDevServer< }; const environmentAPI = Object.fromEntries( - Object.entries(options.context.environments).map( - ([name, environment], index) => { - return [ - name, - { - getStats: async () => { - if (!runCompile) { - throw new Error( - "can't get stats info when runCompile is false", - ); - } - await waitFirstCompileDone; - return lastStats[index]; - }, - loadBundle: async (entryName: string) => { - await waitFirstCompileDone; - return loadBundle(lastStats[index], entryName, { - readFileSync, - environment, - }); - }, - getTransformedHtml: async (entryName: string) => { - await waitFirstCompileDone; - return getTransformedHtml(entryName, { - readFileSync, - environment, - }); - }, + Object.entries(options.context.environments).map(([name, environment]) => { + return [ + name, + { + getStats: async () => { + if (!runCompile) { + throw new Error("can't get stats info when runCompile is false"); + } + await waitFirstCompileDone; + return lastStats[environment.index]; + }, + loadBundle: async (entryName: string) => { + await waitFirstCompileDone; + return loadBundle(lastStats[environment.index], entryName, { + readFileSync, + environment, + }); + }, + getTransformedHtml: async (entryName: string) => { + await waitFirstCompileDone; + return getTransformedHtml(entryName, { + readFileSync, + environment, + }); }, - ]; - }, - ), + }, + ]; + }), ); const devMiddlewares = await getMiddlewares({ diff --git a/packages/core/src/types/context.ts b/packages/core/src/types/context.ts index 05752ad3df..8ef8c343b1 100644 --- a/packages/core/src/types/context.ts +++ b/packages/core/src/types/context.ts @@ -1,4 +1,4 @@ -import type { Hooks } from '../initHooks'; +import type { Hooks } from '../hooks'; import type { NormalizedConfig, RsbuildConfig } from './config'; import type { EnvironmentContext } from './hooks'; import type { RsbuildPluginAPI } from './plugin'; diff --git a/packages/core/src/types/hooks.ts b/packages/core/src/types/hooks.ts index 5af9549524..f85536f53c 100644 --- a/packages/core/src/types/hooks.ts +++ b/packages/core/src/types/hooks.ts @@ -13,21 +13,40 @@ import type { MultiStats, Stats } from './stats'; import type { HtmlRspackPlugin, WebpackConfig } from './thirdParty'; import type { MaybePromise, NodeEnv } from './utils'; -export type OnBeforeBuildFn = (params: { +type CompileCommonParams = { isFirstCompile: boolean; isWatch: boolean; - bundlerConfigs?: B extends 'rspack' - ? Rspack.Configuration[] - : WebpackConfig[]; - environments: Record; -}) => MaybePromise; +}; -export type OnAfterBuildFn = (params: { - isFirstCompile: boolean; - isWatch: boolean; - stats?: Stats | MultiStats; - environments: Record; -}) => MaybePromise; +export type OnBeforeEnvironmentCompile = ( + params: CompileCommonParams & { + environment: EnvironmentContext; + bundlerConfig?: B extends 'rspack' ? Rspack.Configuration : WebpackConfig; + }, +) => MaybePromise; + +export type OnBeforeBuildFn = ( + params: CompileCommonParams & { + environments: Record; + bundlerConfigs?: B extends 'rspack' + ? Rspack.Configuration[] + : WebpackConfig[]; + }, +) => MaybePromise; + +export type OnAfterEnvironmentCompileFn = ( + params: CompileCommonParams & { + stats?: Stats; + environment: EnvironmentContext; + }, +) => MaybePromise; + +export type OnAfterBuildFn = ( + params: CompileCommonParams & { + stats?: Stats | MultiStats; + environments: Record; + }, +) => MaybePromise; export type OnCloseDevServerFn = () => MaybePromise; diff --git a/packages/core/src/types/plugin.ts b/packages/core/src/types/plugin.ts index eee54bc46f..a6d976a54b 100644 --- a/packages/core/src/types/plugin.ts +++ b/packages/core/src/types/plugin.ts @@ -21,10 +21,12 @@ import type { ModifyRsbuildConfigFn, OnAfterBuildFn, OnAfterCreateCompilerFn, + OnAfterEnvironmentCompileFn, OnAfterStartDevServerFn, OnAfterStartProdServerFn, OnBeforeBuildFn, OnBeforeCreateCompilerFn, + OnBeforeEnvironmentCompile, OnBeforeStartDevServerFn, OnBeforeStartProdServerFn, OnCloseDevServerFn, @@ -385,6 +387,8 @@ export type RsbuildPluginAPI = Readonly<{ onExit: PluginHook; onAfterBuild: PluginHook; onBeforeBuild: PluginHook; + onAfterEnvironmentCompile: PluginHook; + onBeforeEnvironmentCompile: PluginHook; onCloseDevServer: PluginHook; onDevCompileDone: PluginHook; onAfterStartDevServer: PluginHook; diff --git a/packages/core/tests/__snapshots__/hooks.test.ts.snap b/packages/core/tests/__snapshots__/hooks.test.ts.snap index dadd36275a..f373fc7536 100644 --- a/packages/core/tests/__snapshots__/hooks.test.ts.snap +++ b/packages/core/tests/__snapshots__/hooks.test.ts.snap @@ -20,5 +20,7 @@ exports[`initHooks > should init hooks correctly 1`] = ` "modifyWebpackConfig", "modifyRsbuildConfig", "modifyEnvironmentConfig", + "onBeforeEnvironmentCompile", + "onAfterEnvironmentCompile", ] `; diff --git a/packages/core/tests/createAsyncHook.test.ts b/packages/core/tests/createAsyncHook.test.ts index 4662790b0b..dc7b01e984 100644 --- a/packages/core/tests/createAsyncHook.test.ts +++ b/packages/core/tests/createAsyncHook.test.ts @@ -1,4 +1,4 @@ -import { createAsyncHook } from '../src/initHooks'; +import { createAsyncHook } from '../src/hooks'; describe('createAsyncHook', () => { test('should execute callback functions in order', async () => { diff --git a/packages/core/tests/hooks.test.ts b/packages/core/tests/hooks.test.ts index 22e39ac984..9fa771b497 100644 --- a/packages/core/tests/hooks.test.ts +++ b/packages/core/tests/hooks.test.ts @@ -1,5 +1,5 @@ import { createStubRsbuild } from '@scripts/test-helper'; -import { createEnvironmentAsyncHook, initHooks } from '../src/initHooks'; +import { createEnvironmentAsyncHook, initHooks } from '../src/hooks'; describe('initHooks', () => { test('should init hooks correctly', async () => { diff --git a/website/docs/en/plugins/dev/hooks.mdx b/website/docs/en/plugins/dev/hooks.mdx index 6231419fab..bfef00c0e8 100644 --- a/website/docs/en/plugins/dev/hooks.mdx +++ b/website/docs/en/plugins/dev/hooks.mdx @@ -13,6 +13,8 @@ This chapter introduces the plugin hooks available for Rsbuild plugins. - [modifyHTMLTags](#modifyhtmltags): Modify the tags that are injected into the HTML. - [onBeforeCreateCompiler](#onbeforecreatecompiler): Called before creating a compiler instance. - [onAfterCreateCompiler](#onaftercreatecompiler): Called after creating a compiler instance and before building. +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile): Called before the compilation of a single environment. +- [onAfterEnvironmentCompile](#onafterenvironmentcompile): Called after the compilation of a single environment. You can get the build result information. - [onExit](#onexit): Called when the process is about to exit. ### Dev Hooks @@ -51,15 +53,19 @@ When the `rsbuild dev` command or `rsbuild.startDevServer()` method is executed, - [modifyRspackConfig](#modifyrspackconfig) - [onBeforeCreateCompiler](#onbeforecreatecompiler) - [onAfterCreateCompiler](#onaftercreatecompiler) +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [onAfterStartDevServer](#onafterstartdevserver) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onDevCompileDone](#ondevcompiledone) - [onCloseDevServer](#onclosedevserver) - [onExit](#onexit) When rebuilding, the following hooks will be triggered again: +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onDevCompileDone](#ondevcompiledone) ### Build Hooks @@ -72,16 +78,20 @@ When the `rsbuild build` command or `rsbuild.build()` method is executed, Rsbuil - [modifyRspackConfig](#modifyrspackconfig) - [onBeforeCreateCompiler](#onbeforecreatecompiler) - [onAfterCreateCompiler](#onaftercreatecompiler) +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [onBeforeBuild](#onbeforebuild) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onAfterBuild](#onafterbuild) - [onExit](#onexit) When rebuilding, the following hooks will be triggered again: +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [onBeforeBuild](#onbeforebuild) - [modifyHTMLTags](#modifyhtmltags) - [onAfterBuild](#onafterbuild) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) ### Preview Hooks @@ -123,6 +133,8 @@ Correspondingly, there are some plugin hooks that are related to the current env - [modifyBundlerChain](#modifybundlerchain) - [modifyRspackConfig](#modifyrspackconfig) - [modifyHTMLTags](#modifyhtmltags) +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) ## Callback Order @@ -524,6 +536,45 @@ const myPlugin = () => ({ }); ``` +### onBeforeEnvironmentCompile + +import OnBeforeEnvironmentCompile from '@en/shared/onBeforeEnvironmentCompile.mdx'; + + + +- **Example:** + +```ts +const myPlugin = () => ({ + setup: (api) => { + api.onBeforeEnvironmentCompile(({ bundlerConfig, environment }) => { + console.log( + `the bundler config for the ${environment.name} is `, + bundlerConfig, + ); + }); + }, +}); +``` + +### onAfterEnvironmentCompile + +import OnAfterEnvironmentCompile from '@en/shared/onAfterEnvironmentCompile.mdx'; + + + +- **Example:** + +```ts +const myPlugin = () => ({ + setup: (api) => { + api.onAfterEnvironmentCompile(({ isFirstCompile, stats }) => { + console.log(stats?.toJson(), isFirstCompile); + }); + }, +}); +``` + ## Build Hooks ### onBeforeBuild @@ -601,6 +652,28 @@ const myPlugin = () => ({ }); ``` +### onAfterEnvironmentCompile + +import OnDevCompileEnvironmentDone from '@en/shared/onAfterEnvironmentCompile.mdx'; + + + +- **Example:** + +```ts +const myPlugin = () => ({ + setup: (api) => { + api.onAfterEnvironmentCompile(({ isFirstCompile }) => { + if (isFirstCompile) { + console.log('first compile!'); + } else { + console.log('re-compile!'); + } + }); + }, +}); +``` + ### onDevCompileDone import OnDevCompileDone from '@en/shared/onDevCompileDone.mdx'; diff --git a/website/docs/en/shared/onAfterEnvironmentCompile.mdx b/website/docs/en/shared/onAfterEnvironmentCompile.mdx new file mode 100644 index 0000000000..79a71d57b5 --- /dev/null +++ b/website/docs/en/shared/onAfterEnvironmentCompile.mdx @@ -0,0 +1,16 @@ +`onAfterEnvironmentCompile` is a callback function that is triggered after the compilation of a single environment. You can access the build result information via the [stats](https://webpack.js.org/api/node/#stats-object) parameter. + +Moreover, you can use `isWatch` to determine whether it is dev or build watch mode, and use `isFirstCompile` to determine whether it is the first build. + +- **Type:** + +```ts +function OnAfterEnvironmentCompile( + callback: (params: { + isFirstCompile: boolean; + isWatch: boolean; + stats?: Stats; + environment: EnvironmentContext; + }) => Promise | void, +): void; +``` diff --git a/website/docs/en/shared/onBeforeEnvironmentCompile.mdx b/website/docs/en/shared/onBeforeEnvironmentCompile.mdx new file mode 100644 index 0000000000..d0a3410042 --- /dev/null +++ b/website/docs/en/shared/onBeforeEnvironmentCompile.mdx @@ -0,0 +1,18 @@ +`onBeforeEnvironmentCompile` is a callback function that is triggered before the compilation of a single environment. + +You can access the Rspack configuration array through the `bundlerConfigs` parameter. The array may contain one or more [Rspack configurations](https://rspack.dev/config/). It depends on whether multiple [environments](/config/environments) are configured. + +Moreover, you can use `isWatch` to determine whether it is dev or build watch mode, and use `isFirstCompile` to determine whether it is the first build. + +- **Type:** + +```ts +function OnBeforeEnvironmentCompile( + callback: (params: { + isWatch: boolean; + isFirstCompile: boolean; + bundlerConfig?: RspackConfig; + environment: EnvironmentContext; + }) => Promise | void, +): void; +``` diff --git a/website/docs/zh/plugins/dev/hooks.mdx b/website/docs/zh/plugins/dev/hooks.mdx index 99d025fb22..b642a8d5c4 100644 --- a/website/docs/zh/plugins/dev/hooks.mdx +++ b/website/docs/zh/plugins/dev/hooks.mdx @@ -13,6 +13,8 @@ - [modifyHTMLTags](#modifyhtmltags):修改注入到 HTML 中的标签。 - [onBeforeCreateCompiler](#onbeforecreatecompiler):在创建 compiler 实例前调用。 - [onAfterCreateCompiler](#onaftercreatecompiler):在创建 compiler 实例后、执行构建前调用。 +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile): 在每次执行单个 environment 的构建前调用。 +- [onAfterEnvironmentCompile](#onafterenvironmentcompile): 在每次单个 environment 的构建结束后调用。 - [onExit](#onexit):在进程即将退出时调用。 ### Dev Hooks @@ -51,15 +53,19 @@ - [modifyRspackConfig](#modifyrspackconfig) - [onBeforeCreateCompiler](#onbeforecreatecompiler) - [onAfterCreateCompiler](#onaftercreatecompiler) +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [onAfterStartDevServer](#onafterstartdevserver) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onDevCompileDone](#ondevcompiledone) - [onCloseDevServer](#onclosedevserver) - [onExit](#onexit) 当 rebuild 时,以下 hooks 会再次触发: +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onDevCompileDone](#ondevcompiledone) ### Build Hooks @@ -72,15 +78,19 @@ - [modifyRspackConfig](#modifyrspackconfig) - [onBeforeCreateCompiler](#onbeforecreatecompiler) - [onAfterCreateCompiler](#onaftercreatecompiler) +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [onBeforeBuild](#onbeforebuild) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onAfterBuild](#onafterbuild) - [onExit](#onexit) 当 rebuild 时,以下 hooks 会再次触发: +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) - [onBeforeBuild](#onbeforebuild) - [modifyHTMLTags](#modifyhtmltags) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) - [onAfterBuild](#onafterbuild) ### Preview Hooks @@ -123,6 +133,8 @@ - [modifyBundlerChain](#modifybundlerchain) - [modifyRspackConfig](#modifyrspackconfig) - [modifyHTMLTags](#modifyhtmltags) +- [onBeforeEnvironmentCompile](#onbeforeenvironmentcompile) +- [onAfterEnvironmentCompile](#onafterenvironmentcompile) ## 回调函数顺序 @@ -520,6 +532,45 @@ const myPlugin = () => ({ }); ``` +### onBeforeEnvironmentCompile + +import OnBeforeEnvironmentCompile from '@zh/shared/onBeforeEnvironmentCompile.mdx'; + + + +- **示例:** + +```ts +const myPlugin = () => ({ + setup: (api) => { + api.onBeforeEnvironmentCompile(({ bundlerConfig, environment }) => { + console.log( + `the bundler config for the ${environment.name} is `, + bundlerConfig, + ); + }); + }, +}); +``` + +### onAfterEnvironmentCompile + +import OnAfterEnvironmentCompile from '@zh/shared/onAfterEnvironmentCompile.mdx'; + + + +- **示例:** + +```ts +const myPlugin = () => ({ + setup: (api) => { + api.onAfterEnvironmentCompile(({ isFirstCompile, stats }) => { + console.log(stats?.toJson(), isFirstCompile); + }); + }, +}); +``` + ## Build Hooks ### onBeforeBuild @@ -597,6 +648,28 @@ const myPlugin = () => ({ }); ``` +### onAfterEnvironmentCompile + +import OnDevCompileEnvironmentDone from '@zh/shared/onAfterEnvironmentCompile.mdx'; + + + +- **示例:** + +```ts +const myPlugin = () => ({ + setup: (api) => { + api.onAfterEnvironmentCompile(({ isFirstCompile }) => { + if (isFirstCompile) { + console.log('first compile!'); + } else { + console.log('re-compile!'); + } + }); + }, +}); +``` + ### onDevCompileDone import OnDevCompileDone from '@zh/shared/onDevCompileDone.mdx'; diff --git a/website/docs/zh/shared/onAfterEnvironmentCompile.mdx b/website/docs/zh/shared/onAfterEnvironmentCompile.mdx new file mode 100644 index 0000000000..2765b3683e --- /dev/null +++ b/website/docs/zh/shared/onAfterEnvironmentCompile.mdx @@ -0,0 +1,16 @@ +`onAfterEnvironmentCompile` 是在执行单个 environment 的构建后触发的回调函数,你可以通过 [stats](https://webpack.js.org/api/node/#stats-object) 参数获取到构建结果信息。 + +另外,你可以通过 `isWatch` 判断是否是 dev 或者 build watch 模式,并通过 `isFirstCompile` 来判断是否为首次构建。 + +- **类型:** + +```ts +function OnAfterEnvironmentCompile( + callback: (params: { + isFirstCompile: boolean; + isWatch: boolean; + stats?: Stats; + environment: EnvironmentContext; + }) => Promise | void, +): void; +``` diff --git a/website/docs/zh/shared/onBeforeEnvironmentCompile.mdx b/website/docs/zh/shared/onBeforeEnvironmentCompile.mdx new file mode 100644 index 0000000000..e1855d107c --- /dev/null +++ b/website/docs/zh/shared/onBeforeEnvironmentCompile.mdx @@ -0,0 +1,18 @@ +`onBeforeEnvironmentCompile` 是在执行单个 environment 的构建前触发的回调函数。 + +你可以通过 `bundlerConfig` 参数获取到当前 environment 对应的 Rspack 配置(https://rspack.dev/config/)。 + +另外,你可以通过 `isWatch` 判断是否是 dev 或者 build watch 模式,并在 watch 模式下通过 `isFirstCompile` 来判断是否为首次构建。 + +- **类型:** + +```ts +function OnBeforeEnvironmentCompile( + callback: (params: { + isWatch: boolean; + isFirstCompile: boolean; + bundlerConfig?: RspackConfig; + environment: EnvironmentContext; + }) => Promise | void, +): void; +``` diff --git a/website/docs/zh/shared/onDevCompileEnvironmentDone.mdx b/website/docs/zh/shared/onDevCompileEnvironmentDone.mdx new file mode 100644 index 0000000000..352113a8d7 --- /dev/null +++ b/website/docs/zh/shared/onDevCompileEnvironmentDone.mdx @@ -0,0 +1,13 @@ +在每次单个 environment 的开发环境构建结束后调用,你可以通过 `isFirstCompile` 来判断是否为首次构建。 + +- **类型:** + +```ts +function OnDevCompileEnvironmentDone( + callback: (params: { + isFirstCompile: boolean; + stats: Stats; + environment: EnvironmentContext; + }) => Promise | void, +): void; +```