Skip to content

Commit

Permalink
feat: export router extend hooks type (#6750)
Browse files Browse the repository at this point in the history
  • Loading branch information
caohuilin authored Jan 22, 2025
1 parent 0e5b623 commit 48c11bf
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 102 deletions.
8 changes: 8 additions & 0 deletions .changeset/plenty-donuts-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@modern-js/plugin-router-v5': patch
'@modern-js/runtime': patch
---

feat: export router extend hooks type

feat: 导出 router 插件扩展 Hooks 类型
7 changes: 7 additions & 0 deletions .changeset/silly-ways-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modern-js/plugin-v2': patch
---

feat: enhance plugin API type inference, supporting extendsHooks and extendsAPI

feat: 增强 plugin api 类型推断,支持 extendsHooks 和 extendsAPI
7 changes: 6 additions & 1 deletion packages/runtime/plugin-router-v5/src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { routerPlugin } from './plugin';

export type { SingleRouteConfig, HistoryConfig, RouterConfig } from './plugin';
export type {
SingleRouteConfig,
HistoryConfig,
RouterConfig,
RouterExtendsHooks,
} from './plugin';
export { routerPlugin } from './plugin';
export default routerPlugin;

Expand Down
8 changes: 5 additions & 3 deletions packages/runtime/plugin-router-v5/src/runtime/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import {
import { modifyRoutesHook } from './hooks';
import { getLocation, renderRoutes, urlJoin } from './utils';

export type RouterExtendsHooks = {
modifyRoutes: typeof modifyRoutesHook;
};

export type SingleRouteConfig = RouteProps & {
redirect?: string;
routes?: SingleRouteConfig[];
Expand Down Expand Up @@ -64,9 +68,7 @@ let routes: SingleRouteConfig[] = [];
export const routerPlugin = (
userConfig: RouterConfig = {},
): RuntimePluginFuture<{
extendHooks: {
modifyRoutes: typeof modifyRoutesHook;
};
extendHooks: RouterExtendsHooks;
}> => {
return {
name: '@modern-js/plugin-router',
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/plugin-runtime/src/router/runtime/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ const onBeforeCreateRoutes =
createSyncHook<(context: RuntimeContext) => void>();

export { modifyRoutes, onBeforeCreateRoutes };

export type RouterExtendsHooks = {
modifyRoutes: typeof modifyRoutes;
onBeforeCreateRoutes: typeof onBeforeCreateRoutes;
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { routerPlugin };
export default routerPlugin;

export { modifyRoutes } from './plugin';
export type { RouterExtendsHooks } from './hooks';

export * from './withRouter';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { RuntimePluginFuture } from '../../core';
import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context';
import DeferredDataScripts from './DeferredDataScripts.node';
import {
type RouterExtendsHooks,
modifyRoutes as modifyRoutesHook,
onBeforeCreateRoutes as onBeforeCreateRoutesHook,
} from './hooks';
Expand All @@ -40,10 +41,7 @@ function createRemixReuqest(request: Request) {
export const routerPlugin = (
userConfig: Partial<RouterConfig> = {},
): RuntimePluginFuture<{
extendHooks: {
modifyRoutes: typeof modifyRoutesHook;
onBeforeCreateRoutes: typeof onBeforeCreateRoutesHook;
};
extendHooks: RouterExtendsHooks;
}> => {
return {
name: '@modern-js/plugin-router',
Expand Down
6 changes: 2 additions & 4 deletions packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useContext, useMemo } from 'react';
import { type RuntimePluginFuture, RuntimeReactContext } from '../../core';
import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context';
import {
type RouterExtendsHooks,
modifyRoutes as modifyRoutesHook,
onBeforeCreateRoutes as onBeforeCreateRoutesHook,
} from './hooks';
Expand Down Expand Up @@ -43,10 +44,7 @@ export function modifyRoutes(modifyFunction: (routes: Routes) => Routes) {
export const routerPlugin = (
userConfig: Partial<RouterConfig> = {},
): RuntimePluginFuture<{
extendHooks: {
modifyRoutes: typeof modifyRoutesHook;
onBeforeCreateRoutes: typeof onBeforeCreateRoutesHook;
};
extendHooks: RouterExtendsHooks;
}> => {
return {
name: '@modern-js/plugin-router',
Expand Down
21 changes: 12 additions & 9 deletions packages/toolkit/plugin-v2/src/cli/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { merge } from '@modern-js/utils/lodash';
import type { PluginHook, PluginHookTap } from '../types';
import type { CLIPluginAPI } from '../types/cli/api';
import type { PluginHook } from '../types';
import type {
AllKeysForCLIPluginExtendsAPI,
AllValueForCLIPluginExtendsAPI,
CLIPluginAPI,
CLIPluginExtendsAPI,
} from '../types/cli/api';
import type { AppContext, InternalContext } from '../types/cli/context';
import type { CLIPluginExtends } from '../types/cli/plugin';
import type { PluginManager } from '../types/plugin';
Expand Down Expand Up @@ -48,26 +53,24 @@ export function initPluginAPI<Extends extends CLIPluginExtends>({
return context.hooks;
}

const extendsPluginApi: Record<
string,
PluginHookTap<(...args: any[]) => any>
> = {};
const extendsPluginApi: Partial<CLIPluginExtendsAPI<Extends>> = {};

plugins.forEach(plugin => {
const { _registryApi } = plugin;
if (_registryApi) {
const apis = _registryApi(getAppContext, updateAppContext);
Object.keys(apis).forEach(apiName => {
extendsPluginApi[apiName] = apis[apiName];
extendsPluginApi[apiName as AllKeysForCLIPluginExtendsAPI<Extends>] =
apis[apiName] as AllValueForCLIPluginExtendsAPI<Extends>;
});
}
});

if (extendsHooks) {
Object.keys(extendsHooks!).forEach(hookName => {
extendsPluginApi[hookName] = (
extendsPluginApi[hookName as AllKeysForCLIPluginExtendsAPI<Extends>] = (
extendsHooks as Record<string, PluginHook<(...args: any[]) => any>>
)[hookName].tap;
)[hookName].tap as AllValueForCLIPluginExtendsAPI<Extends>;
});
}

Expand Down
43 changes: 32 additions & 11 deletions packages/toolkit/plugin-v2/src/runtime/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { merge } from '@modern-js/runtime-utils/merge';
import type { PluginHook, PluginHookTap } from '../types';
import type { PluginHook } from '../types';
import type { PluginManager } from '../types/plugin';
import type { RuntimePluginAPI } from '../types/runtime/api';
import type {
AllKeysForRuntimePluginExtendsAPI,
AllValueForRuntimePluginExtendsAPI,
RuntimePluginAPI,
RuntimePluginExtendsAPI,
} from '../types/runtime/api';
import type {
InternalRuntimeContext,
RuntimeContext,
Expand Down Expand Up @@ -52,30 +57,31 @@ export function initPluginAPI<Extends extends RuntimePluginExtends>({
throw new Error('Cannot access config');
}

const extendsPluginApi: Record<
string,
PluginHookTap<(...args: any[]) => any>
> = {};
const extendsPluginApi: Partial<RuntimePluginExtendsAPI<Extends>> = {};

plugins.forEach(plugin => {
const { _registryApi } = plugin;
if (_registryApi) {
const apis = _registryApi(getRuntimeContext, updateRuntimeContext);
Object.keys(apis).forEach(apiName => {
extendsPluginApi[apiName] = apis[apiName];
extendsPluginApi[apiName as keyof RuntimePluginExtendsAPI<Extends>] =
apis[
apiName
] as RuntimePluginExtendsAPI<Extends>[keyof RuntimePluginExtendsAPI<Extends>];
});
}
});

if (extendsHooks) {
Object.keys(extendsHooks!).forEach(hookName => {
extendsPluginApi[hookName] = (
extendsHooks as Record<string, PluginHook<(...args: any[]) => any>>
)[hookName].tap;
extendsPluginApi[hookName as AllKeysForRuntimePluginExtendsAPI<Extends>] =
(extendsHooks as Record<string, PluginHook<(...args: any[]) => any>>)[
hookName
].tap as AllValueForRuntimePluginExtendsAPI<Extends>;
});
}

return {
const pluginAPI = {
updateRuntimeContext,
getHooks,
getRuntimeConfig,
Expand All @@ -85,4 +91,19 @@ export function initPluginAPI<Extends extends RuntimePluginExtends>({
pickContext: hooks.pickContext.tap,
...extendsPluginApi,
};

return new Proxy(pluginAPI, {
get(target: Record<string, any>, prop: string) {
// hack then function to fix p-defer handle error
if (prop === 'then') {
return undefined;
}
if (prop in target) {
return target[prop];
}
return () => {
console.warn(`api.${prop.toString()} not exist`);
};
},
}) as RuntimePluginAPI<Extends>;
}
136 changes: 78 additions & 58 deletions packages/toolkit/plugin-v2/src/types/cli/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
OnDevCompileDoneFn,
} from '@rsbuild/core';
import type { Hooks } from '../../cli/hooks';
import type { PluginHookTap } from '../hooks';
import type { PluginHook, PluginHookTap } from '../hooks';
import type { DeepPartial } from '../utils';
import type { AppContext } from './context';
import type {
Expand Down Expand Up @@ -35,66 +35,86 @@ import type { CLIPluginExtends } from './plugin';
/**
* Define a generic CLI plugin API that provider can extend as needed.
*/
export type CLIPluginAPI<Extends extends CLIPluginExtends> = Readonly<{
isPluginExists: (name: string) => boolean;
getAppContext: () => Readonly<AppContext<Extends> & Extends['extendContext']>;
getConfig: () => Readonly<Extends['config']>;
getNormalizedConfig: () => Readonly<Extends['normalizedConfig']>;
getHooks: () => Readonly<
Hooks<
Extends['config'],
Extends['normalizedConfig'],
Extends['extendBuildUtils']
> &
Extends['extendHooks']
>;
export type CLIPluginAPI<Extends extends CLIPluginExtends> = Readonly<
{
isPluginExists: (name: string) => boolean;
getAppContext: () => Readonly<
AppContext<Extends> & Extends['extendContext']
>;
getConfig: () => Readonly<Extends['config']>;
getNormalizedConfig: () => Readonly<Extends['normalizedConfig']>;
getHooks: () => Readonly<
Hooks<
Extends['config'],
Extends['normalizedConfig'],
Extends['extendBuildUtils']
> &
Extends['extendHooks']
>;

updateAppContext: (
appContext: DeepPartial<AppContext<Extends> & Extends['extendContext']>,
) => void;
updateAppContext: (
appContext: DeepPartial<AppContext<Extends> & Extends['extendContext']>,
) => void;

// config hooks
config: PluginHookTap<ConfigFn<DeepPartial<Extends['config']>>>;
modifyConfig: PluginHookTap<ModifyConfigFn<Extends['config']>>;
modifyResolvedConfig: PluginHookTap<
ModifyResolvedConfigFn<Extends['normalizedConfig']>
>;
// config hooks
config: PluginHookTap<ConfigFn<DeepPartial<Extends['config']>>>;
modifyConfig: PluginHookTap<ModifyConfigFn<Extends['config']>>;
modifyResolvedConfig: PluginHookTap<
ModifyResolvedConfigFn<Extends['normalizedConfig']>
>;

// modify rsbuild config hooks
modifyRsbuildConfig: PluginHookTap<
ModifyRsbuildConfigFn<Extends['extendBuildUtils']>
>;
modifyBundlerChain: PluginHookTap<
ModifyBundlerChainFn<Extends['extendBuildUtils']>
>;
/** Only works when bundler is Rspack */
modifyRspackConfig: PluginHookTap<
ModifyRspackConfigFn<Extends['extendBuildUtils']>
>;
/** Only works when bundler is Webpack */
modifyWebpackChain: PluginHookTap<
ModifyWebpackChainFn<Extends['extendBuildUtils']>
>;
/** Only works when bundler is Webpack */
modifyWebpackConfig: PluginHookTap<
ModifyWebpackConfigFn<Extends['extendBuildUtils']>
// modify rsbuild config hooks
modifyRsbuildConfig: PluginHookTap<
ModifyRsbuildConfigFn<Extends['extendBuildUtils']>
>;
modifyBundlerChain: PluginHookTap<
ModifyBundlerChainFn<Extends['extendBuildUtils']>
>;
/** Only works when bundler is Rspack */
modifyRspackConfig: PluginHookTap<
ModifyRspackConfigFn<Extends['extendBuildUtils']>
>;
/** Only works when bundler is Webpack */
modifyWebpackChain: PluginHookTap<
ModifyWebpackChainFn<Extends['extendBuildUtils']>
>;
/** Only works when bundler is Webpack */
modifyWebpackConfig: PluginHookTap<
ModifyWebpackConfigFn<Extends['extendBuildUtils']>
>;
modifyHtmlPartials: PluginHookTap<ModifyHtmlPartialsFn>;

addCommand: PluginHookTap<AddCommandFn>;

onPrepare: PluginHookTap<OnPrepareFn>;
addWatchFiles: PluginHookTap<AddWatchFilesFn>;
onFileChanged: PluginHookTap<OnFileChangedFn>;
onBeforeRestart: PluginHookTap<OnBeforeRestartFn>;
onBeforeCreateCompiler: PluginHookTap<OnBeforeCreateCompilerFn>;
onAfterCreateCompiler: PluginHookTap<OnAfterCreateCompilerFn>;
onDevCompileDone: PluginHookTap<OnDevCompileDoneFn>;
onBeforeBuild: PluginHookTap<OnBeforeBuildFn>;
onAfterBuild: PluginHookTap<OnAfterBuildFn>;
onBeforeDev: PluginHookTap<OnBeforeDevFn>;
onAfterDev: PluginHookTap<OnAfterDevFn>;
onBeforeDeploy: PluginHookTap<OnBeforeDeployFn>;
onAfterDeploy: PluginHookTap<OnAfterDeployFn>;
onBeforeExit: PluginHookTap<OnBeforeExitFn>;
} & CLIPluginExtendsAPI<Extends>
>;

export type CLIPluginExtendsAPI<Extends extends CLIPluginExtends> = {
[K in keyof Extends['extendHooks']]: PluginHookTap<
Extends['extendHooks'][K] extends PluginHook<infer Args>
? Args extends (...args: any[]) => any
? Args
: (...args: any[]) => any
: (...args: any[]) => any
>;
modifyHtmlPartials: PluginHookTap<ModifyHtmlPartialsFn>;
} & Extends['extendApi'];

addCommand: PluginHookTap<AddCommandFn>;
export type AllKeysForCLIPluginExtendsAPI<Extends extends CLIPluginExtends> =
keyof CLIPluginExtendsAPI<Extends>;

onPrepare: PluginHookTap<OnPrepareFn>;
addWatchFiles: PluginHookTap<AddWatchFilesFn>;
onFileChanged: PluginHookTap<OnFileChangedFn>;
onBeforeRestart: PluginHookTap<OnBeforeRestartFn>;
onBeforeCreateCompiler: PluginHookTap<OnBeforeCreateCompilerFn>;
onAfterCreateCompiler: PluginHookTap<OnAfterCreateCompilerFn>;
onDevCompileDone: PluginHookTap<OnDevCompileDoneFn>;
onBeforeBuild: PluginHookTap<OnBeforeBuildFn>;
onAfterBuild: PluginHookTap<OnAfterBuildFn>;
onBeforeDev: PluginHookTap<OnBeforeDevFn>;
onAfterDev: PluginHookTap<OnAfterDevFn>;
onBeforeDeploy: PluginHookTap<OnBeforeDeployFn>;
onAfterDeploy: PluginHookTap<OnAfterDeployFn>;
onBeforeExit: PluginHookTap<OnBeforeExitFn>;
}>;
export type AllValueForCLIPluginExtendsAPI<Extends extends CLIPluginExtends> =
CLIPluginExtendsAPI<Extends>[AllKeysForCLIPluginExtendsAPI<Extends>];
Loading

0 comments on commit 48c11bf

Please sign in to comment.