diff --git a/packages/solutions/app-tools/src/v3/index.ts b/packages/solutions/app-tools/src/v3/index.ts index 1686b0142286..383bfe1d3aed 100644 --- a/packages/solutions/app-tools/src/v3/index.ts +++ b/packages/solutions/app-tools/src/v3/index.ts @@ -1,5 +1,15 @@ import type { Plugin } from '@modern-js/plugin-v2'; -import type { AppTools } from './types'; +import { createAsyncHook } from '@modern-js/plugin-v2'; +import type { + AppTools, + CheckEntryPointFn, + DeplpoyFn, + InternalRuntimePluginsFn, + InternalServerPluginsFn, + ModifyEntrypointsFn, + ModifyFileSystemRoutesFn, + ModifyServerRoutesFn, +} from './types'; export * from '../defineConfig'; @@ -11,6 +21,18 @@ export type AppToolsOptions = { bundler?: 'rspack' | 'webpack' | 'experimental-rspack'; }; +const testPlugin = (): Plugin> => { + return { + name: 'test-plugin', + setup: api => { + api.checkEntryPoint(({ path, entry }) => { + console.log('checkEntryPoint'); + return { path, entry }; + }); + }, + }; +}; + export const appTools = ( options: AppToolsOptions = { // default webpack to be compatible with original projects @@ -18,8 +40,20 @@ export const appTools = ( }, ): Plugin> => ({ name: '@modern-js/plugin-app-tools', + usePlugins: [testPlugin()], + registryHooks: { + _internalRuntimePlugins: createAsyncHook(), + _internalServerPlugins: createAsyncHook(), + checkEntryPoint: createAsyncHook(), + modifyEntrypoints: createAsyncHook(), + modifyFileSystemRoutes: createAsyncHook(), + modifyServerRoutes: createAsyncHook(), + deploy: createAsyncHook(), + }, setup: api => { api.onPrepare(() => { + const hooks = api.getHooks(); + hooks.checkEntryPoint.call({ path: '', entry: '' }); console.log('app-tools prepare', options); }); }, diff --git a/packages/solutions/app-tools/src/v3/types/index.ts b/packages/solutions/app-tools/src/v3/types/index.ts index b60e734172f7..314080926d53 100644 --- a/packages/solutions/app-tools/src/v3/types/index.ts +++ b/packages/solutions/app-tools/src/v3/types/index.ts @@ -1,12 +1,53 @@ -import type { CLIPluginAPI } from '@modern-js/plugin-v2'; +import type { + CLIPluginAPI, + PluginHookTap, + TransformFunction, +} from '@modern-js/plugin-v2'; +import type { + Entrypoint, + NestedRouteForCli, + PageRoute, + RouteLegacy, + ServerPlugin, + ServerRoute, +} from '@modern-js/types'; import type { AppToolsNormalizedConfig, AppToolsUserConfig, } from '../../types/config'; +import type { RuntimePlugin } from '../../types/hooks'; import type { Bundler } from '../../types/utils'; +export type InternalRuntimePluginsFn = TransformFunction<{ + entrypoint: Entrypoint; + plugins: RuntimePlugin[]; +}>; +export type InternalServerPluginsFn = TransformFunction<{ + plugins: ServerPlugin[]; +}>; +export type CheckEntryPointFn = TransformFunction<{ + path: string; + entry: false | string; +}>; +export type ModifyEntrypointsFn = TransformFunction; +export type ModifyFileSystemRoutesFn = TransformFunction<{ + entrypoint: Entrypoint; + routes: RouteLegacy[] | (NestedRouteForCli | PageRoute)[]; +}>; +export type ModifyServerRoutesFn = TransformFunction<{ routes: ServerRoute[] }>; +export type DeplpoyFn = () => Promise | void; + export interface AppTools extends CLIPluginAPI< AppToolsUserConfig, AppToolsNormalizedConfig> - > {} + > { + _internalRuntimePlugins: PluginHookTap; + _internalServerPlugins: PluginHookTap; + checkEntryPoint: PluginHookTap; + modifyEntrypoints: PluginHookTap; + modifyFileSystemRoutes: PluginHookTap; + modifyServerRoutes: PluginHookTap; + + deploy: PluginHookTap; +} diff --git a/packages/toolkit/plugin-v2/src/cli/api.ts b/packages/toolkit/plugin-v2/src/cli/api.ts index 49a4c805c74f..0e55e3849a40 100644 --- a/packages/toolkit/plugin-v2/src/cli/api.ts +++ b/packages/toolkit/plugin-v2/src/cli/api.ts @@ -1,3 +1,4 @@ +import type { PluginHookTap } from '../types'; import type { CLIPluginAPI } from '../types/cli/api'; import type { InternalContext } from '../types/cli/context'; import type { PluginManager } from '../types/plugin'; @@ -8,7 +9,7 @@ export function initPluginAPI({ context: InternalContext; pluginManager: PluginManager; }): CLIPluginAPI { - const { hooks } = context; + const { hooks, extendsHooks } = context; function getAppContext() { if (context) { const { hooks, config, normalizedConfig, pluginAPI, ...appContext } = @@ -31,10 +32,26 @@ export function initPluginAPI({ throw new Error('Cannot access normalized config'); } + function getHooks() { + return { + ...context.hooks, + ...context.extendsHooks, + }; + } + const extendsPluginApi: Record< + string, + PluginHookTap<(...args: any[]) => any> + > = {}; + + Object.keys(extendsHooks).forEach(hookName => { + extendsPluginApi[hookName] = extendsHooks[hookName].tap; + }); + return { getAppContext, getConfig, getNormalizedConfig, + getHooks, config: hooks.config.tap, modifyConfig: hooks.modifyConfig.tap, @@ -62,5 +79,6 @@ export function initPluginAPI({ onBeforeDeploy: hooks.onBeforeDeploy.tap, onAfterDeploy: hooks.onAfterDeploy.tap, onBeforeExit: hooks.onBeforeExit.tap, + ...extendsPluginApi, }; } diff --git a/packages/toolkit/plugin-v2/src/cli/context.ts b/packages/toolkit/plugin-v2/src/cli/context.ts index 2d7fc855d6a2..ac673c82dfb6 100644 --- a/packages/toolkit/plugin-v2/src/cli/context.ts +++ b/packages/toolkit/plugin-v2/src/cli/context.ts @@ -1,6 +1,7 @@ import path from 'path'; import type { AppContext, InternalContext } from '../types/cli/context'; import type { CLIPlugin } from '../types/cli/plugin'; +import type { PluginHook } from '../types/hooks'; import { initHooks } from './hooks'; interface ContextParams { @@ -39,9 +40,18 @@ export async function createContext({ }: ContextParams): Promise< InternalContext > { + const { plugins } = appContext; + const extendsHooks: Record any>> = {}; + plugins.forEach(plugin => { + const { registryHooks = {} } = plugin; + Object.keys(registryHooks).forEach(hookName => { + extendsHooks[hookName] = registryHooks[hookName]; + }); + }); return { ...appContext, hooks: initHooks(), + extendsHooks, config, normalizedConfig, }; diff --git a/packages/toolkit/plugin-v2/src/cli/hooks.ts b/packages/toolkit/plugin-v2/src/cli/hooks.ts index 52515cb6742d..0922d7baccfa 100644 --- a/packages/toolkit/plugin-v2/src/cli/hooks.ts +++ b/packages/toolkit/plugin-v2/src/cli/hooks.ts @@ -9,6 +9,7 @@ import type { OnBeforeBuildFn, OnBeforeCreateCompilerFn, } from '@rsbuild/core'; +import { createAsyncHook, createCollectAsyncHook } from '../hooks'; import type { AddCommandFn, AddWatchFilesFn, @@ -27,72 +28,6 @@ import type { } from '../types/cli/hooks'; import type { DeepPartial } from '../types/utils'; -export type AsyncHook any> = { - tap: (cb: Callback) => void; - call: (...args: Parameters) => Promise>; -}; - -export function createAsyncHook< - Callback extends (...args: any[]) => any, ->(): AsyncHook { - const callbacks: Callback[] = []; - - const tap = (cb: Callback) => { - callbacks.push(cb); - }; - - const call = async (...params: Parameters) => { - for (const callback of callbacks) { - const result = await callback(...params); - - if (result !== undefined) { - params[0] = result; - } - } - - return params; - }; - - return { - tap, - call, - }; -} - -export type CollectAsyncHook any> = { - tap: (cb: Callback) => void; - call: (...args: Parameters) => Promise[]>; -}; - -export function createCollectAsyncHook< - Callback extends (...args: any[]) => any, ->(): CollectAsyncHook { - const callbacks: Callback[] = []; - - const tap = (cb: Callback) => { - callbacks.push(cb); - }; - - const call = async (...params: Parameters) => { - const results: ReturnType[] = []; - for (const callback of callbacks) { - const result = await callback(...params); - - if (result !== undefined) { - params[0] = result; - results.push(result); - } - } - - return results; - }; - - return { - tap, - call, - }; -} - export function initHooks() { return { /** diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts new file mode 100644 index 000000000000..004d7220541e --- /dev/null +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -0,0 +1,57 @@ +import type { AsyncHook, CollectAsyncHook } from './types/hooks'; + +export function createAsyncHook< + Callback extends (...args: any[]) => any, +>(): AsyncHook { + const callbacks: Callback[] = []; + + const tap = (cb: Callback) => { + callbacks.push(cb); + }; + + const call = async (...params: Parameters) => { + for (const callback of callbacks) { + const result = await callback(...params); + + if (result !== undefined) { + params[0] = result; + } + } + + return params; + }; + + return { + tap, + call, + }; +} + +export function createCollectAsyncHook< + Callback extends (...args: any[]) => any, +>(): CollectAsyncHook { + const callbacks: Callback[] = []; + + const tap = (cb: Callback) => { + callbacks.push(cb); + }; + + const call = async (...params: Parameters) => { + const results: ReturnType[] = []; + for (const callback of callbacks) { + const result = await callback(...params); + + if (result !== undefined) { + params[0] = result; + results.push(result); + } + } + + return results; + }; + + return { + tap, + call, + }; +} diff --git a/packages/toolkit/plugin-v2/src/index.ts b/packages/toolkit/plugin-v2/src/index.ts index c529bb21ffba..096cf7dd5ad8 100644 --- a/packages/toolkit/plugin-v2/src/index.ts +++ b/packages/toolkit/plugin-v2/src/index.ts @@ -1,4 +1,5 @@ export { createPluginManager } from './manager'; +export { createAsyncHook, createCollectAsyncHook } from './hooks'; export * from './cli'; export * from './types'; diff --git a/packages/toolkit/plugin-v2/src/types/cli/api.ts b/packages/toolkit/plugin-v2/src/types/cli/api.ts index 7184e0767423..98a128679bf9 100644 --- a/packages/toolkit/plugin-v2/src/types/cli/api.ts +++ b/packages/toolkit/plugin-v2/src/types/cli/api.ts @@ -9,7 +9,8 @@ import type { OnBeforeBuildFn, OnBeforeCreateCompilerFn, } from '@rsbuild/core'; -import type { PluginHook } from '../plugin'; +import type { Hooks } from '../../cli/hooks'; +import type { PluginHook, PluginHookTap } from '../hooks'; import type { DeepPartial } from '../utils'; import type { AppContext } from './context'; import type { @@ -36,36 +37,40 @@ export type CLIPluginAPI = Readonly<{ getAppContext: () => Readonly>; getConfig: () => Readonly; getNormalizedConfig: () => Readonly; + getHooks: () => Readonly< + Hooks & + Record any>> + >; // config hooks TOOD check - config: PluginHook>>; - modifyConfig: PluginHook>; - modifyResolvedConfig: PluginHook>; + config: PluginHookTap>>; + modifyConfig: PluginHookTap>; + modifyResolvedConfig: PluginHookTap>; // modify rsbuild config hooks - modifyRsbuildConfig: PluginHook; - modifyBundlerChain: PluginHook; + modifyRsbuildConfig: PluginHookTap; + modifyBundlerChain: PluginHookTap; /** Only works when bundler is Rspack */ - modifyRspackConfig: PluginHook; + modifyRspackConfig: PluginHookTap; /** Only works when bundler is Webpack */ - modifyWebpackChain: PluginHook; + modifyWebpackChain: PluginHookTap; /** Only works when bundler is Webpack */ - modifyWebpackConfig: PluginHook; - modifyHtmlPartials: PluginHook; + modifyWebpackConfig: PluginHookTap; + modifyHtmlPartials: PluginHookTap; - addCommand: PluginHook; + addCommand: PluginHookTap; - onPrepare: PluginHook; - onWatchFiles: PluginHook; - onFileChanged: PluginHook; - onBeforeRestart: PluginHook; - onBeforeCreateCompiler: PluginHook; - onAfterCreateCompiler: PluginHook; - onBeforeBuild: PluginHook; - onAfterBuild: PluginHook; - onBeforeDev: PluginHook; - onAfterDev: PluginHook; - onBeforeDeploy: PluginHook; - onAfterDeploy: PluginHook; - onBeforeExit: PluginHook; + onPrepare: PluginHookTap; + onWatchFiles: PluginHookTap; + onFileChanged: PluginHookTap; + onBeforeRestart: PluginHookTap; + onBeforeCreateCompiler: PluginHookTap; + onAfterCreateCompiler: PluginHookTap; + onBeforeBuild: PluginHookTap; + onAfterBuild: PluginHookTap; + onBeforeDev: PluginHookTap; + onAfterDev: PluginHookTap; + onBeforeDeploy: PluginHookTap; + onAfterDeploy: PluginHookTap; + onBeforeExit: PluginHookTap; }>; diff --git a/packages/toolkit/plugin-v2/src/types/cli/context.ts b/packages/toolkit/plugin-v2/src/types/cli/context.ts index b6a6bd1a400d..ab5f0c79eaaf 100644 --- a/packages/toolkit/plugin-v2/src/types/cli/context.ts +++ b/packages/toolkit/plugin-v2/src/types/cli/context.ts @@ -4,6 +4,7 @@ import type { UniBuilderWebpackInstance, } from '@modern-js/uni-builder'; import type { Hooks } from '../../cli/hooks'; +import type { PluginHook } from '../hooks'; import type { CLIPluginAPI } from './api'; import type { CLIPlugin } from './plugin'; @@ -52,10 +53,14 @@ export type InternalContext = AppContext< NormalizedConfig > & { /** All hooks. */ - hooks: Readonly>; + hooks: Hooks & + Record any>>; + /** All plugin registry hooks */ + extendsHooks: Record any>>; /** Current App config. */ config: Readonly; /** The normalized Rsbuild config. */ normalizedConfig?: NormalizedConfig; - pluginAPI?: CLIPluginAPI; + pluginAPI?: CLIPluginAPI & + Record any>; }; diff --git a/packages/toolkit/plugin-v2/src/types/cli/hooks.ts b/packages/toolkit/plugin-v2/src/types/cli/hooks.ts index d1984ec2ba2d..751c430b80c7 100644 --- a/packages/toolkit/plugin-v2/src/types/cli/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/cli/hooks.ts @@ -1,13 +1,13 @@ import type { Command } from '@modern-js/utils/commander'; +import type { TransformFunction } from '../plugin'; import type { Entrypoint } from './context'; export type ConfigFn = () => Config; -export type ModifyConfigFn = (config: Config) => Config; +export type ModifyConfigFn = TransformFunction; -export type ModifyResolvedConfigFn = ( - config: NormalizedConfig, -) => NormalizedConfig; +export type ModifyResolvedConfigFn = + TransformFunction; type IPartialMethod = (...script: string[]) => void; export interface PartialMethod { diff --git a/packages/toolkit/plugin-v2/src/types/hooks.ts b/packages/toolkit/plugin-v2/src/types/hooks.ts new file mode 100644 index 000000000000..c80e3275f81b --- /dev/null +++ b/packages/toolkit/plugin-v2/src/types/hooks.ts @@ -0,0 +1,17 @@ +export type AsyncHook any> = { + tap: (cb: Callback) => void; + call: (...args: Parameters) => Promise>; +}; + +export type CollectAsyncHook any> = { + tap: (cb: Callback) => void; + call: (...args: Parameters) => Promise[]>; +}; + +export type PluginHook any> = + | AsyncHook + | CollectAsyncHook; + +export type PluginHookTap any> = ( + options: T, +) => void; diff --git a/packages/toolkit/plugin-v2/src/types/index.ts b/packages/toolkit/plugin-v2/src/types/index.ts index 8e95b806188a..3d9acd905b17 100644 --- a/packages/toolkit/plugin-v2/src/types/index.ts +++ b/packages/toolkit/plugin-v2/src/types/index.ts @@ -1,2 +1,7 @@ -export type { Plugin, PluginManager } from './plugin'; +export type { + Plugin, + PluginManager, + TransformFunction, +} from './plugin'; export * from './cli'; +export * from './hooks'; diff --git a/packages/toolkit/plugin-v2/src/types/plugin.ts b/packages/toolkit/plugin-v2/src/types/plugin.ts index 2c8ac945bf1d..e57e18538549 100644 --- a/packages/toolkit/plugin-v2/src/types/plugin.ts +++ b/packages/toolkit/plugin-v2/src/types/plugin.ts @@ -1,9 +1,8 @@ +import type { PluginHook } from './hooks'; import type { Falsy } from './utils'; import type { MaybePromise } from './utils'; -export type PluginHook any> = ( - options: T, -) => void; +export type TransformFunction = (arg: T) => T | Promise; export type Plugin = { /** @@ -14,6 +13,7 @@ export type Plugin = { * The plugins that this plugin depends on. */ usePlugins?: Plugin[]; + registryHooks?: Record any>>; /** * The setup function of the plugin, which can be an async function. * This function is called once when the plugin is initialized.