From b2998d6d5e616064f4ec29898844630e472d0f7d Mon Sep 17 00:00:00 2001 From: caohuilin Date: Tue, 7 Jan 2025 14:37:38 +0800 Subject: [PATCH 01/19] feat: new runtime plugin define --- packages/toolkit/plugin-v2/package.json | 1 + packages/toolkit/plugin-v2/src/runtime/api.ts | 64 +++++++++++++++++++ .../toolkit/plugin-v2/src/runtime/context.ts | 45 +++++++++++++ .../toolkit/plugin-v2/src/runtime/hooks.ts | 21 ++++++ .../toolkit/plugin-v2/src/runtime/index.tsx | 3 + .../plugin-v2/src/types/runtime/api.ts | 24 +++++++ .../plugin-v2/src/types/runtime/context.ts | 21 ++++++ .../plugin-v2/src/types/runtime/hooks.ts | 17 +++++ .../plugin-v2/src/types/runtime/plugin.ts | 24 +++++++ 9 files changed, 220 insertions(+) create mode 100644 packages/toolkit/plugin-v2/src/runtime/api.ts create mode 100644 packages/toolkit/plugin-v2/src/runtime/context.ts create mode 100644 packages/toolkit/plugin-v2/src/runtime/hooks.ts create mode 100644 packages/toolkit/plugin-v2/src/runtime/index.tsx create mode 100644 packages/toolkit/plugin-v2/src/types/runtime/api.ts create mode 100644 packages/toolkit/plugin-v2/src/types/runtime/context.ts create mode 100644 packages/toolkit/plugin-v2/src/types/runtime/hooks.ts create mode 100644 packages/toolkit/plugin-v2/src/types/runtime/plugin.ts diff --git a/packages/toolkit/plugin-v2/package.json b/packages/toolkit/plugin-v2/package.json index e16a9504b436..05efbb1492f8 100644 --- a/packages/toolkit/plugin-v2/package.json +++ b/packages/toolkit/plugin-v2/package.json @@ -77,6 +77,7 @@ "@rsbuild/core": "1.1.13", "@scripts/build": "workspace:*", "@scripts/jest-config": "workspace:*", + "@types/react": "^18.3.11", "@types/jest": "^29", "@types/node": "^14", "jest": "^29", diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts new file mode 100644 index 000000000000..0fcf5437e627 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -0,0 +1,64 @@ +import { merge } from '@modern-js/utils/lodash'; +import type { PluginHookTap } from '../types'; +import type { PluginManager } from '../types/plugin'; +import type { RuntimePluginAPI } from '../types/runtime/api'; +import type { InternalContext, RuntimeContext } from '../types/runtime/context'; +import type { RuntimePluginExtends } from '../types/runtime/plugin'; +import type { DeepPartial } from '../types/utils'; + +export function initPluginAPI({ + context, +}: { + context: InternalContext; + pluginManager: PluginManager; +}): RuntimePluginAPI { + const { hooks, plugins } = context; + + function getRuntimeContext() { + if (context) { + const { hooks, extendsHooks, config, pluginAPI, ...runtimeContext } = + context; + return runtimeContext as RuntimeContext & + Extends['extendContext']; + } + throw new Error('Cannot access context'); + } + + function updateRuntimeContext( + updateContext: DeepPartial>, + ) { + context = merge(context, updateContext); + } + function getRuntimeConfig() { + if (context.config) { + return context.config; + } + throw new Error('Cannot access config'); + } + + const extendsPluginApi: Record< + string, + PluginHookTap<(...args: any[]) => any> + > = {}; + + plugins.forEach(plugin => { + const { _registryApi } = plugin; + if (_registryApi) { + const apis = _registryApi(getRuntimeContext, updateRuntimeContext); + Object.keys(apis).forEach(apiName => { + extendsPluginApi[apiName] = apis[apiName]; + }); + } + }); + + return { + getRuntimeContext, + updateRuntimeContext, + getRuntimeConfig, + modifyRuntimeConfig: hooks.modifyRuntimeConfig.tap, + onBeforeRender: hooks.onBeforeRender.tap, + wrapRoot: hooks.wrapRoot.tap, + pickContext: hooks.pickContext.tap, + ...extendsPluginApi, + }; +} diff --git a/packages/toolkit/plugin-v2/src/runtime/context.ts b/packages/toolkit/plugin-v2/src/runtime/context.ts new file mode 100644 index 000000000000..1f0cfd7919d5 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/context.ts @@ -0,0 +1,45 @@ +import type { PluginHook } from '../types/hooks'; +import type { InternalContext, RuntimeContext } from '../types/runtime/context'; +import type { + RuntimePlugin, + RuntimePluginExtends, +} from '../types/runtime/plugin'; +import { initHooks } from './hooks'; + +export interface RuntimeConfig { + plugins?: RuntimePlugin[]; +} + +export function initRuntimeContext< + Extends extends RuntimePluginExtends, +>(params: { plugins: RuntimePlugin[] }): RuntimeContext { + return { + plugins: params.plugins, + }; +} + +export function createRuntimeContext({ + runtimeContext, + config, +}: { + runtimeContext: RuntimeContext; + config: Extends['config']; +}): InternalContext { + const { plugins } = runtimeContext; + const extendsHooks: Record any>> = {}; + plugins.forEach(plugin => { + const { registryHooks = {} } = plugin; + Object.keys(registryHooks).forEach(hookName => { + extendsHooks[hookName] = registryHooks[hookName]; + }); + }); + return { + ...runtimeContext, + hooks: { + ...initHooks>(), + ...extendsHooks, + }, + extendsHooks, + config, + }; +} diff --git a/packages/toolkit/plugin-v2/src/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/runtime/hooks.ts new file mode 100644 index 000000000000..81c499b0e4f2 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/hooks.ts @@ -0,0 +1,21 @@ +import { createAsyncHook } from '../hooks'; +import type { + ModifyRuntimeConfigFn, + OnBeforeRenderFn, + PickContextFn, + WrapRootFn, +} from '../types/runtime/hooks'; + +export function initHooks() { + return { + onBeforeRender: createAsyncHook>(), + wrapRoot: createAsyncHook(), + pickContext: createAsyncHook>(), + modifyRuntimeConfig: + createAsyncHook>(), + }; +} + +export type Hooks = ReturnType< + typeof initHooks +>; diff --git a/packages/toolkit/plugin-v2/src/runtime/index.tsx b/packages/toolkit/plugin-v2/src/runtime/index.tsx new file mode 100644 index 000000000000..8eaccf9947f5 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/index.tsx @@ -0,0 +1,3 @@ +export { initPluginAPI } from './api'; +export { initRuntimeContext, createRuntimeContext } from './context'; +export { initHooks, type Hooks } from './hooks'; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/api.ts b/packages/toolkit/plugin-v2/src/types/runtime/api.ts new file mode 100644 index 000000000000..74b7ce769f56 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/types/runtime/api.ts @@ -0,0 +1,24 @@ +import type { PluginHookTap } from '../hooks'; +import type { DeepPartial } from '../utils'; +import type { RuntimeContext } from './context'; +import type { + ModifyRuntimeConfigFn, + OnBeforeRenderFn, + PickContextFn, + WrapRootFn, +} from './hooks'; +import type { RuntimePluginExtends } from './plugin'; + +export type RuntimePluginAPI = Readonly<{ + getRuntimeContext: () => Readonly< + RuntimeContext & Extends['extendContext'] + >; + updateRuntimeContext: ( + updateContext: DeepPartial>, + ) => void; + getRuntimeConfig: () => Readonly; + onBeforeRender: PluginHookTap>; + wrapRoot: PluginHookTap; + pickContext: PluginHookTap>>; + modifyRuntimeConfig: PluginHookTap>; +}>; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/context.ts b/packages/toolkit/plugin-v2/src/types/runtime/context.ts new file mode 100644 index 000000000000..0fec5855e6d5 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/types/runtime/context.ts @@ -0,0 +1,21 @@ +import type { Hooks } from '../../runtime/hooks'; +import type { RuntimePluginAPI } from './api'; +import type { RuntimePlugin, RuntimePluginExtends } from './plugin'; + +export interface RuntimeContext { + plugins: RuntimePlugin[]; +} + +export type InternalContext = + RuntimeContext & { + /** All hooks. */ + hooks: Hooks< + Extends['config'], + RuntimeContext & Extends['extendContext'] + > & + Extends['extendHooks']; + /** All plugin registry hooks */ + extendsHooks: Extends['extendHooks']; + config: Extends['config']; + pluginAPI?: RuntimePluginAPI; + }; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts new file mode 100644 index 000000000000..4bf75d6a44e8 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts @@ -0,0 +1,17 @@ +import type React from 'react'; + +export type OnBeforeRenderFn = ( + context: RuntimeContext, +) => Promise | void; + +export type WrapRootFn = ( + root: React.ComponentType, +) => React.ComponentType; + +export type PickContextFn = ( + context: RuntimeContext, +) => RuntimeContext; + +export type ModifyRuntimeConfigFn = ( + config: RuntimeConfig, +) => RuntimeConfig; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts b/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts new file mode 100644 index 000000000000..fdbee63620fe --- /dev/null +++ b/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts @@ -0,0 +1,24 @@ +import type { PluginHook } from '../hooks'; +import type { Plugin } from '../plugin'; +import type { RuntimePluginAPI } from './api'; +import type { RuntimeContext } from './context'; + +export interface RuntimePluginExtends< + Config extends Record = {}, + ExtendContext extends Record = {}, + ExtendAPI extends Record = {}, + ExtendHook extends Record any>> = {}, +> { + config?: Config; + extendContext?: ExtendContext; + extendApi?: ExtendAPI; + extendHooks?: ExtendHook; +} + +/** + * The type of the Runtime plugin object. + */ +export type RuntimePlugin = Plugin< + RuntimePluginAPI & Extends['extendApi'], + RuntimeContext & Extends['extendContext'] +>; From 3bbcfd4a1a2688604dbbd5ad9fe49a1d782d675d Mon Sep 17 00:00:00 2001 From: caohuilin Date: Tue, 7 Jan 2025 15:21:24 +0800 Subject: [PATCH 02/19] feat: support createAsyncInterruptHook --- packages/toolkit/plugin-v2/src/hooks.ts | 36 +++++++++++++++++++ packages/toolkit/plugin-v2/src/index.ts | 6 +++- packages/toolkit/plugin-v2/src/runtime/api.ts | 4 ++- .../toolkit/plugin-v2/src/runtime/context.ts | 7 ++-- .../toolkit/plugin-v2/src/runtime/hooks.ts | 11 ++++-- .../plugin-v2/src/types/runtime/hooks.ts | 1 + 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts index ef4c02e9484c..4823d2fcbed5 100644 --- a/packages/toolkit/plugin-v2/src/hooks.ts +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -1,6 +1,42 @@ import type { AsyncHook, CollectAsyncHook } from './types/hooks'; import type { UnwrapPromise } from './types/utils'; +export function createAsyncInterruptHook< + Callback extends (...args: any[]) => any, +>(): AsyncHook { + const callbacks: Callback[] = []; + + const tap = (cb: Callback) => { + callbacks.push(cb); + }; + + const call = async (...params: Parameters) => { + let interrupted = false; + let interruptResult: any; + + const interrupt = (info: any) => { + interrupted = true; + interruptResult = info; + }; + + for (const callback of callbacks) { + if (interrupted) break; + const result = await callback(...params, interrupt); + + if (result !== undefined) { + params[0] = result; + } + } + + return interrupted ? interruptResult : params[0] || []; + }; + + return { + tap, + call, + }; +} + export function createAsyncHook< Callback extends (...args: any[]) => any, >(): AsyncHook { diff --git a/packages/toolkit/plugin-v2/src/index.ts b/packages/toolkit/plugin-v2/src/index.ts index a236f8807b37..7e5bbbfa77dd 100644 --- a/packages/toolkit/plugin-v2/src/index.ts +++ b/packages/toolkit/plugin-v2/src/index.ts @@ -1,5 +1,9 @@ export { createPluginManager } from './manager'; -export { createAsyncHook, createCollectAsyncHook } from './hooks'; +export { + createAsyncHook, + createCollectAsyncHook, + createAsyncInterruptHook, +} from './hooks'; export type { Plugin, diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index 0fcf5437e627..18d6dbdb2e9d 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -25,7 +25,9 @@ export function initPluginAPI({ } function updateRuntimeContext( - updateContext: DeepPartial>, + updateContext: DeepPartial< + RuntimeContext & Extends['extendContext'] + >, ) { context = merge(context, updateContext); } diff --git a/packages/toolkit/plugin-v2/src/runtime/context.ts b/packages/toolkit/plugin-v2/src/runtime/context.ts index 1f0cfd7919d5..52e88d3abe74 100644 --- a/packages/toolkit/plugin-v2/src/runtime/context.ts +++ b/packages/toolkit/plugin-v2/src/runtime/context.ts @@ -22,7 +22,7 @@ export function createRuntimeContext({ runtimeContext, config, }: { - runtimeContext: RuntimeContext; + runtimeContext: RuntimeContext & Extends['extendContext']; config: Extends['config']; }): InternalContext { const { plugins } = runtimeContext; @@ -36,7 +36,10 @@ export function createRuntimeContext({ return { ...runtimeContext, hooks: { - ...initHooks>(), + ...initHooks< + Extends['config'], + RuntimeContext & Extends['extendContext'] + >(), ...extendsHooks, }, extendsHooks, diff --git a/packages/toolkit/plugin-v2/src/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/runtime/hooks.ts index 81c499b0e4f2..7901d3ccc424 100644 --- a/packages/toolkit/plugin-v2/src/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/runtime/hooks.ts @@ -1,4 +1,8 @@ -import { createAsyncHook } from '../hooks'; +import { + createAsyncHook, + createAsyncInterruptHook, + createCollectAsyncHook, +} from '../hooks'; import type { ModifyRuntimeConfigFn, OnBeforeRenderFn, @@ -8,11 +12,12 @@ import type { export function initHooks() { return { - onBeforeRender: createAsyncHook>(), + onBeforeRender: + createAsyncInterruptHook>(), wrapRoot: createAsyncHook(), pickContext: createAsyncHook>(), modifyRuntimeConfig: - createAsyncHook>(), + createCollectAsyncHook>(), }; } diff --git a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts index 4bf75d6a44e8..46433f3beb30 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts @@ -2,6 +2,7 @@ import type React from 'react'; export type OnBeforeRenderFn = ( context: RuntimeContext, + interrupt: (info: any) => void, ) => Promise | void; export type WrapRootFn = ( From 5489efb139f88d628b24266c700ca22759dcee06 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Tue, 7 Jan 2025 21:20:24 +0800 Subject: [PATCH 03/19] feat: runtime plugin run --- .../src/core/browser/hydrate.tsx | 2 +- .../plugin-runtime/src/core/browser/index.tsx | 12 ++-- .../plugin-runtime/src/core/compatible.tsx | 31 +++++----- .../plugin-runtime/src/core/context/index.ts | 20 ++++++- .../src/core/context/runtime.ts | 10 +--- .../runtime/plugin-runtime/src/core/index.ts | 2 - .../plugin-runtime/src/core/plugin/base.ts | 21 +------ .../plugin-runtime/src/core/plugin/index.ts | 35 +++++------ .../plugin-runtime/src/core/plugin/runner.ts | 16 ----- .../plugin-runtime/src/core/plugin/types.ts | 17 ++++++ .../plugin-runtime/src/core/react/index.tsx | 8 +-- .../src/core/server/requestHandler.ts | 13 ++-- .../src/router/runtime/plugin.node.tsx | 7 ++- .../builder/src/addons/components/modern.tsx | 2 +- packages/toolkit/plugin-v2/package.json | 13 ++-- .../toolkit/plugin-v2/src/cli/run/create.ts | 9 ++- packages/toolkit/plugin-v2/src/hooks.ts | 29 ++++++++- packages/toolkit/plugin-v2/src/index.ts | 7 +++ packages/toolkit/plugin-v2/src/manager.ts | 13 ++-- packages/toolkit/plugin-v2/src/runtime/api.ts | 23 +++++--- .../toolkit/plugin-v2/src/runtime/context.ts | 22 +++---- .../toolkit/plugin-v2/src/runtime/hooks.ts | 3 +- .../toolkit/plugin-v2/src/runtime/index.tsx | 1 + .../plugin-v2/src/runtime/run/create.ts | 59 +++++++++++++++++++ .../plugin-v2/src/runtime/run/index.ts | 3 + .../plugin-v2/src/runtime/run/types.ts | 10 ++++ packages/toolkit/plugin-v2/src/types/hooks.ts | 6 ++ packages/toolkit/plugin-v2/src/types/index.ts | 6 ++ .../toolkit/plugin-v2/src/types/plugin.ts | 5 +- .../plugin-v2/src/types/runtime/api.ts | 10 +--- .../plugin-v2/src/types/runtime/context.ts | 15 ++--- .../plugin-v2/src/types/runtime/hooks.ts | 4 +- .../plugin-v2/src/types/runtime/index.ts | 3 + .../plugin-v2/src/types/runtime/plugin.ts | 2 +- 34 files changed, 277 insertions(+), 162 deletions(-) delete mode 100644 packages/runtime/plugin-runtime/src/core/plugin/runner.ts create mode 100644 packages/runtime/plugin-runtime/src/core/plugin/types.ts create mode 100644 packages/toolkit/plugin-v2/src/runtime/run/create.ts create mode 100644 packages/toolkit/plugin-v2/src/runtime/run/index.ts create mode 100644 packages/toolkit/plugin-v2/src/runtime/run/types.ts create mode 100644 packages/toolkit/plugin-v2/src/types/runtime/index.ts diff --git a/packages/runtime/plugin-runtime/src/core/browser/hydrate.tsx b/packages/runtime/plugin-runtime/src/core/browser/hydrate.tsx index 1df5bafdb67f..19c9e566545e 100644 --- a/packages/runtime/plugin-runtime/src/core/browser/hydrate.tsx +++ b/packages/runtime/plugin-runtime/src/core/browser/hydrate.tsx @@ -3,7 +3,7 @@ import { normalizePathname } from '@modern-js/runtime-utils/url'; import type React from 'react'; import type { Root } from 'react-dom/client'; import { RenderLevel } from '../constants'; -import type { RuntimeContext } from '../context'; +import type { RuntimeContext } from '../context/runtime'; import { wrapRuntimeContextProvider } from '../react/wrapper'; import { WithCallback } from './withCallback'; diff --git a/packages/runtime/plugin-runtime/src/core/browser/index.tsx b/packages/runtime/plugin-runtime/src/core/browser/index.tsx index 8f4694db9056..c8f2d3b9f155 100644 --- a/packages/runtime/plugin-runtime/src/core/browser/index.tsx +++ b/packages/runtime/plugin-runtime/src/core/browser/index.tsx @@ -1,9 +1,8 @@ import cookieTool from 'cookie'; import type React from 'react'; -import { getGlobalAppInit } from '../context'; +import { getGlobalAppInit, getGlobalInternalRuntimeContext } from '../context'; import { type RuntimeContext, getInitialContext } from '../context/runtime'; import { createLoaderManager } from '../loader/loaderManager'; -import { getGlobalRunner } from '../plugin/runner'; import { wrapRuntimeContextProvider } from '../react/wrapper'; import type { SSRContainer } from '../types'; import { hydrateRoot } from './hydrate'; @@ -82,10 +81,13 @@ export async function render( App: React.ReactElement, id?: HTMLElement | string, ) { - const runner = getGlobalRunner(); - const context: RuntimeContext = getInitialContext(runner); + const context: RuntimeContext = getInitialContext(); const runBeforeRender = async (context: RuntimeContext) => { - await runner.beforeRender(context); + const internalRuntimeContext = getGlobalInternalRuntimeContext(); + const api = internalRuntimeContext?.pluginAPI; + api?.updateRuntimeContext(context); + const hooks = internalRuntimeContext?.hooks; + await hooks.onBeforeRender.call(context, info => info); const init = getGlobalAppInit(); return init?.(context); }; diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index 6fcfc5d0aa31..c563b3679b29 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -1,28 +1,29 @@ +import type { RuntimePlugin } from '@modern-js/plugin-v2'; +import { runtime } from '@modern-js/plugin-v2/runtime'; import { ROUTE_MANIFEST } from '@modern-js/utils/universal/constants'; import React, { useContext, useMemo } from 'react'; import type { Renderer } from 'react-dom'; import type { createRoot, hydrateRoot } from 'react-dom/client'; import { hydrateRoot as ModernHydrateRoot } from './browser/hydrate'; -import { getGlobalAppInit } from './context'; +import { getGlobalAppInit, getGlobalInternalRuntimeContext } from './context'; import { type RuntimeContext, RuntimeReactContext, type TRuntimeContext, } from './context/runtime'; import { createLoaderManager } from './loader/loaderManager'; -import { type Plugin, registerPlugin, type runtime } from './plugin'; -import { getGlobalRunner } from './plugin/runner'; +import { registerPlugin } from './plugin'; +import type { RuntimeExtends } from './plugin/types'; import { wrapRuntimeContextProvider } from './react/wrapper'; import type { TSSRContext } from './types'; const IS_REACT18 = process.env.IS_REACT18 === 'true'; export type CreateAppOptions = { - plugins: Plugin[]; + plugins: RuntimePlugin[]; /** * In the test cases, we need to execute multiple createApp instances simultaneously, and they must not share the runtime. */ - runtime?: typeof runtime; props?: any; }; @@ -35,11 +36,8 @@ function isClientArgs( ); } -type PluginRunner = ReturnType; - -const getInitialContext = (runner: PluginRunner) => ({ +const getInitialContext = () => ({ loaderManager: createLoaderManager({}), - runner, isBrowser: true, routeManifest: typeof window !== 'undefined' && (window as any)[ROUTE_MANIFEST], @@ -47,10 +45,10 @@ const getInitialContext = (runner: PluginRunner) => ({ export const createApp = ({ plugins, - runtime, props: globalProps, }: CreateAppOptions) => { - const runner = registerPlugin(plugins, { plugins: [] }, runtime); + const context = registerPlugin(plugins, { plugins: [] }, runtime); + const hooks = context.hooks; return (App?: React.ComponentType) => { const WrapperComponent: React.ComponentType = props => { @@ -70,7 +68,7 @@ export const createApp = ({ ); }; - const WrapperApp = runner.wrapRoot(WrapperComponent); + const WrapperApp = hooks.wrapRoot.call(WrapperComponent); const WrapComponent = (props: any) => { const mergedProps = { ...props, ...globalProps }; return ; @@ -106,12 +104,15 @@ export const bootstrap: BootStrap = async ( ReactDOM, ) => { const App = BootApp; - const runner = getGlobalRunner(); + const internalRuntimeContext = getGlobalInternalRuntimeContext(); + const api = internalRuntimeContext.pluginAPI; + const hooks = internalRuntimeContext.hooks; - const context: RuntimeContext = getInitialContext(runner); + const context: RuntimeContext = getInitialContext(); + api?.updateRuntimeContext(context); const runBeforeRender = async (context: RuntimeContext) => { - await runner.beforeRender(context); + await hooks.onBeforeRender.call(context, info => info); const init = getGlobalAppInit(); return init?.(context); }; diff --git a/packages/runtime/plugin-runtime/src/core/context/index.ts b/packages/runtime/plugin-runtime/src/core/context/index.ts index 39928da3bb66..537583c00eec 100644 --- a/packages/runtime/plugin-runtime/src/core/context/index.ts +++ b/packages/runtime/plugin-runtime/src/core/context/index.ts @@ -1,9 +1,11 @@ +import type { InternalRuntimeContext } from '@modern-js/plugin-v2'; import type { NestedRoute, PageRoute } from '@modern-js/types'; import type { AppConfig } from '../../common'; +import type { RuntimeExtends } from '../plugin/types'; export { - RuntimeReactContext, type RuntimeContext, + RuntimeReactContext, getInitialContext, } from './runtime'; @@ -28,12 +30,16 @@ interface GlobalContext { * page router _app.tsx export layout app */ layoutApp?: React.ComponentType; + + internalRuntimeContext?: InternalRuntimeContext; } const globalContext: GlobalContext = {}; export function setGlobalContext( - context: Omit & { appConfig?: () => AppConfig }, + context: Omit & { + appConfig?: () => AppConfig; + }, ) { globalContext.App = context.App; globalContext.routes = context.routes; @@ -45,6 +51,16 @@ export function setGlobalContext( globalContext.layoutApp = context.layoutApp; } +export function setGlobalInternalRuntimeContext( + context: InternalRuntimeContext, +) { + globalContext.internalRuntimeContext = context; +} + +export function getGlobalInternalRuntimeContext() { + return globalContext.internalRuntimeContext!; +} + export function getGlobalApp() { return globalContext.App; } diff --git a/packages/runtime/plugin-runtime/src/core/context/runtime.ts b/packages/runtime/plugin-runtime/src/core/context/runtime.ts index 2b229fe9032a..c9c3df38e813 100644 --- a/packages/runtime/plugin-runtime/src/core/context/runtime.ts +++ b/packages/runtime/plugin-runtime/src/core/context/runtime.ts @@ -1,21 +1,15 @@ import type { Store } from '@modern-js-reduck/store'; -import type { - Router, - RouterState, - StaticHandlerContext, -} from '@modern-js/runtime-utils/remix-router'; +import type { StaticHandlerContext } from '@modern-js/runtime-utils/remix-router'; import { ROUTE_MANIFEST } from '@modern-js/utils/universal/constants'; import { createContext } from 'react'; import type { RouteManifest } from '../../router/runtime/types'; import { createLoaderManager } from '../loader/loaderManager'; -import type { PluginRunner, runtime } from '../plugin'; import type { SSRServerContext, TSSRContext } from '../types'; interface BaseRuntimeContext { initialData?: Record; loaderManager: ReturnType; isBrowser: boolean; - runner: ReturnType; // ssr type ssrContext?: SSRServerContext; // state type @@ -50,12 +44,10 @@ export interface TRuntimeContext extends Partial { } export const getInitialContext = ( - runner: PluginRunner, isBrowser = true, routeManifest?: RouteManifest, ): RuntimeContext => ({ loaderManager: createLoaderManager({}), - runner, isBrowser, routeManifest: routeManifest || diff --git a/packages/runtime/plugin-runtime/src/core/index.ts b/packages/runtime/plugin-runtime/src/core/index.ts index 495a7fa12c0e..bc12cc859f58 100644 --- a/packages/runtime/plugin-runtime/src/core/index.ts +++ b/packages/runtime/plugin-runtime/src/core/index.ts @@ -1,5 +1,3 @@ -export { runtime } from './plugin'; - export type { Plugin } from './plugin'; export { defineConfig, getConfig, defineRuntimeConfig } from './config'; diff --git a/packages/runtime/plugin-runtime/src/core/plugin/base.ts b/packages/runtime/plugin-runtime/src/core/plugin/base.ts index b7c16664697a..139c5abdb883 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/base.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/base.ts @@ -2,18 +2,12 @@ import { type PluginOptions, type Setup, createAsyncInterruptWorkflow, - createContext, - createManager, createSyncParallelWorkflow, createWaterfall, } from '@modern-js/plugin'; import type { RuntimeContext, TRuntimeContext } from '../context/runtime'; -import type { RuntimeConfig } from './index'; - -export const RuntimeConfigContext = createContext({}); - -export const useRuntimeConfigContext = () => RuntimeConfigContext.use().value; +import type { RuntimeConfig } from './types'; // biome-ignore lint/suspicious/noEmptyInterface: export interface AppProps {} @@ -39,24 +33,13 @@ const runtimeHooks = { modifyRuntimeConfig, }; -const runtimePluginAPI = { - useRuntimeConfigContext, -}; - /** All hooks of runtime plugin. */ export type RuntimeHooks = typeof runtimeHooks; -export type RuntimePluginAPI = typeof runtimePluginAPI; +export type RuntimePluginAPI = { useRuntimeConfigContext: () => RuntimeConfig }; /** Plugin options of a runtime plugin. */ export type Plugin = PluginOptions< RuntimeHooks, Setup >; - -export const createRuntime = () => - createManager(runtimeHooks, runtimePluginAPI); - -export const runtime = createRuntime(); - -export type PluginRunner = ReturnType; diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index cbd0437d3e0b..abfcb5079028 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -1,31 +1,26 @@ +import type { InternalRuntimeContext, Plugin } from '@modern-js/plugin-v2'; +import { runtime } from '@modern-js/plugin-v2/runtime'; import { merge } from '@modern-js/runtime-utils/merge'; -import { type Plugin, RuntimeConfigContext, runtime } from './base'; -import { getGlobalRunner, setGlobalRunner } from './runner'; +import { setGlobalInternalRuntimeContext } from '../context'; +import type { RuntimeConfig, RuntimeExtends, RuntimePlugin } from './types'; -export * from './base'; - -export interface RuntimeConfig { - plugins?: Plugin[]; -} - -function setupConfigContext() { - const runner = getGlobalRunner(); - const configs = runner.modifyRuntimeConfig(); - RuntimeConfigContext.set(merge({}, ...configs)); -} +// old type +export type { Plugin } from './base'; export function registerPlugin( - internalPlugins: Plugin[], + internalPlugins: RuntimePlugin[], runtimeConfig?: RuntimeConfig, customRuntime?: typeof runtime, ) { const { plugins = [] } = runtimeConfig || {}; - (customRuntime || runtime).usePlugin(...internalPlugins, ...plugins); - const runner = (customRuntime || runtime).init(); - // It is necessary to execute init after usePlugin, so that the plugin can be registered successfully. - setGlobalRunner(runner); - setupConfigContext(); - return runner; + const { runtimeContext } = (customRuntime || runtime).init({ + plugins: [...internalPlugins, ...plugins] as Plugin[], + config: runtimeConfig || {}, + }); + setGlobalInternalRuntimeContext( + runtimeContext as unknown as InternalRuntimeContext, + ); + return runtimeContext; } export function mergeConfig( diff --git a/packages/runtime/plugin-runtime/src/core/plugin/runner.ts b/packages/runtime/plugin-runtime/src/core/plugin/runner.ts deleted file mode 100644 index 06814f912ca1..000000000000 --- a/packages/runtime/plugin-runtime/src/core/plugin/runner.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { type PluginRunner, runtime } from './base'; - -let globalRunner: PluginRunner; - -export function setGlobalRunner(runner: PluginRunner) { - globalRunner = runner; -} - -export function getGlobalRunner() { - if (globalRunner) { - return globalRunner; - } - const runner = runtime.init(); - setGlobalRunner(runner); - return runner; -} diff --git a/packages/runtime/plugin-runtime/src/core/plugin/types.ts b/packages/runtime/plugin-runtime/src/core/plugin/types.ts new file mode 100644 index 000000000000..7d33ec09f817 --- /dev/null +++ b/packages/runtime/plugin-runtime/src/core/plugin/types.ts @@ -0,0 +1,17 @@ +import type { + RuntimePlugin as BaseRuntimePlugin, + RuntimePluginExtends, +} from '@modern-js/plugin-v2'; +import type { Hooks } from '@modern-js/plugin-v2/runtime'; +import type { RuntimeContext } from '../context/runtime'; + +export type RuntimeHooks = Hooks; + +export type RuntimeExtends = Required< + RuntimePluginExtends +>; + +export type RuntimePlugin = BaseRuntimePlugin; +export interface RuntimeConfig { + plugins?: RuntimePlugin[]; +} diff --git a/packages/runtime/plugin-runtime/src/core/react/index.tsx b/packages/runtime/plugin-runtime/src/core/react/index.tsx index a25fcb317f65..7961de6402be 100644 --- a/packages/runtime/plugin-runtime/src/core/react/index.tsx +++ b/packages/runtime/plugin-runtime/src/core/react/index.tsx @@ -2,8 +2,7 @@ import { parsedJSONFromElement } from '@modern-js/runtime-utils/parsed'; import type React from 'react'; import { isBrowser } from '../../common'; import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID } from '../constants'; -import { getGlobalApp } from '../context'; -import { getGlobalRunner } from '../plugin/runner'; +import { getGlobalApp, getGlobalInternalRuntimeContext } from '../context'; export function createRoot(UserApp?: React.ComponentType | null) { const App = UserApp || getGlobalApp(); @@ -17,10 +16,11 @@ export function createRoot(UserApp?: React.ComponentType | null) { window._ROUTER_DATA || parsedJSONFromElement(ROUTER_DATA_JSON_ID); } - const runner = getGlobalRunner(); + const internalRuntimeContext = getGlobalInternalRuntimeContext(); + const hooks = internalRuntimeContext.hooks; /** * when use routes entry, after running router plugin, the App will be define */ - const WrapperApp = runner.wrapRoot(App!); + const WrapperApp = hooks.wrapRoot.call(App!); return WrapperApp; } diff --git a/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts b/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts index f833892b6392..cb08033eafda 100644 --- a/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts +++ b/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts @@ -9,10 +9,13 @@ import { parseQuery, } from '@modern-js/runtime-utils/universal/request'; import type React from 'react'; -import { type RuntimeContext, getGlobalAppInit } from '../context'; +import { + type RuntimeContext, + getGlobalAppInit, + getGlobalInternalRuntimeContext, +} from '../context'; import { getInitialContext } from '../context/runtime'; import { createLoaderManager } from '../loader/loaderManager'; -import { getGlobalRunner } from '../plugin/runner'; import { createRoot } from '../react'; import type { SSRServerContext } from '../types'; import { CHUNK_CSS_PLACEHOLDER } from './constants'; @@ -148,19 +151,19 @@ export const createRequestHandler: CreateRequestHandler = const requestHandler: RequestHandler = async (request, options) => { const Root = createRoot(); - const runner = getGlobalRunner(); + const internalRuntimeContext = getGlobalInternalRuntimeContext(); + const hooks = internalRuntimeContext.hooks; const { routeManifest } = options.resource; const context: RuntimeContext = getInitialContext( - runner, false, routeManifest as any, ); const runBeforeRender = async (context: RuntimeContext) => { // when router is redirect, beforeRender will return a response - const result = await runner.beforeRender(context); + const result = await hooks.onBeforeRender.call(context, info => info); if (typeof Response !== 'undefined' && result instanceof Response) { return result; } diff --git a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx index 59cad96aa8bd..3d75786b56b7 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx +++ b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx @@ -15,7 +15,7 @@ import type React from 'react'; import { useContext } from 'react'; import { JSX_SHELL_STREAM_END_MARK } from '../../common'; import { RuntimeReactContext } from '../../core'; -import type { Plugin } from '../../core'; +import type { Plugin, RuntimeContext } from '../../core'; import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context'; import DeferredDataScripts from './DeferredDataScripts.node'; import { @@ -50,7 +50,10 @@ export const routerPlugin = ( let finalRouteConfig: any = {}; return { - async beforeRender(context, interrupt) { + async beforeRender( + context: RuntimeContext, + interrupt: (info: any) => any, + ) { const pluginConfig: Record = api.useRuntimeConfigContext(); const { diff --git a/packages/storybook/builder/src/addons/components/modern.tsx b/packages/storybook/builder/src/addons/components/modern.tsx index 589485255a36..cdcfddaa6e0a 100644 --- a/packages/storybook/builder/src/addons/components/modern.tsx +++ b/packages/storybook/builder/src/addons/components/modern.tsx @@ -7,7 +7,7 @@ import type { IConfig } from '../type'; export const WrapProviders = (storyFn: any, config: IConfig) => { const App = createApp({ - plugins: resolvePlugins(config.modernConfigRuntime), + plugins: resolvePlugins(config.modernConfigRuntime) as any, })(storyFn); return ; diff --git a/packages/toolkit/plugin-v2/package.json b/packages/toolkit/plugin-v2/package.json index 05efbb1492f8..16448f98ab15 100644 --- a/packages/toolkit/plugin-v2/package.json +++ b/packages/toolkit/plugin-v2/package.json @@ -30,11 +30,6 @@ }, "default": "./dist/esm/index.js" }, - "./types": { - "types": "./dist/types/types/index.d.ts", - "jsnext:source": "./src/types/index.ts", - "default": "./dist/cjs/types/index.js" - }, "./run": { "types": "./dist/types/cli/run/run.d.ts", "jsnext:source": "./src/cli/run/run.ts", @@ -44,6 +39,11 @@ "types": "./dist/types/cli/index.d.ts", "jsnext:source": "./src/cli/index.ts", "default": "./dist/cjs/cli/index.js" + }, + "./runtime": { + "types": "./dist/types/runtime/index.d.ts", + "jsnext:source": "./src/runtime/index.ts", + "default": "./dist/cjs/runtime/index.js" } }, "typesVersions": { @@ -56,6 +56,9 @@ ], "cli": [ "./dist/types/cli/index.d.ts" + ], + "runtime": [ + "./dist/types/runtime/index.d.ts" ] } }, diff --git a/packages/toolkit/plugin-v2/src/cli/run/create.ts b/packages/toolkit/plugin-v2/src/cli/run/create.ts index 51a71f9223f2..9f673ccf1c25 100644 --- a/packages/toolkit/plugin-v2/src/cli/run/create.ts +++ b/packages/toolkit/plugin-v2/src/cli/run/create.ts @@ -1,4 +1,4 @@ -import { logger } from '@modern-js/utils'; +import { createDebugger, logger } from '@modern-js/utils'; import { program } from '@modern-js/utils/commander'; import { createPluginManager } from '../../manager'; import type { CLIPlugin, CLIPluginExtends } from '../../types/cli/plugin'; @@ -15,6 +15,8 @@ import { createFileWatcher } from './utils/createFileWatcher'; import { initAppDir } from './utils/initAppDir'; import { loadEnv } from './utils/loadEnv'; +const debug = createDebugger('plugin-v2'); + export const createCli = () => { let initOptions: CLIRunOptions; const pluginManager = createPluginManager(); @@ -62,6 +64,11 @@ export const createCli = () => { const plugins = (await pluginManager.getPlugins()) as CLIPlugin[]; + debug( + 'CLI Plugins:', + plugins.map(p => p.name), + ); + const context = await createContext({ appContext: initAppContext({ packageName: loaded.packageName, diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts index 4823d2fcbed5..d11be2771e62 100644 --- a/packages/toolkit/plugin-v2/src/hooks.ts +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -1,4 +1,4 @@ -import type { AsyncHook, CollectAsyncHook } from './types/hooks'; +import type { AsyncHook, CollectAsyncHook, SyncHook } from './types/hooks'; import type { UnwrapPromise } from './types/utils'; export function createAsyncInterruptHook< @@ -37,6 +37,33 @@ export function createAsyncInterruptHook< }; } +export function createSyncHook< + Callback extends (...args: any[]) => any, +>(): SyncHook { + const callbacks: Callback[] = []; + + const tap = (cb: Callback) => { + callbacks.push(cb); + }; + + const call = (...params: Parameters) => { + for (const callback of callbacks) { + const result = callback(...params); + + if (result !== undefined) { + params[0] = result; + } + } + + return params[0] || []; + }; + + return { + tap, + call, + }; +} + export function createAsyncHook< Callback extends (...args: any[]) => any, >(): AsyncHook { diff --git a/packages/toolkit/plugin-v2/src/index.ts b/packages/toolkit/plugin-v2/src/index.ts index 7e5bbbfa77dd..ec19b3a0afd7 100644 --- a/packages/toolkit/plugin-v2/src/index.ts +++ b/packages/toolkit/plugin-v2/src/index.ts @@ -18,6 +18,13 @@ export type { CLIPlugin, CLIPluginExtends, } from './types/cli'; +export type { + RuntimePluginAPI, + RuntimeContext, + InternalRuntimeContext, + RuntimePlugin, + RuntimePluginExtends, +} from './types/runtime'; export type { AsyncHook, CollectAsyncHook, diff --git a/packages/toolkit/plugin-v2/src/manager.ts b/packages/toolkit/plugin-v2/src/manager.ts index 0eaebb97d2a1..a26721d4ca72 100644 --- a/packages/toolkit/plugin-v2/src/manager.ts +++ b/packages/toolkit/plugin-v2/src/manager.ts @@ -1,8 +1,8 @@ -import { isFunction, logger } from '@modern-js/utils'; -import { debug } from './cli/run/utils/debug'; +import { isFunction } from '@modern-js/utils/lodash'; import type { Plugin, PluginManager } from './types/plugin'; import type { Falsy } from './types/utils'; -// Validates if the plugin is a valid CLIPlugin instance + +// Validates if the plugin is a valid plugin instance function validatePlugin(plugin: unknown) { const type = typeof plugin; @@ -75,7 +75,7 @@ export function createPluginManager(): PluginManager { validatePlugin(newPlugin); const { name, usePlugins = [], pre = [], post = [] } = newPlugin; if (plugins.has(name)) { - logger.warn(`Plugin ${name} already exists.`); + console.warn(`Plugin ${name} already exists.`); return; } plugins.set(name, newPlugin); @@ -153,11 +153,6 @@ export function createPluginManager(): PluginManager { result = result.filter(result => result); - debug( - 'CLI Plugins:', - result.map(p => p.name), - ); - return result; }; diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index 18d6dbdb2e9d..0792ea447af0 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -2,32 +2,37 @@ import { merge } from '@modern-js/utils/lodash'; import type { PluginHookTap } from '../types'; import type { PluginManager } from '../types/plugin'; import type { RuntimePluginAPI } from '../types/runtime/api'; -import type { InternalContext, RuntimeContext } from '../types/runtime/context'; -import type { RuntimePluginExtends } from '../types/runtime/plugin'; +import type { + InternalRuntimeContext, + RuntimeContext, +} from '../types/runtime/context'; +import type { + RuntimePlugin, + RuntimePluginExtends, +} from '../types/runtime/plugin'; import type { DeepPartial } from '../types/utils'; export function initPluginAPI({ context, + plugins, }: { - context: InternalContext; + context: InternalRuntimeContext; pluginManager: PluginManager; + plugins: RuntimePlugin[]; }): RuntimePluginAPI { - const { hooks, plugins } = context; + const { hooks } = context; function getRuntimeContext() { if (context) { const { hooks, extendsHooks, config, pluginAPI, ...runtimeContext } = context; - return runtimeContext as RuntimeContext & - Extends['extendContext']; + return runtimeContext as RuntimeContext & Extends['extendContext']; } throw new Error('Cannot access context'); } function updateRuntimeContext( - updateContext: DeepPartial< - RuntimeContext & Extends['extendContext'] - >, + updateContext: DeepPartial, ) { context = merge(context, updateContext); } diff --git a/packages/toolkit/plugin-v2/src/runtime/context.ts b/packages/toolkit/plugin-v2/src/runtime/context.ts index 52e88d3abe74..5343122e2b66 100644 --- a/packages/toolkit/plugin-v2/src/runtime/context.ts +++ b/packages/toolkit/plugin-v2/src/runtime/context.ts @@ -1,5 +1,8 @@ import type { PluginHook } from '../types/hooks'; -import type { InternalContext, RuntimeContext } from '../types/runtime/context'; +import type { + InternalRuntimeContext, + RuntimeContext, +} from '../types/runtime/context'; import type { RuntimePlugin, RuntimePluginExtends, @@ -10,22 +13,19 @@ export interface RuntimeConfig { plugins?: RuntimePlugin[]; } -export function initRuntimeContext< - Extends extends RuntimePluginExtends, ->(params: { plugins: RuntimePlugin[] }): RuntimeContext { - return { - plugins: params.plugins, - }; +export function initRuntimeContext(): RuntimeContext { + return {}; } export function createRuntimeContext({ runtimeContext, config, + plugins, }: { - runtimeContext: RuntimeContext & Extends['extendContext']; + runtimeContext: RuntimeContext & Extends['extendContext']; config: Extends['config']; -}): InternalContext { - const { plugins } = runtimeContext; + plugins: RuntimePlugin[]; +}): InternalRuntimeContext { const extendsHooks: Record any>> = {}; plugins.forEach(plugin => { const { registryHooks = {} } = plugin; @@ -38,7 +38,7 @@ export function createRuntimeContext({ hooks: { ...initHooks< Extends['config'], - RuntimeContext & Extends['extendContext'] + RuntimeContext & Extends['extendContext'] >(), ...extendsHooks, }, diff --git a/packages/toolkit/plugin-v2/src/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/runtime/hooks.ts index 7901d3ccc424..849d82be1c99 100644 --- a/packages/toolkit/plugin-v2/src/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/runtime/hooks.ts @@ -2,6 +2,7 @@ import { createAsyncHook, createAsyncInterruptHook, createCollectAsyncHook, + createSyncHook, } from '../hooks'; import type { ModifyRuntimeConfigFn, @@ -14,7 +15,7 @@ export function initHooks() { return { onBeforeRender: createAsyncInterruptHook>(), - wrapRoot: createAsyncHook(), + wrapRoot: createSyncHook(), pickContext: createAsyncHook>(), modifyRuntimeConfig: createCollectAsyncHook>(), diff --git a/packages/toolkit/plugin-v2/src/runtime/index.tsx b/packages/toolkit/plugin-v2/src/runtime/index.tsx index 8eaccf9947f5..6c9823784245 100644 --- a/packages/toolkit/plugin-v2/src/runtime/index.tsx +++ b/packages/toolkit/plugin-v2/src/runtime/index.tsx @@ -1,3 +1,4 @@ export { initPluginAPI } from './api'; export { initRuntimeContext, createRuntimeContext } from './context'; export { initHooks, type Hooks } from './hooks'; +export { runtime } from './run'; diff --git a/packages/toolkit/plugin-v2/src/runtime/run/create.ts b/packages/toolkit/plugin-v2/src/runtime/run/create.ts new file mode 100644 index 000000000000..e177234bf97f --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/run/create.ts @@ -0,0 +1,59 @@ +import { createPluginManager } from '../../manager'; +import { initPluginAPI } from '../../runtime/api'; +import { + createRuntimeContext, + initRuntimeContext, +} from '../../runtime/context'; +import type { + RuntimePlugin, + RuntimePluginExtends, +} from '../../types/runtime/plugin'; +import type { RuntimeRunOptions } from './types'; + +export const createRuntime = () => { + let initOptions: RuntimeRunOptions; + const pluginManager = createPluginManager(); + + function init(options: RuntimeRunOptions) { + pluginManager.clear(); + initOptions = options; + + const { plugins: allPlugins, handleSetupResult } = options; + + pluginManager.addPlugins(allPlugins); + + const plugins = pluginManager.getPlugins() as RuntimePlugin[]; + + const context = createRuntimeContext({ + runtimeContext: initRuntimeContext(), + config: initOptions.config, + plugins, + }); + + const pluginAPI = initPluginAPI({ + context, + pluginManager, + plugins, + }); + + context.pluginAPI = pluginAPI; + + for (const plugin of plugins) { + const setupResult = plugin.setup?.(pluginAPI); + if (handleSetupResult) { + handleSetupResult(setupResult, pluginAPI); + } + } + return { runtimeContext: context }; + } + + async function run(options: RuntimeRunOptions) { + await init(options); + } + + return { + init, + run, + getPrevInitOptions: () => initOptions, + }; +}; diff --git a/packages/toolkit/plugin-v2/src/runtime/run/index.ts b/packages/toolkit/plugin-v2/src/runtime/run/index.ts new file mode 100644 index 000000000000..9b2db7489b6f --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/run/index.ts @@ -0,0 +1,3 @@ +import { createRuntime } from './create'; + +export const runtime = createRuntime(); diff --git a/packages/toolkit/plugin-v2/src/runtime/run/types.ts b/packages/toolkit/plugin-v2/src/runtime/run/types.ts new file mode 100644 index 000000000000..ead4a1c6e4aa --- /dev/null +++ b/packages/toolkit/plugin-v2/src/runtime/run/types.ts @@ -0,0 +1,10 @@ +import type { Plugin } from '../../types/plugin'; + +export type RuntimeRunOptions = { + plugins: Plugin[]; + handleSetupResult?: ( + params: any, + api: Record, + ) => Promise | void; + config: Record; +}; diff --git a/packages/toolkit/plugin-v2/src/types/hooks.ts b/packages/toolkit/plugin-v2/src/types/hooks.ts index 90224db0893e..feb0d0eb4708 100644 --- a/packages/toolkit/plugin-v2/src/types/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/hooks.ts @@ -1,5 +1,10 @@ import type { UnwrapPromise } from './utils'; +export type SyncHook any> = { + tap: (cb: Callback) => void; + call: (...args: Parameters) => ReturnType; +}; + export type AsyncHook any> = { tap: (cb: Callback) => void; call: (...args: Parameters) => Promise>; @@ -13,6 +18,7 @@ export type CollectAsyncHook any> = { }; export type PluginHook any> = + | SyncHook | AsyncHook | CollectAsyncHook; diff --git a/packages/toolkit/plugin-v2/src/types/index.ts b/packages/toolkit/plugin-v2/src/types/index.ts index 9a69a3a25f0e..9d1c44efdd5a 100644 --- a/packages/toolkit/plugin-v2/src/types/index.ts +++ b/packages/toolkit/plugin-v2/src/types/index.ts @@ -10,6 +10,12 @@ export type { Entrypoint, CLIPlugin, } from './cli'; +export type { + RuntimePluginAPI, + RuntimeContext, + InternalRuntimeContext, + RuntimePlugin, +} from './runtime'; export type { AsyncHook, CollectAsyncHook, diff --git a/packages/toolkit/plugin-v2/src/types/plugin.ts b/packages/toolkit/plugin-v2/src/types/plugin.ts index 6dd33959dbd3..34a250de857e 100644 --- a/packages/toolkit/plugin-v2/src/types/plugin.ts +++ b/packages/toolkit/plugin-v2/src/types/plugin.ts @@ -20,10 +20,7 @@ export type Plugin = { /** * The plugins add new apis to the plugin manager. */ - _registryApi?: ( - getAppContext: () => Context, - updateAppContext: (context: Context) => void, - ) => Record any>; + _registryApi?: (...params: any[]) => Record any>; /** * The setup function of the plugin, which can be an async function. * This function is called once when the plugin is initialized. diff --git a/packages/toolkit/plugin-v2/src/types/runtime/api.ts b/packages/toolkit/plugin-v2/src/types/runtime/api.ts index 74b7ce769f56..813e96379542 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/api.ts @@ -10,15 +10,11 @@ import type { import type { RuntimePluginExtends } from './plugin'; export type RuntimePluginAPI = Readonly<{ - getRuntimeContext: () => Readonly< - RuntimeContext & Extends['extendContext'] - >; - updateRuntimeContext: ( - updateContext: DeepPartial>, - ) => void; + getRuntimeContext: () => Readonly; + updateRuntimeContext: (updateContext: DeepPartial) => void; getRuntimeConfig: () => Readonly; onBeforeRender: PluginHookTap>; wrapRoot: PluginHookTap; - pickContext: PluginHookTap>>; + pickContext: PluginHookTap>; modifyRuntimeConfig: PluginHookTap>; }>; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/context.ts b/packages/toolkit/plugin-v2/src/types/runtime/context.ts index 0fec5855e6d5..89c414db7f13 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/context.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/context.ts @@ -1,18 +1,13 @@ import type { Hooks } from '../../runtime/hooks'; import type { RuntimePluginAPI } from './api'; -import type { RuntimePlugin, RuntimePluginExtends } from './plugin'; +import type { RuntimePluginExtends } from './plugin'; -export interface RuntimeContext { - plugins: RuntimePlugin[]; -} +export type RuntimeContext = {}; -export type InternalContext = - RuntimeContext & { +export type InternalRuntimeContext = + RuntimeContext & { /** All hooks. */ - hooks: Hooks< - Extends['config'], - RuntimeContext & Extends['extendContext'] - > & + hooks: Hooks & Extends['extendHooks']; /** All plugin registry hooks */ extendsHooks: Extends['extendHooks']; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts index 46433f3beb30..87870c2406b6 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts @@ -2,8 +2,8 @@ import type React from 'react'; export type OnBeforeRenderFn = ( context: RuntimeContext, - interrupt: (info: any) => void, -) => Promise | void; + interrupt: (info: any) => any, +) => Promise | any; export type WrapRootFn = ( root: React.ComponentType, diff --git a/packages/toolkit/plugin-v2/src/types/runtime/index.ts b/packages/toolkit/plugin-v2/src/types/runtime/index.ts new file mode 100644 index 000000000000..73638882fc45 --- /dev/null +++ b/packages/toolkit/plugin-v2/src/types/runtime/index.ts @@ -0,0 +1,3 @@ +export type { RuntimePluginAPI } from './api'; +export type { RuntimeContext, InternalRuntimeContext } from './context'; +export type { RuntimePlugin, RuntimePluginExtends } from './plugin'; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts b/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts index fdbee63620fe..6d6f51fea853 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/plugin.ts @@ -20,5 +20,5 @@ export interface RuntimePluginExtends< */ export type RuntimePlugin = Plugin< RuntimePluginAPI & Extends['extendApi'], - RuntimeContext & Extends['extendContext'] + RuntimeContext & Extends['extendContext'] >; From a83294ed8e5fa719f9884cc1326ce86dbe3db3ef Mon Sep 17 00:00:00 2001 From: caohuilin Date: Tue, 7 Jan 2025 21:33:37 +0800 Subject: [PATCH 04/19] fix: compat old plugin --- packages/runtime/plugin-runtime/src/core/compatible.tsx | 4 ++-- packages/runtime/plugin-runtime/src/core/plugin/index.ts | 3 ++- packages/runtime/plugin-runtime/src/core/plugin/types.ts | 3 ++- .../plugin-runtime/src/router/runtime/plugin.node.tsx | 7 ++----- .../storybook/builder/src/addons/components/modern.tsx | 2 +- packages/toolkit/plugin-v2/src/runtime/run/types.ts | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index c563b3679b29..00ca9e4544ca 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -12,7 +12,7 @@ import { type TRuntimeContext, } from './context/runtime'; import { createLoaderManager } from './loader/loaderManager'; -import { registerPlugin } from './plugin'; +import { type Plugin, registerPlugin } from './plugin'; import type { RuntimeExtends } from './plugin/types'; import { wrapRuntimeContextProvider } from './react/wrapper'; import type { TSSRContext } from './types'; @@ -20,7 +20,7 @@ import type { TSSRContext } from './types'; const IS_REACT18 = process.env.IS_REACT18 === 'true'; export type CreateAppOptions = { - plugins: RuntimePlugin[]; + plugins: (Plugin | RuntimePlugin)[]; /** * In the test cases, we need to execute multiple createApp instances simultaneously, and they must not share the runtime. */ diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index abfcb5079028..97977d13e6e8 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -2,13 +2,14 @@ import type { InternalRuntimeContext, Plugin } from '@modern-js/plugin-v2'; import { runtime } from '@modern-js/plugin-v2/runtime'; import { merge } from '@modern-js/runtime-utils/merge'; import { setGlobalInternalRuntimeContext } from '../context'; +import type { Plugin as OldRuntimePlugin } from './base'; import type { RuntimeConfig, RuntimeExtends, RuntimePlugin } from './types'; // old type export type { Plugin } from './base'; export function registerPlugin( - internalPlugins: RuntimePlugin[], + internalPlugins: (OldRuntimePlugin | RuntimePlugin)[], runtimeConfig?: RuntimeConfig, customRuntime?: typeof runtime, ) { diff --git a/packages/runtime/plugin-runtime/src/core/plugin/types.ts b/packages/runtime/plugin-runtime/src/core/plugin/types.ts index 7d33ec09f817..d2c702424b8a 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/types.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/types.ts @@ -4,6 +4,7 @@ import type { } from '@modern-js/plugin-v2'; import type { Hooks } from '@modern-js/plugin-v2/runtime'; import type { RuntimeContext } from '../context/runtime'; +import type { Plugin } from './base'; export type RuntimeHooks = Hooks; @@ -13,5 +14,5 @@ export type RuntimeExtends = Required< export type RuntimePlugin = BaseRuntimePlugin; export interface RuntimeConfig { - plugins?: RuntimePlugin[]; + plugins?: (Plugin | RuntimePlugin)[]; } diff --git a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx index 3d75786b56b7..59cad96aa8bd 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx +++ b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx @@ -15,7 +15,7 @@ import type React from 'react'; import { useContext } from 'react'; import { JSX_SHELL_STREAM_END_MARK } from '../../common'; import { RuntimeReactContext } from '../../core'; -import type { Plugin, RuntimeContext } from '../../core'; +import type { Plugin } from '../../core'; import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context'; import DeferredDataScripts from './DeferredDataScripts.node'; import { @@ -50,10 +50,7 @@ export const routerPlugin = ( let finalRouteConfig: any = {}; return { - async beforeRender( - context: RuntimeContext, - interrupt: (info: any) => any, - ) { + async beforeRender(context, interrupt) { const pluginConfig: Record = api.useRuntimeConfigContext(); const { diff --git a/packages/storybook/builder/src/addons/components/modern.tsx b/packages/storybook/builder/src/addons/components/modern.tsx index cdcfddaa6e0a..589485255a36 100644 --- a/packages/storybook/builder/src/addons/components/modern.tsx +++ b/packages/storybook/builder/src/addons/components/modern.tsx @@ -7,7 +7,7 @@ import type { IConfig } from '../type'; export const WrapProviders = (storyFn: any, config: IConfig) => { const App = createApp({ - plugins: resolvePlugins(config.modernConfigRuntime) as any, + plugins: resolvePlugins(config.modernConfigRuntime), })(storyFn); return ; diff --git a/packages/toolkit/plugin-v2/src/runtime/run/types.ts b/packages/toolkit/plugin-v2/src/runtime/run/types.ts index ead4a1c6e4aa..23170ef60d37 100644 --- a/packages/toolkit/plugin-v2/src/runtime/run/types.ts +++ b/packages/toolkit/plugin-v2/src/runtime/run/types.ts @@ -1,10 +1,10 @@ import type { Plugin } from '../../types/plugin'; export type RuntimeRunOptions = { + config: Record; plugins: Plugin[]; handleSetupResult?: ( params: any, api: Record, ) => Promise | void; - config: Record; }; From 592cb9c7ac35437302ea6db6e6128ff3d0abb2fd Mon Sep 17 00:00:00 2001 From: caohuilin Date: Tue, 7 Jan 2025 23:17:33 +0800 Subject: [PATCH 05/19] feat: compat old plugin --- .../plugin-runtime/src/core/compat/hooks.ts | 61 +++++++++++++++++++ .../plugin-runtime/src/core/compat/index.ts | 23 +++++++ .../plugin-runtime/src/core/plugin/index.ts | 7 ++- packages/toolkit/plugin-v2/src/hooks.ts | 35 ++++++++++- packages/toolkit/plugin-v2/src/index.ts | 2 + packages/toolkit/plugin-v2/src/runtime/api.ts | 1 + .../toolkit/plugin-v2/src/runtime/hooks.ts | 4 +- .../plugin-v2/src/runtime/run/create.ts | 10 ++- packages/toolkit/plugin-v2/src/types/hooks.ts | 6 ++ .../plugin-v2/src/types/runtime/context.ts | 1 + 10 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 packages/runtime/plugin-runtime/src/core/compat/hooks.ts create mode 100644 packages/runtime/plugin-runtime/src/core/compat/index.ts diff --git a/packages/runtime/plugin-runtime/src/core/compat/hooks.ts b/packages/runtime/plugin-runtime/src/core/compat/hooks.ts new file mode 100644 index 000000000000..e7883f3c9132 --- /dev/null +++ b/packages/runtime/plugin-runtime/src/core/compat/hooks.ts @@ -0,0 +1,61 @@ +import type { RuntimeContext } from '../context'; +import type { RuntimeConfig } from '../plugin/types'; + +export function transformHookRunner(hookRunnerName: string) { + switch (hookRunnerName) { + case 'beforeRender': + return 'onBeforeRender'; + default: + return hookRunnerName; + } +} +export function handleSetupResult( + setupResult: Record any>, + api: Record, +) { + if (!setupResult) { + return; + } + Object.keys(setupResult).forEach(key => { + const fn = setupResult[key]; + if (typeof fn === 'function') { + const newAPI = transformHookRunner(key); + if (api[newAPI]) { + if (key === 'beforeRender') { + api[newAPI](async (...params: any) => { + await fn(...params); + }); + } else { + api[newAPI]((...params: any) => { + const res = fn(...params); + return res; + }); + } + } + } + }); +} + +export function getHookRunners( + runtimeContext: RuntimeContext, +): Record { + const { _internalContext } = runtimeContext; + const { hooks } = _internalContext; + return { + beforeRender: async (context: any) => { + return hooks.onBeforeRender.call(context); + }, + wrapRoot: (App: React.ComponentType) => { + return hooks.wrapRoot.call(App); + }, + pickContext: (context: RuntimeContext) => { + return hooks.pickContext.call(context); + }, + modifyRuntimeConfig: (config: RuntimeConfig) => { + return hooks.modifyRuntimeConfig.call(config); + }, + modifyRoutes: (routes: any) => { + return hooks.modifyRoutes.call(routes); + }, + }; +} diff --git a/packages/runtime/plugin-runtime/src/core/compat/index.ts b/packages/runtime/plugin-runtime/src/core/compat/index.ts new file mode 100644 index 000000000000..e81544c0c71c --- /dev/null +++ b/packages/runtime/plugin-runtime/src/core/compat/index.ts @@ -0,0 +1,23 @@ +import { createSyncHook } from '@modern-js/plugin-v2'; +import type { RuntimePlugin } from '../plugin/types'; +import { getHookRunners } from './hooks'; + +type ModifyRoutesFn = (routes: any) => any; + +export const compatPlugin = (): RuntimePlugin => ({ + name: '@modern-js/runtime-plugin-compat', + _registryApi: getRuntimeContext => { + return { + useRuntimeConfigContext: () => { + const { _internalContext } = getRuntimeContext(); + return _internalContext.config; + }, + useHookRunners: () => { + return getHookRunners(getRuntimeContext()); + }, + }; + }, + registryHooks: { + modifyRoutes: createSyncHook(), + }, +}); diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index 97977d13e6e8..e51d94990e5b 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -1,6 +1,8 @@ import type { InternalRuntimeContext, Plugin } from '@modern-js/plugin-v2'; import { runtime } from '@modern-js/plugin-v2/runtime'; import { merge } from '@modern-js/runtime-utils/merge'; +import { compatPlugin } from '../compat'; +import { handleSetupResult } from '../compat/hooks'; import { setGlobalInternalRuntimeContext } from '../context'; import type { Plugin as OldRuntimePlugin } from './base'; import type { RuntimeConfig, RuntimeExtends, RuntimePlugin } from './types'; @@ -14,9 +16,10 @@ export function registerPlugin( customRuntime?: typeof runtime, ) { const { plugins = [] } = runtimeConfig || {}; - const { runtimeContext } = (customRuntime || runtime).init({ - plugins: [...internalPlugins, ...plugins] as Plugin[], + const { runtimeContext } = (customRuntime || runtime).run({ + plugins: [compatPlugin(), ...internalPlugins, ...plugins] as Plugin[], config: runtimeConfig || {}, + handleSetupResult, }); setGlobalInternalRuntimeContext( runtimeContext as unknown as InternalRuntimeContext, diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts index d11be2771e62..f19bb6049c68 100644 --- a/packages/toolkit/plugin-v2/src/hooks.ts +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -1,4 +1,9 @@ -import type { AsyncHook, CollectAsyncHook, SyncHook } from './types/hooks'; +import type { + AsyncHook, + CollectAsyncHook, + CollectSyncHook, + SyncHook, +} from './types/hooks'; import type { UnwrapPromise } from './types/utils'; export function createAsyncInterruptHook< @@ -118,3 +123,31 @@ export function createCollectAsyncHook< call, }; } + +export function createCollectSyncHook< + Callback extends (...params: any[]) => any, +>(): CollectSyncHook { + const callbacks: Callback[] = []; + + const tap = (cb: Callback) => { + callbacks.push(cb); + }; + + const call = (...params: Parameters) => { + const results: ReturnType[] = []; + for (const callback of callbacks) { + const result = callback(params); + + if (result !== undefined) { + 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 ec19b3a0afd7..ffa0fc155f00 100644 --- a/packages/toolkit/plugin-v2/src/index.ts +++ b/packages/toolkit/plugin-v2/src/index.ts @@ -1,6 +1,8 @@ export { createPluginManager } from './manager'; export { + createSyncHook, createAsyncHook, + createCollectSyncHook, createCollectAsyncHook, createAsyncInterruptHook, } from './hooks'; diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index 0792ea447af0..acaf5be4ba36 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -26,6 +26,7 @@ export function initPluginAPI({ if (context) { const { hooks, extendsHooks, config, pluginAPI, ...runtimeContext } = context; + runtimeContext._internalContext = context; return runtimeContext as RuntimeContext & Extends['extendContext']; } throw new Error('Cannot access context'); diff --git a/packages/toolkit/plugin-v2/src/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/runtime/hooks.ts index 849d82be1c99..6e4677866f48 100644 --- a/packages/toolkit/plugin-v2/src/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/runtime/hooks.ts @@ -1,7 +1,7 @@ import { createAsyncHook, createAsyncInterruptHook, - createCollectAsyncHook, + createCollectSyncHook, createSyncHook, } from '../hooks'; import type { @@ -18,7 +18,7 @@ export function initHooks() { wrapRoot: createSyncHook(), pickContext: createAsyncHook>(), modifyRuntimeConfig: - createCollectAsyncHook>(), + createCollectSyncHook>(), }; } diff --git a/packages/toolkit/plugin-v2/src/runtime/run/create.ts b/packages/toolkit/plugin-v2/src/runtime/run/create.ts index e177234bf97f..5c394deec1cc 100644 --- a/packages/toolkit/plugin-v2/src/runtime/run/create.ts +++ b/packages/toolkit/plugin-v2/src/runtime/run/create.ts @@ -1,3 +1,4 @@ +import { merge } from '@modern-js/utils/lodash'; import { createPluginManager } from '../../manager'; import { initPluginAPI } from '../../runtime/api'; import { @@ -47,8 +48,13 @@ export const createRuntime = () => { return { runtimeContext: context }; } - async function run(options: RuntimeRunOptions) { - await init(options); + function run(options: RuntimeRunOptions) { + const { runtimeContext } = init(options); + const configs = runtimeContext.hooks.modifyRuntimeConfig.call( + runtimeContext.config, + ); + runtimeContext.config = merge({}, ...configs); + return { runtimeContext }; } return { diff --git a/packages/toolkit/plugin-v2/src/types/hooks.ts b/packages/toolkit/plugin-v2/src/types/hooks.ts index feb0d0eb4708..a931a00c4aff 100644 --- a/packages/toolkit/plugin-v2/src/types/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/hooks.ts @@ -17,9 +17,15 @@ export type CollectAsyncHook any> = { ) => Promise>[]>; }; +export type CollectSyncHook any> = { + tap: (cb: Callback) => void; + call: (...params: Parameters) => ReturnType[]; +}; + export type PluginHook any> = | SyncHook | AsyncHook + | CollectSyncHook | CollectAsyncHook; export type PluginHookTap any> = ( diff --git a/packages/toolkit/plugin-v2/src/types/runtime/context.ts b/packages/toolkit/plugin-v2/src/types/runtime/context.ts index 89c414db7f13..03b308389a4f 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/context.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/context.ts @@ -13,4 +13,5 @@ export type InternalRuntimeContext = extendsHooks: Extends['extendHooks']; config: Extends['config']; pluginAPI?: RuntimePluginAPI; + _internalContext?: InternalRuntimeContext; }; From 93fc12514719cb403b52289f0334a58a14046909 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Tue, 7 Jan 2025 23:34:56 +0800 Subject: [PATCH 06/19] feat: add beforeCreateRoutes compat hook --- packages/runtime/plugin-runtime/src/core/compat/hooks.ts | 3 +++ packages/runtime/plugin-runtime/src/core/compat/index.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/runtime/plugin-runtime/src/core/compat/hooks.ts b/packages/runtime/plugin-runtime/src/core/compat/hooks.ts index e7883f3c9132..d1847f53263e 100644 --- a/packages/runtime/plugin-runtime/src/core/compat/hooks.ts +++ b/packages/runtime/plugin-runtime/src/core/compat/hooks.ts @@ -57,5 +57,8 @@ export function getHookRunners( modifyRoutes: (routes: any) => { return hooks.modifyRoutes.call(routes); }, + beforeCreateRoutes: (context: RuntimeContext) => { + return hooks.beforeCreateRoutes.call(context); + }, }; } diff --git a/packages/runtime/plugin-runtime/src/core/compat/index.ts b/packages/runtime/plugin-runtime/src/core/compat/index.ts index e81544c0c71c..1049dd6a8ef2 100644 --- a/packages/runtime/plugin-runtime/src/core/compat/index.ts +++ b/packages/runtime/plugin-runtime/src/core/compat/index.ts @@ -1,9 +1,12 @@ -import { createSyncHook } from '@modern-js/plugin-v2'; +import { createAsyncInterruptHook, createSyncHook } from '@modern-js/plugin-v2'; +import type { RuntimeContext } from '../context'; import type { RuntimePlugin } from '../plugin/types'; import { getHookRunners } from './hooks'; type ModifyRoutesFn = (routes: any) => any; +type BeforeCreateRoutesFn = (context: RuntimeContext) => any; + export const compatPlugin = (): RuntimePlugin => ({ name: '@modern-js/runtime-plugin-compat', _registryApi: getRuntimeContext => { @@ -19,5 +22,6 @@ export const compatPlugin = (): RuntimePlugin => ({ }, registryHooks: { modifyRoutes: createSyncHook(), + beforeCreateRoutes: createAsyncInterruptHook(), }, }); From 2c9e913e0914035386e7bffaafdc28173d1f5ff8 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 10:51:34 +0800 Subject: [PATCH 07/19] fix: redirect error --- .../runtime/plugin-runtime/src/core/browser/index.tsx | 2 +- .../runtime/plugin-runtime/src/core/compatible.tsx | 2 +- .../plugin-runtime/src/core/server/requestHandler.ts | 2 +- packages/toolkit/plugin-v2/package.json | 1 + packages/toolkit/plugin-v2/src/hooks.ts | 11 +++++++---- packages/toolkit/plugin-v2/src/manager.ts | 5 +++-- packages/toolkit/plugin-v2/src/runtime/api.ts | 2 +- packages/toolkit/plugin-v2/src/runtime/run/create.ts | 10 ++++++---- packages/toolkit/plugin-v2/src/types/hooks.ts | 10 ++++++++-- packages/toolkit/plugin-v2/src/types/utils.ts | 4 ++++ 10 files changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/runtime/plugin-runtime/src/core/browser/index.tsx b/packages/runtime/plugin-runtime/src/core/browser/index.tsx index c8f2d3b9f155..f2d28dd3ae98 100644 --- a/packages/runtime/plugin-runtime/src/core/browser/index.tsx +++ b/packages/runtime/plugin-runtime/src/core/browser/index.tsx @@ -87,7 +87,7 @@ export async function render( const api = internalRuntimeContext?.pluginAPI; api?.updateRuntimeContext(context); const hooks = internalRuntimeContext?.hooks; - await hooks.onBeforeRender.call(context, info => info); + await hooks.onBeforeRender.call(context); const init = getGlobalAppInit(); return init?.(context); }; diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index 00ca9e4544ca..c1259c8b4d5f 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -112,7 +112,7 @@ export const bootstrap: BootStrap = async ( api?.updateRuntimeContext(context); const runBeforeRender = async (context: RuntimeContext) => { - await hooks.onBeforeRender.call(context, info => info); + await hooks.onBeforeRender.call(context); const init = getGlobalAppInit(); return init?.(context); }; diff --git a/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts b/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts index cb08033eafda..fcb93f8af49a 100644 --- a/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts +++ b/packages/runtime/plugin-runtime/src/core/server/requestHandler.ts @@ -163,7 +163,7 @@ export const createRequestHandler: CreateRequestHandler = const runBeforeRender = async (context: RuntimeContext) => { // when router is redirect, beforeRender will return a response - const result = await hooks.onBeforeRender.call(context, info => info); + const result = await hooks.onBeforeRender.call(context); if (typeof Response !== 'undefined' && result instanceof Response) { return result; } diff --git a/packages/toolkit/plugin-v2/package.json b/packages/toolkit/plugin-v2/package.json index 16448f98ab15..7f8af9589105 100644 --- a/packages/toolkit/plugin-v2/package.json +++ b/packages/toolkit/plugin-v2/package.json @@ -72,6 +72,7 @@ "dependencies": { "@modern-js/node-bundle-require": "workspace:*", "@modern-js/utils": "workspace:*", + "@modern-js/runtime-utils": "workspace:*", "@swc/helpers": "0.5.13" }, "devDependencies": { diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts index f19bb6049c68..29de4241cbe7 100644 --- a/packages/toolkit/plugin-v2/src/hooks.ts +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -1,30 +1,33 @@ import type { AsyncHook, + AsyncInterruptHook, CollectAsyncHook, CollectSyncHook, SyncHook, } from './types/hooks'; -import type { UnwrapPromise } from './types/utils'; +import type { Tail, UnwrapPromise } from './types/utils'; export function createAsyncInterruptHook< Callback extends (...args: any[]) => any, ->(): AsyncHook { +>(): AsyncInterruptHook { const callbacks: Callback[] = []; const tap = (cb: Callback) => { callbacks.push(cb); }; - const call = async (...params: Parameters) => { + const call = async (...params: Tail>) => { let interrupted = false; let interruptResult: any; const interrupt = (info: any) => { + console.log('===interrupt'); interrupted = true; interruptResult = info; }; for (const callback of callbacks) { + console.log('===for callback', interrupted); if (interrupted) break; const result = await callback(...params, interrupt); @@ -60,7 +63,7 @@ export function createSyncHook< } } - return params[0] || []; + return params[0]; }; return { diff --git a/packages/toolkit/plugin-v2/src/manager.ts b/packages/toolkit/plugin-v2/src/manager.ts index a26721d4ca72..50b9899eaf1c 100644 --- a/packages/toolkit/plugin-v2/src/manager.ts +++ b/packages/toolkit/plugin-v2/src/manager.ts @@ -1,7 +1,8 @@ -import { isFunction } from '@modern-js/utils/lodash'; import type { Plugin, PluginManager } from './types/plugin'; -import type { Falsy } from './types/utils'; +import type { Falsy, MaybePromise } from './types/utils'; +const isFunction = (obj: ((api: {}) => MaybePromise) | undefined) => + typeof obj === 'function'; // Validates if the plugin is a valid plugin instance function validatePlugin(plugin: unknown) { const type = typeof plugin; diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index acaf5be4ba36..49fb420d7c07 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -1,4 +1,4 @@ -import { merge } from '@modern-js/utils/lodash'; +import { merge } from '@modern-js/runtime-utils/merge'; import type { PluginHookTap } from '../types'; import type { PluginManager } from '../types/plugin'; import type { RuntimePluginAPI } from '../types/runtime/api'; diff --git a/packages/toolkit/plugin-v2/src/runtime/run/create.ts b/packages/toolkit/plugin-v2/src/runtime/run/create.ts index 5c394deec1cc..02a66e14c18e 100644 --- a/packages/toolkit/plugin-v2/src/runtime/run/create.ts +++ b/packages/toolkit/plugin-v2/src/runtime/run/create.ts @@ -1,4 +1,4 @@ -import { merge } from '@modern-js/utils/lodash'; +import { merge } from '@modern-js/runtime-utils/merge'; import { createPluginManager } from '../../manager'; import { initPluginAPI } from '../../runtime/api'; import { @@ -50,9 +50,11 @@ export const createRuntime = () => { function run(options: RuntimeRunOptions) { const { runtimeContext } = init(options); - const configs = runtimeContext.hooks.modifyRuntimeConfig.call( - runtimeContext.config, - ); + const configs = runtimeContext.hooks.modifyRuntimeConfig + .call(runtimeContext.config) + .filter((config): config is NonNullable => + Boolean(config), + ); runtimeContext.config = merge({}, ...configs); return { runtimeContext }; } diff --git a/packages/toolkit/plugin-v2/src/types/hooks.ts b/packages/toolkit/plugin-v2/src/types/hooks.ts index a931a00c4aff..a11d4e301dbf 100644 --- a/packages/toolkit/plugin-v2/src/types/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/hooks.ts @@ -1,4 +1,4 @@ -import type { UnwrapPromise } from './utils'; +import type { Tail, UnwrapPromise } from './utils'; export type SyncHook any> = { tap: (cb: Callback) => void; @@ -10,6 +10,11 @@ export type AsyncHook any> = { call: (...args: Parameters) => Promise>; }; +export type AsyncInterruptHook any> = { + tap: (cb: Callback) => void; + call: (...args: Tail>) => Promise>; +}; + export type CollectAsyncHook any> = { tap: (cb: Callback) => void; call: ( @@ -26,7 +31,8 @@ export type PluginHook any> = | SyncHook | AsyncHook | CollectSyncHook - | CollectAsyncHook; + | CollectAsyncHook + | AsyncInterruptHook; export type PluginHookTap any> = ( options: T, diff --git a/packages/toolkit/plugin-v2/src/types/utils.ts b/packages/toolkit/plugin-v2/src/types/utils.ts index 27152706b8d5..af691edd6d76 100644 --- a/packages/toolkit/plugin-v2/src/types/utils.ts +++ b/packages/toolkit/plugin-v2/src/types/utils.ts @@ -7,3 +7,7 @@ export type DeepPartial = { }; export type UnwrapPromise = T extends Promise ? U : T; + +export type Tail = T extends [...infer Rest, any] + ? Rest + : never; From 8a68cdbb967c25f4e74a0e96f946ababf1b92cdf Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 10:53:01 +0800 Subject: [PATCH 08/19] docs: changeset --- .changeset/thirty-monkeys-tap.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/thirty-monkeys-tap.md diff --git a/.changeset/thirty-monkeys-tap.md b/.changeset/thirty-monkeys-tap.md new file mode 100644 index 000000000000..b158bc284173 --- /dev/null +++ b/.changeset/thirty-monkeys-tap.md @@ -0,0 +1,8 @@ +--- +'@modern-js/runtime': minor +'@modern-js/plugin-v2': minor +--- + +feat: runtime plugin use plugin v2 + +feat: runtime 插件使用插件 v2 From daeee0f0778bc24b69e8523419a6f6f6b82349c1 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 14:26:57 +0800 Subject: [PATCH 09/19] feat: op unit test --- .../runtime/plugin-garfish/src/cli/code.ts | 9 +++- .../runtime/plugin-garfish/src/cli/index.ts | 2 +- .../plugin-garfish/tests/index.test.tsx | 4 -- .../plugin-router-v5/src/runtime/plugin.tsx | 1 - .../plugin-router-v5/tests/router.test.tsx | 46 ++++++++----------- .../plugin-runtime/src/core/compatible.tsx | 3 +- .../runtime/plugin-runtime/src/core/index.ts | 1 - .../plugin-runtime/src/core/plugin/index.ts | 1 + packages/toolkit/plugin-v2/modern.config.js | 4 +- packages/toolkit/plugin-v2/package.json | 2 +- packages/toolkit/plugin-v2/src/runtime/api.ts | 12 ++++- tests/jest-ut.config.js | 2 + 12 files changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/runtime/plugin-garfish/src/cli/code.ts b/packages/runtime/plugin-garfish/src/cli/code.ts index 9f15aa64e6b8..939bdb90484d 100644 --- a/packages/runtime/plugin-garfish/src/cli/code.ts +++ b/packages/runtime/plugin-garfish/src/cli/code.ts @@ -5,8 +5,10 @@ import type { AppToolsFeatureHooks, NormalizedConfig, } from '@modern-js/app-tools'; +import type { CollectAsyncHook } from '@modern-js/plugin-v2'; import type { Entrypoint } from '@modern-js/types'; import { fs } from '@modern-js/utils'; +import type { AppendEntryCodeFn } from '.'; import * as template from './template'; import { generateAsyncEntryCode } from './utils'; @@ -27,7 +29,12 @@ export const generateCode = async ( entrypoints.map(async entrypoint => { const { entryName, isAutoMount, entry, customEntry, customBootstrap } = entrypoint; - const appendCode = await hooks.appendEntryCode.call({ entrypoint }); + const appendCode = await ( + hooks.appendEntryCode as CollectAsyncHook + ).call({ + entrypoint, + code: '', + }); if (isAutoMount) { // index.jsx diff --git a/packages/runtime/plugin-garfish/src/cli/index.ts b/packages/runtime/plugin-garfish/src/cli/index.ts index 5c143ddc1717..a77e59de0bcb 100644 --- a/packages/runtime/plugin-garfish/src/cli/index.ts +++ b/packages/runtime/plugin-garfish/src/cli/index.ts @@ -39,7 +39,7 @@ export function getDefaultMicroFrontedConfig( }; } -type AppendEntryCodeFn = (params: { +export type AppendEntryCodeFn = (params: { entrypoint: Entrypoint; code: string; }) => string | Promise; diff --git a/packages/runtime/plugin-garfish/tests/index.test.tsx b/packages/runtime/plugin-garfish/tests/index.test.tsx index 3c302c87bd52..245e81bc8239 100644 --- a/packages/runtime/plugin-garfish/tests/index.test.tsx +++ b/packages/runtime/plugin-garfish/tests/index.test.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createApp } from '@modern-js/runtime'; -import { createRuntime } from '@modern-js/runtime/plugin'; import fetchMock from 'jest-fetch-mock'; import '@testing-library/jest-dom'; import 'jest-location-mock'; @@ -152,7 +151,6 @@ describe('plugin-garfish', () => { let unmount = () => {}; await act(async () => { const AppWrapper = createApp({ - runtime: createRuntime(), plugins: [garfishPlugin(microFrontendConfig)], })(App); const res = render(); @@ -256,7 +254,6 @@ describe('plugin-garfish', () => { await act(async () => { const AppWrapper = createApp({ - runtime: createRuntime(), plugins: [garfishPlugin(microFrontendConfig)], })(App); render(, {}); @@ -313,7 +310,6 @@ describe('plugin-garfish', () => { await act(async () => { const AppWrapper = createApp({ - runtime: createRuntime(), plugins: [garfishPlugin(microFrontendConfig)], })(App); render(, {}); diff --git a/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx b/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx index eeb637483915..b24fd796f0aa 100644 --- a/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx +++ b/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx @@ -2,7 +2,6 @@ import { RuntimeReactContext, isBrowser } from '@meta/runtime'; import { getGlobalLayoutApp, getGlobalRoutes } from '@meta/runtime/context'; import type { Plugin } from '@modern-js/runtime'; import { merge } from '@modern-js/runtime-utils/merge'; -import { parsedJSONFromElement } from '@modern-js/runtime-utils/parsed'; import { type BrowserHistoryBuildOptions, type HashHistoryBuildOptions, diff --git a/packages/runtime/plugin-router-v5/tests/router.test.tsx b/packages/runtime/plugin-router-v5/tests/router.test.tsx index 74cb409b4cfb..81cc2099e7e4 100644 --- a/packages/runtime/plugin-router-v5/tests/router.test.tsx +++ b/packages/runtime/plugin-router-v5/tests/router.test.tsx @@ -1,5 +1,5 @@ import { createApp } from '@modern-js/runtime'; -import { createRuntime } from '@modern-js/runtime/plugin'; +import type { RuntimePlugin } from '@modern-js/runtime/plugin'; import { fireEvent, render, screen } from '@testing-library/react'; import type React from 'react'; import { useHistory } from '../src'; @@ -9,17 +9,16 @@ import createRouterPlugin, { } from '../src/runtime'; import { DefaultNotFound } from '../src/runtime/DefaultNotFound'; +const testPlugin: RuntimePlugin = { + name: 'test', + setup: api => { + api.wrapRoot(App1 => App1); + }, +}; describe('@modern-js/plugin-router-v5', () => { it('base usage', () => { - const runtime = createRuntime(); const AppWrapper = createApp({ - runtime, - plugins: [ - runtime.createPlugin(() => ({ - wrapRoot: App1 => App1, - })), - createRouterPlugin({}), - ], + plugins: [testPlugin, createRouterPlugin({})], })(App); interface Props { @@ -35,13 +34,9 @@ describe('@modern-js/plugin-router-v5', () => { }); it('pages', () => { - const runtime = createRuntime(); const AppWrapper = createApp({ - runtime, plugins: [ - runtime.createPlugin(() => ({ - wrapRoot: App1 => App1, - })), + testPlugin, createRouterPlugin({ routesConfig: { routes: [{ path: '/', component: App as any }], @@ -88,13 +83,9 @@ describe('@modern-js/plugin-router-v5', () => { return
home
; } - const runtime = createRuntime(); const AppWrapper = createApp({ - runtime, plugins: [ - runtime.createPlugin(() => ({ - wrapRoot: App1 => App1, - })), + testPlugin, createRouterPlugin({ routesConfig: { routes: [ @@ -135,18 +126,19 @@ describe('@modern-js/plugin-router-v5', () => { return routes; }); - const runtime = createRuntime(); const AppWrapper = createApp({ - runtime, plugins: [ - runtime.createPlugin(() => ({ - wrapRoot: App1 => App1, - modifyRoutes(routes: RouteProps[]) { - return modifyFn?.(routes); + { + name: 'test', + setup: (api: any) => { + api.wrapRoot((App1: any) => App1); + api.modifyRoutes((routes: RouteProps[]) => { + return modifyFn?.(routes); + }); }, - })), + } as RuntimePlugin, createRouterPlugin({ - routesConfig: { routes: [{ path: '/', component: App as any }] }, + routesConfig: { routes: [{ path: '/' }] }, }), ], })(App); diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index c1259c8b4d5f..b53e318b0700 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -1,5 +1,4 @@ import type { RuntimePlugin } from '@modern-js/plugin-v2'; -import { runtime } from '@modern-js/plugin-v2/runtime'; import { ROUTE_MANIFEST } from '@modern-js/utils/universal/constants'; import React, { useContext, useMemo } from 'react'; import type { Renderer } from 'react-dom'; @@ -12,7 +11,7 @@ import { type TRuntimeContext, } from './context/runtime'; import { createLoaderManager } from './loader/loaderManager'; -import { type Plugin, registerPlugin } from './plugin'; +import { type Plugin, registerPlugin, runtime } from './plugin'; import type { RuntimeExtends } from './plugin/types'; import { wrapRuntimeContextProvider } from './react/wrapper'; import type { TSSRContext } from './types'; diff --git a/packages/runtime/plugin-runtime/src/core/index.ts b/packages/runtime/plugin-runtime/src/core/index.ts index bc12cc859f58..7ae0e7f0d5c8 100644 --- a/packages/runtime/plugin-runtime/src/core/index.ts +++ b/packages/runtime/plugin-runtime/src/core/index.ts @@ -1,5 +1,4 @@ export type { Plugin } from './plugin'; - export { defineConfig, getConfig, defineRuntimeConfig } from './config'; // compatible diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index e51d94990e5b..089b6cd8de57 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -9,6 +9,7 @@ import type { RuntimeConfig, RuntimeExtends, RuntimePlugin } from './types'; // old type export type { Plugin } from './base'; +export { runtime, type RuntimePlugin }; export function registerPlugin( internalPlugins: (OldRuntimePlugin | RuntimePlugin)[], diff --git a/packages/toolkit/plugin-v2/modern.config.js b/packages/toolkit/plugin-v2/modern.config.js index 800eb37e75fd..5c0331ad0c29 100644 --- a/packages/toolkit/plugin-v2/modern.config.js +++ b/packages/toolkit/plugin-v2/modern.config.js @@ -1,5 +1,5 @@ -const { universalBuildConfigWithBundle } = require('@scripts/build'); +const { universalBuildConfig } = require('@scripts/build'); module.exports = { - buildConfig: universalBuildConfigWithBundle, + buildConfig: universalBuildConfig, }; diff --git a/packages/toolkit/plugin-v2/package.json b/packages/toolkit/plugin-v2/package.json index 7f8af9589105..4f636edc0445 100644 --- a/packages/toolkit/plugin-v2/package.json +++ b/packages/toolkit/plugin-v2/package.json @@ -43,7 +43,7 @@ "./runtime": { "types": "./dist/types/runtime/index.d.ts", "jsnext:source": "./src/runtime/index.ts", - "default": "./dist/cjs/runtime/index.js" + "default": "./dist/esm/runtime/index.js" } }, "typesVersions": { diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index 49fb420d7c07..2b73785bd455 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -1,5 +1,5 @@ import { merge } from '@modern-js/runtime-utils/merge'; -import type { PluginHookTap } from '../types'; +import type { PluginHook, PluginHookTap } from '../types'; import type { PluginManager } from '../types/plugin'; import type { RuntimePluginAPI } from '../types/runtime/api'; import type { @@ -20,7 +20,7 @@ export function initPluginAPI({ pluginManager: PluginManager; plugins: RuntimePlugin[]; }): RuntimePluginAPI { - const { hooks } = context; + const { hooks, extendsHooks } = context; function getRuntimeContext() { if (context) { @@ -59,6 +59,14 @@ export function initPluginAPI({ } }); + if (extendsHooks) { + Object.keys(extendsHooks!).forEach(hookName => { + extendsPluginApi[hookName] = ( + extendsHooks as Record any>> + )[hookName].tap; + }); + } + return { getRuntimeContext, updateRuntimeContext, diff --git a/tests/jest-ut.config.js b/tests/jest-ut.config.js index 94cd20cf0ede..1c2ae695cb5c 100644 --- a/tests/jest-ut.config.js +++ b/tests/jest-ut.config.js @@ -34,6 +34,8 @@ module.exports = { '/packages/runtime/plugin-runtime/src/core/react', '^@modern-js/runtime$': '/packages/runtime/plugin-runtime/src/index', + '^@modern-js/plugin-v2/runtime$': + '/packages/toolkit/plugin-v2/src/runtime/index', }, globals: {}, resolver: '/tests/jest.resolver.js', From 1d9268cd5b904f70cd8ef9c3bc491f0e4faec842 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 14:47:34 +0800 Subject: [PATCH 10/19] feat: rename runtime plugin type --- .../runtime/plugin-runtime/src/core/compat/index.ts | 4 ++-- .../runtime/plugin-runtime/src/core/plugin/index.ts | 12 ++++++++---- .../runtime/plugin-runtime/src/core/plugin/types.ts | 4 ++-- packages/toolkit/plugin-v2/src/hooks.ts | 1 - 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/runtime/plugin-runtime/src/core/compat/index.ts b/packages/runtime/plugin-runtime/src/core/compat/index.ts index 1049dd6a8ef2..e0bc82a0598a 100644 --- a/packages/runtime/plugin-runtime/src/core/compat/index.ts +++ b/packages/runtime/plugin-runtime/src/core/compat/index.ts @@ -1,13 +1,13 @@ import { createAsyncInterruptHook, createSyncHook } from '@modern-js/plugin-v2'; import type { RuntimeContext } from '../context'; -import type { RuntimePlugin } from '../plugin/types'; +import type { RuntimePluginFuture } from '../plugin/types'; import { getHookRunners } from './hooks'; type ModifyRoutesFn = (routes: any) => any; type BeforeCreateRoutesFn = (context: RuntimeContext) => any; -export const compatPlugin = (): RuntimePlugin => ({ +export const compatPlugin = (): RuntimePluginFuture => ({ name: '@modern-js/runtime-plugin-compat', _registryApi: getRuntimeContext => { return { diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index 089b6cd8de57..218795de2c63 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -4,15 +4,19 @@ import { merge } from '@modern-js/runtime-utils/merge'; import { compatPlugin } from '../compat'; import { handleSetupResult } from '../compat/hooks'; import { setGlobalInternalRuntimeContext } from '../context'; -import type { Plugin as OldRuntimePlugin } from './base'; -import type { RuntimeConfig, RuntimeExtends, RuntimePlugin } from './types'; +import type { Plugin as RuntimePlugin } from './base'; +import type { + RuntimeConfig, + RuntimeExtends, + RuntimePluginFuture, +} from './types'; // old type export type { Plugin } from './base'; -export { runtime, type RuntimePlugin }; +export { runtime, type RuntimePluginFuture }; export function registerPlugin( - internalPlugins: (OldRuntimePlugin | RuntimePlugin)[], + internalPlugins: (RuntimePlugin | RuntimePluginFuture)[], runtimeConfig?: RuntimeConfig, customRuntime?: typeof runtime, ) { diff --git a/packages/runtime/plugin-runtime/src/core/plugin/types.ts b/packages/runtime/plugin-runtime/src/core/plugin/types.ts index d2c702424b8a..73e2a023aaa1 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/types.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/types.ts @@ -12,7 +12,7 @@ export type RuntimeExtends = Required< RuntimePluginExtends >; -export type RuntimePlugin = BaseRuntimePlugin; +export type RuntimePluginFuture = BaseRuntimePlugin; export interface RuntimeConfig { - plugins?: (Plugin | RuntimePlugin)[]; + plugins?: (Plugin | RuntimePluginFuture)[]; } diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts index 29de4241cbe7..255212f03739 100644 --- a/packages/toolkit/plugin-v2/src/hooks.ts +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -27,7 +27,6 @@ export function createAsyncInterruptHook< }; for (const callback of callbacks) { - console.log('===for callback', interrupted); if (interrupted) break; const result = await callback(...params, interrupt); From 38544dc1857fa1a1dda2002337839701b8169bbb Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 14:56:09 +0800 Subject: [PATCH 11/19] feat: remove custom runtime --- packages/runtime/plugin-runtime/src/core/compatible.tsx | 4 ++-- packages/runtime/plugin-runtime/src/core/plugin/index.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index b53e318b0700..499502c87149 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -11,7 +11,7 @@ import { type TRuntimeContext, } from './context/runtime'; import { createLoaderManager } from './loader/loaderManager'; -import { type Plugin, registerPlugin, runtime } from './plugin'; +import { type Plugin, registerPlugin } from './plugin'; import type { RuntimeExtends } from './plugin/types'; import { wrapRuntimeContextProvider } from './react/wrapper'; import type { TSSRContext } from './types'; @@ -46,7 +46,7 @@ export const createApp = ({ plugins, props: globalProps, }: CreateAppOptions) => { - const context = registerPlugin(plugins, { plugins: [] }, runtime); + const context = registerPlugin(plugins, { plugins: [] }); const hooks = context.hooks; return (App?: React.ComponentType) => { diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index 218795de2c63..2190ba2874f9 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -13,15 +13,15 @@ import type { // old type export type { Plugin } from './base'; -export { runtime, type RuntimePluginFuture }; +// new type +export type { RuntimePluginFuture }; export function registerPlugin( internalPlugins: (RuntimePlugin | RuntimePluginFuture)[], runtimeConfig?: RuntimeConfig, - customRuntime?: typeof runtime, ) { const { plugins = [] } = runtimeConfig || {}; - const { runtimeContext } = (customRuntime || runtime).run({ + const { runtimeContext } = runtime.run({ plugins: [compatPlugin(), ...internalPlugins, ...plugins] as Plugin[], config: runtimeConfig || {}, handleSetupResult, From ae7903b3ad587afe775dc00e78628859980234fc Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 15:13:44 +0800 Subject: [PATCH 12/19] fix: pick context --- packages/runtime/plugin-runtime/src/core/compatible.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index 499502c87149..a69878a0866f 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -248,8 +248,10 @@ export const useRuntimeContext = () => { response: context.ssrContext?.response, }; + const internalRuntimeContext = getGlobalInternalRuntimeContext(); + const hooks = internalRuntimeContext.hooks; const memoizedContext = useMemo( - () => context.runner.pickContext(pickedContext), + () => hooks.pickContext.call(pickedContext as RuntimeContext), [context], ); From c47e99bf236098627760ca2f821d14109921dcb9 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 21:51:51 +0800 Subject: [PATCH 13/19] feat: migrate router plugin --- .../plugin-router-v5/src/runtime/hooks.ts | 6 +- .../plugin-router-v5/src/runtime/plugin.tsx | 186 +++++----- .../plugin-runtime/src/core/compat/index.ts | 10 - .../runtime/plugin-runtime/src/core/index.ts | 2 +- packages/runtime/plugin-runtime/src/index.ts | 2 +- .../src/router/runtime/hooks.ts | 10 +- .../src/router/runtime/plugin.tsx | 320 +++++++++--------- .../builder/src/addons/components/modern.tsx | 8 +- 8 files changed, 268 insertions(+), 276 deletions(-) diff --git a/packages/runtime/plugin-router-v5/src/runtime/hooks.ts b/packages/runtime/plugin-router-v5/src/runtime/hooks.ts index 5c8c85ce9b21..8063b11162e3 100644 --- a/packages/runtime/plugin-router-v5/src/runtime/hooks.ts +++ b/packages/runtime/plugin-router-v5/src/runtime/hooks.ts @@ -1,6 +1,8 @@ -import { createWaterfall } from '@modern-js/plugin'; +import { createSyncHook } from '@modern-js/plugin-v2'; import type { RouteProps } from 'react-router-dom'; -const modifyRoutesHook = createWaterfall(); +// only for inhouse use +const modifyRoutesHook = + createSyncHook<(routes: RouteProps[]) => RouteProps[]>(); export { modifyRoutesHook }; diff --git a/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx b/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx index b24fd796f0aa..9b1914faaaa6 100644 --- a/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx +++ b/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx @@ -1,6 +1,6 @@ import { RuntimeReactContext, isBrowser } from '@meta/runtime'; import { getGlobalLayoutApp, getGlobalRoutes } from '@meta/runtime/context'; -import type { Plugin } from '@modern-js/runtime'; +import type { RuntimePluginFuture } from '@modern-js/runtime'; import { merge } from '@modern-js/runtime-utils/merge'; import { type BrowserHistoryBuildOptions, @@ -61,100 +61,54 @@ export type RouterConfig = Partial & { let routes: SingleRouteConfig[] = []; -export const routerPlugin = (userConfig: RouterConfig = {}): Plugin => { +export const routerPlugin = ( + userConfig: RouterConfig = {}, +): RuntimePluginFuture => { return { name: '@modern-js/plugin-router', - registerHook: { + registryHooks: { modifyRoutes: modifyRoutesHook, }, setup: api => { - return { - beforeRender(context) { - context.router = { - useRouteMatch, - useLocation, - useHistory, - }; - - Object.defineProperty(context, 'routes', { - get() { - return routes; - }, - }); - }, - wrapRoot: App => { - const pluginConfig: Record = - api.useRuntimeConfigContext(); - const { - serverBase = [], - history: customHistory, - supportHtml5History = true, - routesConfig, - createRoutes, - historyOptions = {}, - } = merge(pluginConfig.router || {}, userConfig) as RouterConfig; - const finalRouteConfig = { - routes: getGlobalRoutes() as SingleRouteConfig[], - globalApp: getGlobalLayoutApp(), - ...routesConfig, - }; - const originRoutes = finalRouteConfig?.routes; - const isBrow = isBrowser(); + api.onBeforeRender(context => { + context.router = { + useRouteMatch, + useLocation, + useHistory, + }; - const select = (pathname: string) => - serverBase.find(baseUrl => pathname.search(baseUrl) === 0) || '/'; + Object.defineProperty(context, 'routes', { + get() { + return routes; + }, + }); + }); + api.wrapRoot(App => { + const pluginConfig: Record = api.getRuntimeConfig(); + const { + serverBase = [], + history: customHistory, + supportHtml5History = true, + routesConfig, + createRoutes, + historyOptions = {}, + } = merge(pluginConfig.router || {}, userConfig) as RouterConfig; + const finalRouteConfig = { + routes: getGlobalRoutes() as SingleRouteConfig[], + globalApp: getGlobalLayoutApp(), + ...routesConfig, + }; + const originRoutes = finalRouteConfig?.routes; + const isBrow = isBrowser(); - const getRouteApp = () => { - if (isBrow) { - return (props: any) => { - const runtimeContext = useContext(RuntimeReactContext); - const baseUrl = select(location.pathname).replace(/^\/*/, '/'); - const basename = - baseUrl === '/' - ? urlJoin( - baseUrl, - runtimeContext._internalRouterBaseName || - (historyOptions.basename as string), - ) - : baseUrl; + const select = (pathname: string) => + serverBase.find(baseUrl => pathname.search(baseUrl) === 0) || '/'; - historyOptions.basename = basename; - const history = - customHistory || - (supportHtml5History - ? createBrowserHistory(historyOptions) - : createHashHistory(historyOptions)); - const runner = (api as any).useHookRunners(); - routes = runner.modifyRoutes(originRoutes); - finalRouteConfig && (finalRouteConfig.routes = routes); - /** - * when exist createRoutes function, App.tsx must be exist, and support Component props - * this is compatible config routes - */ - return ( - - {createRoutes ? ( - - ) : App && !finalRouteConfig?.routes ? ( - - ) : ( - renderRoutes(finalRouteConfig, props) - )} - - ); - }; - } + const getRouteApp = () => { + if (isBrow) { return (props: any) => { const runtimeContext = useContext(RuntimeReactContext); - const { ssrContext } = runtimeContext; - const location = getLocation(ssrContext); - const routerContext = ssrContext?.redirection || {}; - const request = ssrContext?.request; - const baseUrl = (request?.baseUrl as string)?.replace( - /^\/*/, - '/', - ); - + const baseUrl = select(location.pathname).replace(/^\/*/, '/'); const basename = baseUrl === '/' ? urlJoin( @@ -163,15 +117,22 @@ export const routerPlugin = (userConfig: RouterConfig = {}): Plugin => { (historyOptions.basename as string), ) : baseUrl; + + historyOptions.basename = basename; + const history = + customHistory || + (supportHtml5History + ? createBrowserHistory(historyOptions) + : createHashHistory(historyOptions)); const runner = (api as any).useHookRunners(); - const routes = runner.modifyRoutes(originRoutes); + routes = runner.modifyRoutes(originRoutes); finalRouteConfig && (finalRouteConfig.routes = routes); + /** + * when exist createRoutes function, App.tsx must be exist, and support Component props + * this is compatible config routes + */ return ( - + {createRoutes ? ( ) : App && !finalRouteConfig?.routes ? ( @@ -179,14 +140,49 @@ export const routerPlugin = (userConfig: RouterConfig = {}): Plugin => { ) : ( renderRoutes(finalRouteConfig, props) )} - + ); }; + } + return (props: any) => { + const runtimeContext = useContext(RuntimeReactContext); + const { ssrContext } = runtimeContext; + const location = getLocation(ssrContext); + const routerContext = ssrContext?.redirection || {}; + const request = ssrContext?.request; + const baseUrl = (request?.baseUrl as string)?.replace(/^\/*/, '/'); + + const basename = + baseUrl === '/' + ? urlJoin( + baseUrl, + runtimeContext._internalRouterBaseName || + (historyOptions.basename as string), + ) + : baseUrl; + const runner = (api as any).useHookRunners(); + const routes = runner.modifyRoutes(originRoutes); + finalRouteConfig && (finalRouteConfig.routes = routes); + return ( + + {createRoutes ? ( + + ) : App && !finalRouteConfig?.routes ? ( + + ) : ( + renderRoutes(finalRouteConfig, props) + )} + + ); }; + }; - return getRouteApp(); - }, - }; + return getRouteApp(); + }); }, }; }; diff --git a/packages/runtime/plugin-runtime/src/core/compat/index.ts b/packages/runtime/plugin-runtime/src/core/compat/index.ts index e0bc82a0598a..494bbd5439ef 100644 --- a/packages/runtime/plugin-runtime/src/core/compat/index.ts +++ b/packages/runtime/plugin-runtime/src/core/compat/index.ts @@ -1,12 +1,6 @@ -import { createAsyncInterruptHook, createSyncHook } from '@modern-js/plugin-v2'; -import type { RuntimeContext } from '../context'; import type { RuntimePluginFuture } from '../plugin/types'; import { getHookRunners } from './hooks'; -type ModifyRoutesFn = (routes: any) => any; - -type BeforeCreateRoutesFn = (context: RuntimeContext) => any; - export const compatPlugin = (): RuntimePluginFuture => ({ name: '@modern-js/runtime-plugin-compat', _registryApi: getRuntimeContext => { @@ -20,8 +14,4 @@ export const compatPlugin = (): RuntimePluginFuture => ({ }, }; }, - registryHooks: { - modifyRoutes: createSyncHook(), - beforeCreateRoutes: createAsyncInterruptHook(), - }, }); diff --git a/packages/runtime/plugin-runtime/src/core/index.ts b/packages/runtime/plugin-runtime/src/core/index.ts index 7ae0e7f0d5c8..5eeba30cef38 100644 --- a/packages/runtime/plugin-runtime/src/core/index.ts +++ b/packages/runtime/plugin-runtime/src/core/index.ts @@ -1,4 +1,4 @@ -export type { Plugin } from './plugin'; +export type { Plugin, RuntimePluginFuture } from './plugin'; export { defineConfig, getConfig, defineRuntimeConfig } from './config'; // compatible diff --git a/packages/runtime/plugin-runtime/src/index.ts b/packages/runtime/plugin-runtime/src/index.ts index 5cedc88eab42..42732d3a0109 100644 --- a/packages/runtime/plugin-runtime/src/index.ts +++ b/packages/runtime/plugin-runtime/src/index.ts @@ -1,7 +1,7 @@ import type { RouterConfig } from './router'; import type { StateConfig } from './state'; -export type { Plugin } from './core'; +export type { Plugin, RuntimePluginFuture } from './core'; export type { AppConfig, RuntimeConfig } from './common'; export { isBrowser } from './common'; diff --git a/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts b/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts index 8ed440816277..fc6a9644225e 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts +++ b/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts @@ -1,12 +1,10 @@ -import { - createAsyncInterruptWorkflow, - createWaterfall, -} from '@modern-js/plugin'; +import { createAsyncInterruptHook, createSyncHook } from '@modern-js/plugin-v2'; import type { RouteObject } from '@modern-js/runtime-utils/router'; import type { RuntimeContext } from '../../core'; // only for inhouse use -const modifyRoutes = createWaterfall(); -const beforeCreateRoutes = createAsyncInterruptWorkflow(); +const modifyRoutes = createSyncHook<(routes: RouteObject[]) => RouteObject[]>(); +const beforeCreateRoutes = + createAsyncInterruptHook<(context: RuntimeContext) => void>(); export { modifyRoutes, beforeCreateRoutes }; diff --git a/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx b/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx index 1a54370cbb10..f2a70a5c225f 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx +++ b/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx @@ -13,9 +13,12 @@ import { import { normalizePathname } from '@modern-js/runtime-utils/url'; import type React from 'react'; import { useContext, useMemo } from 'react'; -import { type Plugin, RuntimeReactContext } from '../../core'; +import { type RuntimePluginFuture, RuntimeReactContext } from '../../core'; import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context'; -import { modifyRoutes as modifyRoutesHook } from './hooks'; +import { + beforeCreateRoutes as beforeCreateRoutesHook, + modifyRoutes as modifyRoutesHook, +} from './hooks'; import type { RouterConfig, Routes } from './types'; import { deserializeErrors, renderRoutes, urlJoin } from './utils'; @@ -39,174 +42,173 @@ export function modifyRoutes(modifyFunction: (routes: Routes) => Routes) { export const routerPlugin = ( userConfig: Partial = {}, -): Plugin => { +): RuntimePluginFuture => { return { name: '@modern-js/plugin-router', - registerHook: { + registryHooks: { modifyRoutes: modifyRoutesHook, + beforeCreateRoutes: beforeCreateRoutesHook, }, setup: api => { let routes: RouteObject[] = []; - return { - beforeRender(context) { - // In some scenarios, the initial pathname and the current pathname do not match. - // We add a configuration to support the page to reload. - if (window._SSR_DATA && userConfig.unstable_reloadOnURLMismatch) { - const { ssrContext } = context; - const currentPathname = normalizePathname(window.location.pathname); - const initialPathname = - ssrContext?.request?.pathname && - normalizePathname(ssrContext.request.pathname); - - if (initialPathname && initialPathname !== currentPathname) { - const errorMsg = `The initial URL ${initialPathname} and the URL ${currentPathname} to be hydrated do not match, reload.`; - console.error(errorMsg); - window.location.reload(); - } - } - // for garfish plugin to get basename, - // why not let garfish plugin just import @modern-js/runtime/router - // so the `router` has no type declare in RuntimeContext - context.router = { - useMatches, - useLocation, - useHref, - }; - - // Prefetch Link will use routes for match next route - Object.defineProperty(context, 'routes', { - get() { - return routes; - }, - }); - }, - wrapRoot: App => { - const pluginConfig: Record = - api.useRuntimeConfigContext(); - const { - serverBase = [], - supportHtml5History = true, - basename = '', - routesConfig, - createRoutes, - future, - } = merge(pluginConfig.router || {}, userConfig) as RouterConfig; - const select = (pathname: string) => - serverBase.find(baseUrl => pathname.search(baseUrl) === 0) || '/'; - finalRouteConfig = { - routes: getGlobalRoutes(), - globalApp: getGlobalLayoutApp(), - ...routesConfig, - }; - - // can not get routes config, skip wrapping React Router. - // e.g. App.tsx as the entrypoint - if (!finalRouteConfig.routes && !createRoutes) { - return App; + api.onBeforeRender(context => { + // In some scenarios, the initial pathname and the current pathname do not match. + // We add a configuration to support the page to reload. + if (window._SSR_DATA && userConfig.unstable_reloadOnURLMismatch) { + const { ssrContext } = context; + const currentPathname = normalizePathname(window.location.pathname); + const initialPathname = + ssrContext?.request?.pathname && + normalizePathname(ssrContext.request.pathname); + + if (initialPathname && initialPathname !== currentPathname) { + const errorMsg = `The initial URL ${initialPathname} and the URL ${currentPathname} to be hydrated do not match, reload.`; + console.error(errorMsg); + window.location.reload(); } - - const getRouteApp = () => { - const useCreateRouter = (props: any) => { - const runtimeContext = useContext(RuntimeReactContext); - /** - * _internalRouterBaseName: garfish plugin params, priority - * basename: modern config file config - */ - const baseUrl = select(location.pathname).replace(/^\/*/, '/'); - const _basename = - baseUrl === '/' - ? urlJoin( - baseUrl, - runtimeContext._internalRouterBaseName || basename, - ) - : baseUrl; - - let hydrationData = window._ROUTER_DATA; - - const { unstable_getBlockNavState: getBlockNavState } = - runtimeContext; - - return useMemo(() => { - if (hydrationData?.errors) { - hydrationData = { - ...hydrationData, - errors: deserializeErrors(hydrationData.errors), - }; - } - - routes = createRoutes - ? createRoutes() - : createRoutesFromElements( - renderRoutes({ - routesConfig: finalRouteConfig, - props, - }), - ); - - const runner = (api as any).useHookRunners(); - // inhouse private, try deprecated, different from the export function - routes = runner.modifyRoutes(routes); - - const router = supportHtml5History - ? createBrowserRouter(routes, { - basename: _basename, - hydrationData, - }) - : createHashRouter(routes, { - basename: _basename, - hydrationData, - }); - - const originSubscribe = router.subscribe; - - router.subscribe = (listener: RouterSubscriber) => { - const wrapedListener: RouterSubscriber = (...args) => { - const blockRoute = getBlockNavState - ? getBlockNavState() - : false; - - if (blockRoute) { - return; - } - return listener(...args); - }; - return originSubscribe(wrapedListener); + } + + // for garfish plugin to get basename, + // why not let garfish plugin just import @modern-js/runtime/router + // so the `router` has no type declare in RuntimeContext + context.router = { + useMatches, + useLocation, + useHref, + }; + + // Prefetch Link will use routes for match next route + Object.defineProperty(context, 'routes', { + get() { + return routes; + }, + }); + }); + api.wrapRoot(App => { + const pluginConfig: Record = api.getRuntimeConfig(); + const { + serverBase = [], + supportHtml5History = true, + basename = '', + routesConfig, + createRoutes, + future, + } = merge(pluginConfig.router || {}, userConfig) as RouterConfig; + const select = (pathname: string) => + serverBase.find(baseUrl => pathname.search(baseUrl) === 0) || '/'; + finalRouteConfig = { + routes: getGlobalRoutes(), + globalApp: getGlobalLayoutApp(), + ...routesConfig, + }; + + // can not get routes config, skip wrapping React Router. + // e.g. App.tsx as the entrypoint + if (!finalRouteConfig.routes && !createRoutes) { + return App; + } + + const getRouteApp = () => { + const useCreateRouter = (props: any) => { + const runtimeContext = useContext(RuntimeReactContext); + /** + * _internalRouterBaseName: garfish plugin params, priority + * basename: modern config file config + */ + const baseUrl = select(location.pathname).replace(/^\/*/, '/'); + const _basename = + baseUrl === '/' + ? urlJoin( + baseUrl, + runtimeContext._internalRouterBaseName || basename, + ) + : baseUrl; + + let hydrationData = window._ROUTER_DATA; + + const { unstable_getBlockNavState: getBlockNavState } = + runtimeContext; + + return useMemo(() => { + if (hydrationData?.errors) { + hydrationData = { + ...hydrationData, + errors: deserializeErrors(hydrationData.errors), }; - - return router; - }, [ - finalRouteConfig, - props, - _basename, - hydrationData, - getBlockNavState, - ]); - }; - - const Null = () => null; - - return (props => { - beforeCreateRouter = false; - const router = useCreateRouter(props); - const routerWrapper = ( - // To match the node tree about https://github.com/web-infra-dev/modern.js/blob/v2.59.0/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx#L150-L168 - // According to react [useId generation algorithm](https://github.com/facebook/react/pull/22644), `useId` will generate id with the react node react struct. - // To void hydration failed, we must guarantee that the node tree when browser hydrate must have same struct with node tree when ssr render. - <> - - - - - ); - - return App ? {routerWrapper} : routerWrapper; - }) as React.ComponentType; + } + + routes = createRoutes + ? createRoutes() + : createRoutesFromElements( + renderRoutes({ + routesConfig: finalRouteConfig, + props, + }), + ); + + const runner = (api as any).useHookRunners(); + // inhouse private, try deprecated, different from the export function + routes = runner.modifyRoutes(routes); + + const router = supportHtml5History + ? createBrowserRouter(routes, { + basename: _basename, + hydrationData, + }) + : createHashRouter(routes, { + basename: _basename, + hydrationData, + }); + + const originSubscribe = router.subscribe; + + router.subscribe = (listener: RouterSubscriber) => { + const wrapedListener: RouterSubscriber = (...args) => { + const blockRoute = getBlockNavState + ? getBlockNavState() + : false; + + if (blockRoute) { + return; + } + return listener(...args); + }; + return originSubscribe(wrapedListener); + }; + + return router; + }, [ + finalRouteConfig, + props, + _basename, + hydrationData, + getBlockNavState, + ]); }; - return getRouteApp(); - }, - }; + const Null = () => null; + + return (props => { + beforeCreateRouter = false; + const router = useCreateRouter(props); + const routerWrapper = ( + // To match the node tree about https://github.com/web-infra-dev/modern.js/blob/v2.59.0/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx#L150-L168 + // According to react [useId generation algorithm](https://github.com/facebook/react/pull/22644), `useId` will generate id with the react node react struct. + // To void hydration failed, we must guarantee that the node tree when browser hydrate must have same struct with node tree when ssr render. + <> + + + + + ); + + return App ? {routerWrapper} : routerWrapper; + }) as React.ComponentType; + }; + + return getRouteApp(); + }); }, }; }; diff --git a/packages/storybook/builder/src/addons/components/modern.tsx b/packages/storybook/builder/src/addons/components/modern.tsx index 589485255a36..2e46ec5540fd 100644 --- a/packages/storybook/builder/src/addons/components/modern.tsx +++ b/packages/storybook/builder/src/addons/components/modern.tsx @@ -1,5 +1,9 @@ import { createApp } from '@modern-js/runtime'; -import type { Plugin, RouterConfig } from '@modern-js/runtime'; +import type { + Plugin, + RouterConfig, + RuntimePluginFuture, +} from '@modern-js/runtime'; import state from '@modern-js/runtime/model'; import router from '@modern-js/runtime/router'; import React from 'react'; @@ -20,7 +24,7 @@ const allowedRuntimeAPI = { const allowedRuntimeAPIValues = Object.values(allowedRuntimeAPI); export const resolvePlugins = (runtime: IConfig['modernConfigRuntime']) => { - const plugins: Plugin[] = []; + const plugins: (Plugin | RuntimePluginFuture)[] = []; if (!runtime) { return plugins; From 9282688358e0c02f12c443114f282f32a2ced059 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 22:13:12 +0800 Subject: [PATCH 14/19] feat: beforeCreateRoutes => onBeforeCreateRoutes --- .../plugin-runtime/src/core/compat/hooks.ts | 6 - .../plugin-runtime/src/core/plugin/types.ts | 3 +- .../src/router/runtime/hooks.ts | 8 +- .../src/router/runtime/plugin.node.tsx | 299 +++++++++--------- .../src/router/runtime/plugin.tsx | 15 +- .../builder/src/addons/components/modern.tsx | 2 +- packages/toolkit/plugin-v2/src/runtime/api.ts | 9 + .../plugin-v2/src/types/runtime/api.ts | 5 + 8 files changed, 183 insertions(+), 164 deletions(-) diff --git a/packages/runtime/plugin-runtime/src/core/compat/hooks.ts b/packages/runtime/plugin-runtime/src/core/compat/hooks.ts index d1847f53263e..f8412a194448 100644 --- a/packages/runtime/plugin-runtime/src/core/compat/hooks.ts +++ b/packages/runtime/plugin-runtime/src/core/compat/hooks.ts @@ -54,11 +54,5 @@ export function getHookRunners( modifyRuntimeConfig: (config: RuntimeConfig) => { return hooks.modifyRuntimeConfig.call(config); }, - modifyRoutes: (routes: any) => { - return hooks.modifyRoutes.call(routes); - }, - beforeCreateRoutes: (context: RuntimeContext) => { - return hooks.beforeCreateRoutes.call(context); - }, }; } diff --git a/packages/runtime/plugin-runtime/src/core/plugin/types.ts b/packages/runtime/plugin-runtime/src/core/plugin/types.ts index 73e2a023aaa1..0a4962d5711a 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/types.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/types.ts @@ -12,7 +12,8 @@ export type RuntimeExtends = Required< RuntimePluginExtends >; -export type RuntimePluginFuture = BaseRuntimePlugin; +export type RuntimePluginFuture = + BaseRuntimePlugin; export interface RuntimeConfig { plugins?: (Plugin | RuntimePluginFuture)[]; } diff --git a/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts b/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts index fc6a9644225e..42f88bb983b4 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts +++ b/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts @@ -4,7 +4,9 @@ import type { RuntimeContext } from '../../core'; // only for inhouse use const modifyRoutes = createSyncHook<(routes: RouteObject[]) => RouteObject[]>(); -const beforeCreateRoutes = - createAsyncInterruptHook<(context: RuntimeContext) => void>(); +const onBeforeCreateRoutes = + createAsyncInterruptHook< + (context: RuntimeContext, interrupt: (info: any) => any) => void + >(); -export { modifyRoutes, beforeCreateRoutes }; +export { modifyRoutes, onBeforeCreateRoutes }; diff --git a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx index 59cad96aa8bd..224e7e03b772 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx +++ b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx @@ -15,12 +15,12 @@ import type React from 'react'; import { useContext } from 'react'; import { JSX_SHELL_STREAM_END_MARK } from '../../common'; import { RuntimeReactContext } from '../../core'; -import type { Plugin } from '../../core'; +import type { RuntimePluginFuture } from '../../core'; import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context'; import DeferredDataScripts from './DeferredDataScripts.node'; import { - beforeCreateRoutes as beforeCreateRoutesHook, modifyRoutes as modifyRoutesHook, + onBeforeCreateRoutes as onBeforeCreateRoutesHook, } from './hooks'; import type { RouterConfig } from './types'; import { renderRoutes, urlJoin } from './utils'; @@ -39,162 +39,165 @@ function createRemixReuqest(request: Request) { export const routerPlugin = ( userConfig: Partial = {}, -): Plugin => { +): RuntimePluginFuture<{ + extendHooks: { + modifyRoutes: typeof modifyRoutesHook; + onBeforeCreateRoutes: typeof onBeforeCreateRoutesHook; + }; +}> => { return { name: '@modern-js/plugin-router', - registerHook: { + registryHooks: { modifyRoutes: modifyRoutesHook, - beforeCreateRoutes: beforeCreateRoutesHook, + onBeforeCreateRoutes: onBeforeCreateRoutesHook, }, setup: api => { let finalRouteConfig: any = {}; - return { - async beforeRender(context, interrupt) { - const pluginConfig: Record = - api.useRuntimeConfigContext(); - const { - basename = '', - routesConfig, - createRoutes, - } = merge(pluginConfig.router || {}, userConfig); - finalRouteConfig = { - routes: getGlobalRoutes(), - globalApp: getGlobalLayoutApp(), - ...routesConfig, - }; - // can not get routes config, skip wrapping React Router. - // e.g. App.tsx as the entrypoint - if (!finalRouteConfig.routes && !createRoutes) { - return; - } - - const { - request, - mode: ssrMode, - nonce, - loaderFailureMode = 'errorBoundary', - } = context.ssrContext!; - const { baseUrl } = request; - const _basename = - baseUrl === '/' ? urlJoin(baseUrl, basename) : baseUrl; - const { reporter } = context.ssrContext!; - const requestContext = createRequestContext( - context.ssrContext?.loaderContext, - ); - requestContext.set(reporterCtx, reporter); - const runner = (api as any).useHookRunners(); - - await runner.beforeCreateRoutes(context); - - let routes = createRoutes - ? createRoutes() - : createRoutesFromElements( - renderRoutes({ - routesConfig: finalRouteConfig, - ssrMode, - props: { - nonce, - }, - reporter, - }), - ); - - routes = runner.modifyRoutes(routes); - - const { query } = createStaticHandler(routes, { - basename: _basename, - }); - - // We can't pass post request to query,due to post request would triger react-router submit action. - // But user maybe do not define action for page. - const remixRequest = createRemixReuqest( - context.ssrContext!.request.raw, - ); - - const end = time(); - const routerContext = await query(remixRequest, { - requestContext, - }); - const cost = end(); - context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost); - - if (routerContext instanceof Response) { - // React Router would return a Response when redirects occur in loader. - // Throw the Response to bail out and let the server handle it with an HTTP redirect - return interrupt(routerContext); - } - - // Now `throw new Response` or `throw new Error` is same, both will be caught by errorBoundary by default - // If loaderFailureMode is 'clientRender', we will downgrade to csr, and the loader will be request in client again - const errors = Object.values( - (routerContext.errors || {}) as Record, - ); - if ( - // TODO: if loaderFailureMode is not 'errroBoundary', error log will not be printed. - errors.length > 0 && - loaderFailureMode === 'clientRender' - ) { - routerContext.statusCode = 200; - throw errors[0]; - } - - const router = createStaticRouter(routes, routerContext); - // routerContext is used in in css colletor、handle status code、inject loader data in html - context.routerContext = routerContext; - - // private api, pass to React Component in `wrapRoot` - // in the browser, we not need to pass router, cause we create Router in `wrapRoot` - // but in node, we need to pass router, cause we need run async function, it can only run in `beforeRender` - // when we deprected React 17, we can use Suspense to handle this async function - // so the `remixRouter` has no type declare in RuntimeContext - context.remixRouter = router; - - // private api, pass to React Component in `wrapRoot` - context.routes = routes; - }, - wrapRoot: App => { - // can not get routes config, skip wrapping React Router. - // e.g. App.tsx as the entrypoint - if (!finalRouteConfig) { - return App; - } - - const getRouteApp = () => { - return (() => { - const context = useContext(RuntimeReactContext); - const { remixRouter, routerContext, ssrContext } = context; - - const { nonce, mode, useJsonScript } = ssrContext!; - - const routerWrapper = ( - <> - { + const pluginConfig: Record = api.getRuntimeConfig(); + const { + basename = '', + routesConfig, + createRoutes, + } = merge(pluginConfig.router || {}, userConfig); + finalRouteConfig = { + routes: getGlobalRoutes(), + globalApp: getGlobalLayoutApp(), + ...routesConfig, + }; + // can not get routes config, skip wrapping React Router. + // e.g. App.tsx as the entrypoint + if (!finalRouteConfig.routes && !createRoutes) { + return; + } + + const { + request, + mode: ssrMode, + nonce, + loaderFailureMode = 'errorBoundary', + } = context.ssrContext!; + const { baseUrl } = request; + const _basename = + baseUrl === '/' ? urlJoin(baseUrl, basename) : baseUrl; + const { reporter } = context.ssrContext!; + const requestContext = createRequestContext( + context.ssrContext?.loaderContext, + ); + requestContext.set(reporterCtx, reporter); + const runner = (api as any).useHookRunners(); + + await runner.beforeCreateRoutes(context); + + let routes = createRoutes + ? createRoutes() + : createRoutesFromElements( + renderRoutes({ + routesConfig: finalRouteConfig, + ssrMode, + props: { + nonce, + }, + reporter, + }), + ); + + routes = runner.modifyRoutes(routes); + + const { query } = createStaticHandler(routes, { + basename: _basename, + }); + + // We can't pass post request to query,due to post request would triger react-router submit action. + // But user maybe do not define action for page. + const remixRequest = createRemixReuqest( + context.ssrContext!.request.raw, + ); + + const end = time(); + const routerContext = await query(remixRequest, { + requestContext, + }); + const cost = end(); + context.ssrContext?.onTiming?.(LOADER_REPORTER_NAME, cost); + + if (routerContext instanceof Response) { + // React Router would return a Response when redirects occur in loader. + // Throw the Response to bail out and let the server handle it with an HTTP redirect + return interrupt(routerContext); + } + + // Now `throw new Response` or `throw new Error` is same, both will be caught by errorBoundary by default + // If loaderFailureMode is 'clientRender', we will downgrade to csr, and the loader will be request in client again + const errors = Object.values( + (routerContext.errors || {}) as Record, + ); + if ( + // TODO: if loaderFailureMode is not 'errroBoundary', error log will not be printed. + errors.length > 0 && + loaderFailureMode === 'clientRender' + ) { + routerContext.statusCode = 200; + throw errors[0]; + } + + const router = createStaticRouter(routes, routerContext); + // routerContext is used in in css colletor、handle status code、inject loader data in html + context.routerContext = routerContext; + + // private api, pass to React Component in `wrapRoot` + // in the browser, we not need to pass router, cause we create Router in `wrapRoot` + // but in node, we need to pass router, cause we need run async function, it can only run in `beforeRender` + // when we deprected React 17, we can use Suspense to handle this async function + // so the `remixRouter` has no type declare in RuntimeContext + context.remixRouter = router; + + // private api, pass to React Component in `wrapRoot` + context.routes = routes; + }); + + api.wrapRoot(App => { + // can not get routes config, skip wrapping React Router. + // e.g. App.tsx as the entrypoint + if (!finalRouteConfig) { + return App; + } + + const getRouteApp = () => { + return (() => { + const context = useContext(RuntimeReactContext); + const { remixRouter, routerContext, ssrContext } = context; + + const { nonce, mode, useJsonScript } = ssrContext!; + + const routerWrapper = ( + <> + + + {mode === 'stream' && ( + // ROUTER_DATA will inject in `packages/runtime/plugin-runtime/src/core/server/string/ssrData.ts` in string ssr + // So we can inject it only when streaming ssr + + )} + {mode === 'stream' && JSX_SHELL_STREAM_END_MARK} + + ); + + return App ? {routerWrapper} : routerWrapper; + }) as React.FC; + }; - {mode === 'stream' && ( - // ROUTER_DATA will inject in `packages/runtime/plugin-runtime/src/core/server/string/ssrData.ts` in string ssr - // So we can inject it only when streaming ssr - - )} - {mode === 'stream' && JSX_SHELL_STREAM_END_MARK} - - ); - - return App ? {routerWrapper} : routerWrapper; - }) as React.FC; - }; - - return getRouteApp(); - }, - }; + return getRouteApp(); + }); }, }; }; diff --git a/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx b/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx index f2a70a5c225f..41aafe58008d 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx +++ b/packages/runtime/plugin-runtime/src/router/runtime/plugin.tsx @@ -16,8 +16,8 @@ import { useContext, useMemo } from 'react'; import { type RuntimePluginFuture, RuntimeReactContext } from '../../core'; import { getGlobalLayoutApp, getGlobalRoutes } from '../../core/context'; import { - beforeCreateRoutes as beforeCreateRoutesHook, modifyRoutes as modifyRoutesHook, + onBeforeCreateRoutes as onBeforeCreateRoutesHook, } from './hooks'; import type { RouterConfig, Routes } from './types'; import { deserializeErrors, renderRoutes, urlJoin } from './utils'; @@ -42,12 +42,17 @@ export function modifyRoutes(modifyFunction: (routes: Routes) => Routes) { export const routerPlugin = ( userConfig: Partial = {}, -): RuntimePluginFuture => { +): RuntimePluginFuture<{ + extendHooks: { + modifyRoutes: typeof modifyRoutesHook; + onBeforeCreateRoutes: typeof onBeforeCreateRoutesHook; + }; +}> => { return { name: '@modern-js/plugin-router', registryHooks: { modifyRoutes: modifyRoutesHook, - beforeCreateRoutes: beforeCreateRoutesHook, + onBeforeCreateRoutes: onBeforeCreateRoutesHook, }, setup: api => { let routes: RouteObject[] = []; @@ -147,9 +152,9 @@ export const routerPlugin = ( }), ); - const runner = (api as any).useHookRunners(); + const hooks = api.getHooks(); // inhouse private, try deprecated, different from the export function - routes = runner.modifyRoutes(routes); + routes = hooks.modifyRoutes.call(routes); const router = supportHtml5History ? createBrowserRouter(routes, { diff --git a/packages/storybook/builder/src/addons/components/modern.tsx b/packages/storybook/builder/src/addons/components/modern.tsx index 2e46ec5540fd..00340b0f3740 100644 --- a/packages/storybook/builder/src/addons/components/modern.tsx +++ b/packages/storybook/builder/src/addons/components/modern.tsx @@ -45,7 +45,7 @@ export const resolvePlugins = (runtime: IConfig['modernConfigRuntime']) => { router({ ...{ serverBase: ['/'] }, ...(runtime.router as RouterConfig), - }), + }) as RuntimePluginFuture, ); } } diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index 2b73785bd455..4e61c88114ac 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -37,6 +37,14 @@ export function initPluginAPI({ ) { context = merge(context, updateContext); } + + function getHooks() { + return { + ...hooks, + ...extendsHooks, + }; + } + function getRuntimeConfig() { if (context.config) { return context.config; @@ -70,6 +78,7 @@ export function initPluginAPI({ return { getRuntimeContext, updateRuntimeContext, + getHooks, getRuntimeConfig, modifyRuntimeConfig: hooks.modifyRuntimeConfig.tap, onBeforeRender: hooks.onBeforeRender.tap, diff --git a/packages/toolkit/plugin-v2/src/types/runtime/api.ts b/packages/toolkit/plugin-v2/src/types/runtime/api.ts index 813e96379542..d8c5742d9d04 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/api.ts @@ -1,3 +1,4 @@ +import type { Hooks } from '../../runtime'; import type { PluginHookTap } from '../hooks'; import type { DeepPartial } from '../utils'; import type { RuntimeContext } from './context'; @@ -12,6 +13,10 @@ import type { RuntimePluginExtends } from './plugin'; export type RuntimePluginAPI = Readonly<{ getRuntimeContext: () => Readonly; updateRuntimeContext: (updateContext: DeepPartial) => void; + getHooks: () => Readonly< + Hooks & + Extends['extendHooks'] + >; getRuntimeConfig: () => Readonly; onBeforeRender: PluginHookTap>; wrapRoot: PluginHookTap; From 8dcfce821cdfaf5eea6583b85dde04b3c1919e67 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Wed, 8 Jan 2025 22:16:33 +0800 Subject: [PATCH 15/19] fix: router v5 unit test --- .../runtime/plugin-router-v5/src/runtime/hooks.ts | 3 ++- .../plugin-router-v5/src/runtime/plugin.tsx | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/runtime/plugin-router-v5/src/runtime/hooks.ts b/packages/runtime/plugin-router-v5/src/runtime/hooks.ts index 8063b11162e3..11861d4af6f6 100644 --- a/packages/runtime/plugin-router-v5/src/runtime/hooks.ts +++ b/packages/runtime/plugin-router-v5/src/runtime/hooks.ts @@ -1,8 +1,9 @@ import { createSyncHook } from '@modern-js/plugin-v2'; import type { RouteProps } from 'react-router-dom'; +import type { SingleRouteConfig } from './plugin'; // only for inhouse use const modifyRoutesHook = - createSyncHook<(routes: RouteProps[]) => RouteProps[]>(); + createSyncHook<(routes: RouteProps[]) => SingleRouteConfig[]>(); export { modifyRoutesHook }; diff --git a/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx b/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx index 9b1914faaaa6..b7e16d40065c 100644 --- a/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx +++ b/packages/runtime/plugin-router-v5/src/runtime/plugin.tsx @@ -63,7 +63,11 @@ let routes: SingleRouteConfig[] = []; export const routerPlugin = ( userConfig: RouterConfig = {}, -): RuntimePluginFuture => { +): RuntimePluginFuture<{ + extendHooks: { + modifyRoutes: typeof modifyRoutesHook; + }; +}> => { return { name: '@modern-js/plugin-router', registryHooks: { @@ -124,8 +128,8 @@ export const routerPlugin = ( (supportHtml5History ? createBrowserHistory(historyOptions) : createHashHistory(historyOptions)); - const runner = (api as any).useHookRunners(); - routes = runner.modifyRoutes(originRoutes); + const hooks = api.getHooks(); + routes = hooks.modifyRoutes.call(originRoutes); finalRouteConfig && (finalRouteConfig.routes = routes); /** * when exist createRoutes function, App.tsx must be exist, and support Component props @@ -160,8 +164,8 @@ export const routerPlugin = ( (historyOptions.basename as string), ) : baseUrl; - const runner = (api as any).useHookRunners(); - const routes = runner.modifyRoutes(originRoutes); + const hooks = api.getHooks(); + const routes = hooks.modifyRoutes.call(originRoutes); finalRouteConfig && (finalRouteConfig.routes = routes); return ( Date: Thu, 9 Jan 2025 16:19:25 +0800 Subject: [PATCH 16/19] fix: pick context function to sync function --- packages/toolkit/plugin-v2/src/runtime/hooks.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/toolkit/plugin-v2/src/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/runtime/hooks.ts index 6e4677866f48..16ee34e3a635 100644 --- a/packages/toolkit/plugin-v2/src/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/runtime/hooks.ts @@ -1,5 +1,4 @@ import { - createAsyncHook, createAsyncInterruptHook, createCollectSyncHook, createSyncHook, @@ -16,7 +15,7 @@ export function initHooks() { onBeforeRender: createAsyncInterruptHook>(), wrapRoot: createSyncHook(), - pickContext: createAsyncHook>(), + pickContext: createSyncHook>(), modifyRuntimeConfig: createCollectSyncHook>(), }; From f224ec0eb052243f05ee4cb6cd3f2d5f6b5da2de Mon Sep 17 00:00:00 2001 From: caohuilin Date: Mon, 13 Jan 2025 18:36:13 +0800 Subject: [PATCH 17/19] fix: hooks --- .../plugin-runtime/src/router/runtime/plugin.node.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx index 224e7e03b772..97c3af0a2c8b 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx +++ b/packages/runtime/plugin-runtime/src/router/runtime/plugin.node.tsx @@ -86,9 +86,9 @@ export const routerPlugin = ( context.ssrContext?.loaderContext, ); requestContext.set(reporterCtx, reporter); - const runner = (api as any).useHookRunners(); + const hooks = api.getHooks(); - await runner.beforeCreateRoutes(context); + await hooks.onBeforeCreateRoutes.call(context); let routes = createRoutes ? createRoutes() @@ -103,7 +103,7 @@ export const routerPlugin = ( }), ); - routes = runner.modifyRoutes(routes); + routes = hooks.modifyRoutes.call(routes); const { query } = createStaticHandler(routes, { basename: _basename, From 7556631daafdb35bcca1a4fba85c18c2a936894e Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Jan 2025 10:27:48 +0800 Subject: [PATCH 18/19] fix: comments --- .../runtime/plugin-garfish/src/cli/code.ts | 2 +- .../runtime/plugin-garfish/src/cli/hooks.ts | 6 +++ .../runtime/plugin-garfish/src/cli/index.ts | 7 +-- .../plugin-runtime/src/core/browser/index.tsx | 6 +-- .../plugin-runtime/src/core/compatible.tsx | 2 +- .../plugin-runtime/src/core/plugin/base.ts | 43 ++++++------------- .../plugin-runtime/src/core/plugin/index.ts | 3 +- .../src/router/runtime/hooks.ts | 4 +- packages/toolkit/plugin-v2/src/hooks.ts | 1 - packages/toolkit/plugin-v2/src/manager.ts | 3 ++ packages/toolkit/plugin-v2/src/runtime/api.ts | 1 - .../toolkit/plugin-v2/src/runtime/hooks.ts | 10 ++--- .../toolkit/plugin-v2/src/runtime/index.tsx | 10 ++++- .../plugin-v2/src/runtime/run/create.ts | 2 - .../plugin-v2/src/types/runtime/api.ts | 3 +- .../plugin-v2/src/types/runtime/context.ts | 2 +- .../plugin-v2/src/types/runtime/hooks.ts | 8 ++++ .../plugin-v2/src/types/runtime/index.ts | 1 + 18 files changed, 55 insertions(+), 59 deletions(-) create mode 100644 packages/runtime/plugin-garfish/src/cli/hooks.ts diff --git a/packages/runtime/plugin-garfish/src/cli/code.ts b/packages/runtime/plugin-garfish/src/cli/code.ts index 939bdb90484d..e4a6e412f719 100644 --- a/packages/runtime/plugin-garfish/src/cli/code.ts +++ b/packages/runtime/plugin-garfish/src/cli/code.ts @@ -8,7 +8,7 @@ import type { import type { CollectAsyncHook } from '@modern-js/plugin-v2'; import type { Entrypoint } from '@modern-js/types'; import { fs } from '@modern-js/utils'; -import type { AppendEntryCodeFn } from '.'; +import type { AppendEntryCodeFn } from './hooks'; import * as template from './template'; import { generateAsyncEntryCode } from './utils'; diff --git a/packages/runtime/plugin-garfish/src/cli/hooks.ts b/packages/runtime/plugin-garfish/src/cli/hooks.ts new file mode 100644 index 000000000000..077e657b112d --- /dev/null +++ b/packages/runtime/plugin-garfish/src/cli/hooks.ts @@ -0,0 +1,6 @@ +import type { Entrypoint } from '@modern-js/types'; + +export type AppendEntryCodeFn = (params: { + entrypoint: Entrypoint; + code: string; +}) => string | Promise; diff --git a/packages/runtime/plugin-garfish/src/cli/index.ts b/packages/runtime/plugin-garfish/src/cli/index.ts index a77e59de0bcb..4233a71325df 100644 --- a/packages/runtime/plugin-garfish/src/cli/index.ts +++ b/packages/runtime/plugin-garfish/src/cli/index.ts @@ -5,10 +5,10 @@ import type { } from '@modern-js/app-tools'; import type { CliHookCallbacks, useConfigContext } from '@modern-js/core'; import { createCollectAsyncHook } from '@modern-js/plugin-v2'; -import type { Entrypoint } from '@modern-js/types'; import { createRuntimeExportsUtils, getEntryOptions } from '@modern-js/utils'; import { logger } from '../util'; import { generateCode } from './code'; +import type { AppendEntryCodeFn } from './hooks'; import { getRuntimeConfig, setRuntimeConfig } from './utils'; export type UseConfig = ReturnType; @@ -39,11 +39,6 @@ export function getDefaultMicroFrontedConfig( }; } -export type AppendEntryCodeFn = (params: { - entrypoint: Entrypoint; - code: string; -}) => string | Promise; - export const garfishPlugin = (): CliPluginFuture> => ({ name: '@modern-js/plugin-garfish', pre: ['@modern-js/runtime'], diff --git a/packages/runtime/plugin-runtime/src/core/browser/index.tsx b/packages/runtime/plugin-runtime/src/core/browser/index.tsx index f2d28dd3ae98..d24d94a3bebd 100644 --- a/packages/runtime/plugin-runtime/src/core/browser/index.tsx +++ b/packages/runtime/plugin-runtime/src/core/browser/index.tsx @@ -84,9 +84,9 @@ export async function render( const context: RuntimeContext = getInitialContext(); const runBeforeRender = async (context: RuntimeContext) => { const internalRuntimeContext = getGlobalInternalRuntimeContext(); - const api = internalRuntimeContext?.pluginAPI; - api?.updateRuntimeContext(context); - const hooks = internalRuntimeContext?.hooks; + const api = internalRuntimeContext!.pluginAPI; + api!.updateRuntimeContext(context); + const hooks = internalRuntimeContext!.hooks; await hooks.onBeforeRender.call(context); const init = getGlobalAppInit(); return init?.(context); diff --git a/packages/runtime/plugin-runtime/src/core/compatible.tsx b/packages/runtime/plugin-runtime/src/core/compatible.tsx index a69878a0866f..297e5cc5c948 100644 --- a/packages/runtime/plugin-runtime/src/core/compatible.tsx +++ b/packages/runtime/plugin-runtime/src/core/compatible.tsx @@ -108,7 +108,7 @@ export const bootstrap: BootStrap = async ( const hooks = internalRuntimeContext.hooks; const context: RuntimeContext = getInitialContext(); - api?.updateRuntimeContext(context); + api!.updateRuntimeContext(context); const runBeforeRender = async (context: RuntimeContext) => { await hooks.onBeforeRender.call(context); diff --git a/packages/runtime/plugin-runtime/src/core/plugin/base.ts b/packages/runtime/plugin-runtime/src/core/plugin/base.ts index 139c5abdb883..41587355c0bc 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/base.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/base.ts @@ -1,40 +1,21 @@ -import { - type PluginOptions, - type Setup, - createAsyncInterruptWorkflow, - createSyncParallelWorkflow, - createWaterfall, +import type { + AsyncInterruptWorkflow, + PluginOptions, + Setup, + SyncParallelWorkflow, + Waterfall, } from '@modern-js/plugin'; import type { RuntimeContext, TRuntimeContext } from '../context/runtime'; import type { RuntimeConfig } from './types'; -// biome-ignore lint/suspicious/noEmptyInterface: -export interface AppProps {} - -const wrapRoot = createWaterfall>(); - -const beforeRender = createAsyncInterruptWorkflow(); - -/** - * To add runtime info to runtime context - */ -const pickContext = createWaterfall(); - -const modifyRuntimeConfig = createSyncParallelWorkflow< - void, - Record ->(); - -const runtimeHooks = { - beforeRender, - wrapRoot, - pickContext, - modifyRuntimeConfig, -}; - /** All hooks of runtime plugin. */ -export type RuntimeHooks = typeof runtimeHooks; +export type RuntimeHooks = { + beforeRender: AsyncInterruptWorkflow; + wrapRoot: Waterfall>; + pickContext: Waterfall; + modifyRuntimeConfig: SyncParallelWorkflow>; +}; export type RuntimePluginAPI = { useRuntimeConfigContext: () => RuntimeConfig }; diff --git a/packages/runtime/plugin-runtime/src/core/plugin/index.ts b/packages/runtime/plugin-runtime/src/core/plugin/index.ts index 2190ba2874f9..36d90df382d1 100644 --- a/packages/runtime/plugin-runtime/src/core/plugin/index.ts +++ b/packages/runtime/plugin-runtime/src/core/plugin/index.ts @@ -1,4 +1,5 @@ -import type { InternalRuntimeContext, Plugin } from '@modern-js/plugin-v2'; +import type { Plugin } from '@modern-js/plugin-v2'; +import type { InternalRuntimeContext } from '@modern-js/plugin-v2/runtime'; import { runtime } from '@modern-js/plugin-v2/runtime'; import { merge } from '@modern-js/runtime-utils/merge'; import { compatPlugin } from '../compat'; diff --git a/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts b/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts index 42f88bb983b4..91e2d42ee8c7 100644 --- a/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts +++ b/packages/runtime/plugin-runtime/src/router/runtime/hooks.ts @@ -5,8 +5,6 @@ import type { RuntimeContext } from '../../core'; // only for inhouse use const modifyRoutes = createSyncHook<(routes: RouteObject[]) => RouteObject[]>(); const onBeforeCreateRoutes = - createAsyncInterruptHook< - (context: RuntimeContext, interrupt: (info: any) => any) => void - >(); + createSyncHook<(context: RuntimeContext) => void>(); export { modifyRoutes, onBeforeCreateRoutes }; diff --git a/packages/toolkit/plugin-v2/src/hooks.ts b/packages/toolkit/plugin-v2/src/hooks.ts index 255212f03739..c0b650cc7729 100644 --- a/packages/toolkit/plugin-v2/src/hooks.ts +++ b/packages/toolkit/plugin-v2/src/hooks.ts @@ -21,7 +21,6 @@ export function createAsyncInterruptHook< let interruptResult: any; const interrupt = (info: any) => { - console.log('===interrupt'); interrupted = true; interruptResult = info; }; diff --git a/packages/toolkit/plugin-v2/src/manager.ts b/packages/toolkit/plugin-v2/src/manager.ts index 50b9899eaf1c..065922eb9ef9 100644 --- a/packages/toolkit/plugin-v2/src/manager.ts +++ b/packages/toolkit/plugin-v2/src/manager.ts @@ -1,3 +1,6 @@ +/** + * NOTE: This file will be used in both Node.js and runtime environments. Please avoid importing Node.js-specific APIs. + */ import type { Plugin, PluginManager } from './types/plugin'; import type { Falsy, MaybePromise } from './types/utils'; diff --git a/packages/toolkit/plugin-v2/src/runtime/api.ts b/packages/toolkit/plugin-v2/src/runtime/api.ts index 4e61c88114ac..d45a77e0c29f 100644 --- a/packages/toolkit/plugin-v2/src/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/runtime/api.ts @@ -76,7 +76,6 @@ export function initPluginAPI({ } return { - getRuntimeContext, updateRuntimeContext, getHooks, getRuntimeConfig, diff --git a/packages/toolkit/plugin-v2/src/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/runtime/hooks.ts index 16ee34e3a635..e017f427de15 100644 --- a/packages/toolkit/plugin-v2/src/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/runtime/hooks.ts @@ -4,13 +4,17 @@ import { createSyncHook, } from '../hooks'; import type { + Hooks, ModifyRuntimeConfigFn, OnBeforeRenderFn, PickContextFn, WrapRootFn, } from '../types/runtime/hooks'; -export function initHooks() { +export function initHooks(): Hooks< + RuntimeConfig, + RuntimeContext +> { return { onBeforeRender: createAsyncInterruptHook>(), @@ -20,7 +24,3 @@ export function initHooks() { createCollectSyncHook>(), }; } - -export type Hooks = ReturnType< - typeof initHooks ->; diff --git a/packages/toolkit/plugin-v2/src/runtime/index.tsx b/packages/toolkit/plugin-v2/src/runtime/index.tsx index 6c9823784245..5048caecec5e 100644 --- a/packages/toolkit/plugin-v2/src/runtime/index.tsx +++ b/packages/toolkit/plugin-v2/src/runtime/index.tsx @@ -1,4 +1,12 @@ export { initPluginAPI } from './api'; export { initRuntimeContext, createRuntimeContext } from './context'; -export { initHooks, type Hooks } from './hooks'; +export { initHooks } from './hooks'; export { runtime } from './run'; +export type { + RuntimePluginAPI, + RuntimeContext, + InternalRuntimeContext, + RuntimePlugin, + RuntimePluginExtends, + Hooks, +} from '../types/runtime'; diff --git a/packages/toolkit/plugin-v2/src/runtime/run/create.ts b/packages/toolkit/plugin-v2/src/runtime/run/create.ts index 02a66e14c18e..edf5926a8c35 100644 --- a/packages/toolkit/plugin-v2/src/runtime/run/create.ts +++ b/packages/toolkit/plugin-v2/src/runtime/run/create.ts @@ -60,8 +60,6 @@ export const createRuntime = () => { } return { - init, run, - getPrevInitOptions: () => initOptions, }; }; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/api.ts b/packages/toolkit/plugin-v2/src/types/runtime/api.ts index d8c5742d9d04..6a01a38e78c1 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/api.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/api.ts @@ -1,7 +1,7 @@ -import type { Hooks } from '../../runtime'; import type { PluginHookTap } from '../hooks'; import type { DeepPartial } from '../utils'; import type { RuntimeContext } from './context'; +import type { Hooks } from './hooks'; import type { ModifyRuntimeConfigFn, OnBeforeRenderFn, @@ -11,7 +11,6 @@ import type { import type { RuntimePluginExtends } from './plugin'; export type RuntimePluginAPI = Readonly<{ - getRuntimeContext: () => Readonly; updateRuntimeContext: (updateContext: DeepPartial) => void; getHooks: () => Readonly< Hooks & diff --git a/packages/toolkit/plugin-v2/src/types/runtime/context.ts b/packages/toolkit/plugin-v2/src/types/runtime/context.ts index 03b308389a4f..f808868d6667 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/context.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/context.ts @@ -1,5 +1,5 @@ -import type { Hooks } from '../../runtime/hooks'; import type { RuntimePluginAPI } from './api'; +import type { Hooks } from './hooks'; import type { RuntimePluginExtends } from './plugin'; export type RuntimeContext = {}; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts index 87870c2406b6..7947a7b7d965 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/hooks.ts @@ -1,4 +1,5 @@ import type React from 'react'; +import type { AsyncInterruptHook, CollectSyncHook, SyncHook } from '../hooks'; export type OnBeforeRenderFn = ( context: RuntimeContext, @@ -16,3 +17,10 @@ export type PickContextFn = ( export type ModifyRuntimeConfigFn = ( config: RuntimeConfig, ) => RuntimeConfig; + +export type Hooks = { + onBeforeRender: AsyncInterruptHook>; + wrapRoot: SyncHook; + pickContext: SyncHook>; + modifyRuntimeConfig: CollectSyncHook>; +}; diff --git a/packages/toolkit/plugin-v2/src/types/runtime/index.ts b/packages/toolkit/plugin-v2/src/types/runtime/index.ts index 73638882fc45..a4c30fb7990a 100644 --- a/packages/toolkit/plugin-v2/src/types/runtime/index.ts +++ b/packages/toolkit/plugin-v2/src/types/runtime/index.ts @@ -1,3 +1,4 @@ export type { RuntimePluginAPI } from './api'; export type { RuntimeContext, InternalRuntimeContext } from './context'; export type { RuntimePlugin, RuntimePluginExtends } from './plugin'; +export type { Hooks } from './hooks'; From 7b542e5747587322170fabbbbf91f64e6cc392ab Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Jan 2025 10:30:24 +0800 Subject: [PATCH 19/19] feat: update lock file --- pnpm-lock.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d40a2afc35c..25966815262c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4015,6 +4015,9 @@ importers: '@modern-js/node-bundle-require': specifier: workspace:* version: link:../node-bundle-require + '@modern-js/runtime-utils': + specifier: workspace:* + version: link:../runtime-utils '@modern-js/utils': specifier: workspace:* version: link:../utils @@ -4043,6 +4046,9 @@ importers: '@types/node': specifier: ^14 version: 14.18.35 + '@types/react': + specifier: ^18.3.11 + version: 18.3.18 jest: specifier: ^29 version: 29.5.0(@types/node@14.18.35)(ts-node@10.9.2(@swc/core@1.10.7(@swc/helpers@0.5.13))(@types/node@14.18.35)(typescript@5.6.3))