diff --git a/.changeset/witty-hotels-collect.md b/.changeset/witty-hotels-collect.md new file mode 100644 index 000000000000..bb637c03af22 --- /dev/null +++ b/.changeset/witty-hotels-collect.md @@ -0,0 +1,7 @@ +--- +'@modern-js/runtime': patch +--- + +feat: migrate runtime cli plugin to new cli plugin + +feat: runtime CLI 插件迁移到新的 CLI 插件 diff --git a/packages/cli/core/src/context.ts b/packages/cli/core/src/context.ts index be5f796be2e7..276a53dd82d9 100644 --- a/packages/cli/core/src/context.ts +++ b/packages/cli/core/src/context.ts @@ -52,7 +52,7 @@ export const initAppContext = ({ appDirectory: string; plugins: CliPlugin[]; configFile: string | false; - runtimeConfigFile: string | false; + runtimeConfigFile: string; options?: { metaName?: string; srcDir?: string; diff --git a/packages/document/module-doc/docs/en/guide/basic/command-preview.md b/packages/document/module-doc/docs/en/guide/basic/command-preview.md index 6849e4a30a8b..c06b3383d146 100644 --- a/packages/document/module-doc/docs/en/guide/basic/command-preview.md +++ b/packages/document/module-doc/docs/en/guide/basic/command-preview.md @@ -47,7 +47,6 @@ The following features can currently be enabled. - Storybook V7 - Tailwind CSS support -- Modern.js Runtime API You can learn more about these features in the [Using the micro generator](/guide/basic/use-micro-generator) section. diff --git a/packages/document/module-doc/docs/en/guide/basic/use-micro-generator.md b/packages/document/module-doc/docs/en/guide/basic/use-micro-generator.md index 69359a67f8f2..049b29a3520c 100644 --- a/packages/document/module-doc/docs/en/guide/basic/use-micro-generator.md +++ b/packages/document/module-doc/docs/en/guide/basic/use-micro-generator.md @@ -43,24 +43,3 @@ The **Storybook feature** can be enabled when we want to debug a component or a [Tailwind CSS](https://tailwindcss.com/) is a CSS framework and design system based on Utility Class, which can quickly add common styles to components, and support flexible extension of theme styles. If you want to use Tailwind CSS for a project, you can refer to ["Using Tailwind CSS"](https://modernjs.dev/module-tools/guide/best-practices/components.html#tailwind-css). - -## Modern.js Runtime API - -**Modern.js provides Runtime API capabilities that can only be used in the Modern.js application project environment**. If you need to develop a component for use in a Modern.js application environment, then you can turn on this feature and the microgenerator will add the `"@modern-js/runtime"` dependency. - -Also, the Storybook debugging tool will determine if the project needs to use the Runtime API by checking the project's dependencies and providing the same Runtime API runtime environment as the Modern.js application project. - -:::tip - -After successfully enabling it, you will be prompted to manually add a code similar to the one below to the configuration. - -```ts -import { moduleTools, defineConfig } from '@modern-js/module-tools'; -import runtime from '@modern-js/runtime/cli'; - -export default defineConfig({ - plugins: [moduleTools(), runtime()], -}); -``` - -::: diff --git a/packages/document/module-doc/docs/zh/guide/basic/command-preview.md b/packages/document/module-doc/docs/zh/guide/basic/command-preview.md index 22d7823e9b35..e48fcdcaeaf8 100644 --- a/packages/document/module-doc/docs/zh/guide/basic/command-preview.md +++ b/packages/document/module-doc/docs/zh/guide/basic/command-preview.md @@ -47,7 +47,6 @@ Options: - Storybook V7 - Tailwind CSS 支持 -- Modern.js Runtime API 关于这些功能,可以通过[「使用微生成器」](/guide/basic/use-micro-generator) 章节了解更多。 diff --git a/packages/document/module-doc/docs/zh/guide/basic/use-micro-generator.md b/packages/document/module-doc/docs/zh/guide/basic/use-micro-generator.md index 197d716a80e2..981b91211919 100644 --- a/packages/document/module-doc/docs/zh/guide/basic/use-micro-generator.md +++ b/packages/document/module-doc/docs/zh/guide/basic/use-micro-generator.md @@ -43,24 +43,3 @@ export default defineConfig({ [Tailwind CSS](https://tailwindcss.com/) 是一个以 Utility Class 为基础的 CSS 框架和设计系统,可以快速地为组件添加常用样式,同时支持主题样式的灵活扩展。 如果你想要在项目使用 [Tailwind CSS](https://tailwindcss.com/),可以参考 [「使用 Tailwind CSS」](https://modernjs.dev/module-tools/guide/best-practices/components.html#tailwind-css)。 - -## Modern.js Runtime API 支持 - -**Modern.js 提供了 [Runtime API](https://modernjs.dev/configure/app/runtime/intro) 能力,这些 API 只能在 Modern.js 的应用项目环境中使用**。如果你需要开发一个 Modern.js 应用环境中使用的组件,那么你可以开启该特性,微生成器会增加 `"@modern-js/runtime"`依赖。 - -另外,Storybook 调试工具也会通过检测项目的依赖确定项目是否需要使用 Runtime API,并且提供与 Modern.js 应用项目一样的 Runtime API 运行环境。 - -:::tip - -在成功开启后,会提示需要手动在配置中增加如下类似的代码。 - -```ts -import { moduleTools, defineConfig } from '@modern-js/module-tools'; -import runtime from '@modern-js/runtime/cli'; - -export default defineConfig({ - plugins: [moduleTools(), runtime()], -}); -``` - -::: diff --git a/packages/runtime/plugin-garfish/jest.config.js b/packages/runtime/plugin-garfish/jest.config.js index 1f37fa70d3d3..95cbeb567fa1 100644 --- a/packages/runtime/plugin-garfish/jest.config.js +++ b/packages/runtime/plugin-garfish/jest.config.js @@ -6,5 +6,9 @@ module.exports = { rootDir: __dirname, moduleNameMapper: { '^@meta/runtime$': '/node_modules/@modern-js/runtime/src', + '^@modern-js/runtime/browser$': + '/node_modules/@modern-js/runtime/src/core/browser', + '^@modern-js/runtime/react$': + '/node_modules/@modern-js/runtime/src/core/react', }, }; diff --git a/packages/runtime/plugin-garfish/package.json b/packages/runtime/plugin-garfish/package.json index 3121842cbedb..e90bcbed508d 100644 --- a/packages/runtime/plugin-garfish/package.json +++ b/packages/runtime/plugin-garfish/package.json @@ -71,7 +71,7 @@ "test": "jest --passWithNoTests" }, "dependencies": { - "@modern-js/plugin": "workspace:*", + "@modern-js/plugin-v2": "workspace:*", "@modern-js/runtime-utils": "workspace:*", "@modern-js/utils": "workspace:*", "@swc/helpers": "0.5.13", diff --git a/packages/runtime/plugin-garfish/src/cli/code.ts b/packages/runtime/plugin-garfish/src/cli/code.ts index 4e246bab0de6..9f15aa64e6b8 100644 --- a/packages/runtime/plugin-garfish/src/cli/code.ts +++ b/packages/runtime/plugin-garfish/src/cli/code.ts @@ -1,10 +1,10 @@ import path from 'path'; import type { AppTools, - IAppContext, + AppToolsContext, + AppToolsFeatureHooks, NormalizedConfig, } from '@modern-js/app-tools'; -import type { MaybeAsync } from '@modern-js/plugin'; import type { Entrypoint } from '@modern-js/types'; import { fs } from '@modern-js/utils'; import * as template from './template'; @@ -15,9 +15,9 @@ export const ENTRY_BOOTSTRAP_FILE_NAME = 'bootstrap.jsx'; export const generateCode = async ( entrypoints: Entrypoint[], - appContext: IAppContext, + appContext: AppToolsContext<'shared'>, config: NormalizedConfig, - appendEntryCode: (input: { entrypoint: Entrypoint }) => MaybeAsync, + hooks: AppToolsFeatureHooks<'shared'>, ) => { const { mountId } = config.html; const { enableAsyncEntry } = config.source; @@ -27,7 +27,7 @@ export const generateCode = async ( entrypoints.map(async entrypoint => { const { entryName, isAutoMount, entry, customEntry, customBootstrap } = entrypoint; - const appendCode = await appendEntryCode({ entrypoint }); + const appendCode = await hooks.appendEntryCode.call({ entrypoint }); 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 6aa533d9db41..5c143ddc1717 100644 --- a/packages/runtime/plugin-garfish/src/cli/index.ts +++ b/packages/runtime/plugin-garfish/src/cli/index.ts @@ -1,6 +1,10 @@ -import type { AppTools, CliPlugin } from '@modern-js/app-tools'; +import type { + AppNormalizedConfig, + AppTools, + CliPluginFuture, +} from '@modern-js/app-tools'; import type { CliHookCallbacks, useConfigContext } from '@modern-js/core'; -import { type AsyncWorkflow, createAsyncWorkflow } from '@modern-js/plugin'; +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'; @@ -35,211 +39,194 @@ export function getDefaultMicroFrontedConfig( }; } -const appendEntryCode = createAsyncWorkflow< - { entrypoint: Entrypoint }, - string ->(); +type AppendEntryCodeFn = (params: { + entrypoint: Entrypoint; + code: string; +}) => string | Promise; -export const garfishPlugin = (): CliPlugin< - AppTools & { - hooks: { - appendEntryCode: AsyncWorkflow<{ entrypoint: Entrypoint }, string>; - }; - } -> => ({ +export const garfishPlugin = (): CliPluginFuture> => ({ name: '@modern-js/plugin-garfish', pre: ['@modern-js/runtime'], - registerHook: { - appendEntryCode, + registryHooks: { + appendEntryCode: createCollectAsyncHook(), }, setup: api => { - return { - _internalRuntimePlugins({ entrypoint, plugins }) { - const userConfig = api.useResolvedConfigContext(); - const { packageName, metaName } = api.useAppContext(); - const runtimeConfig = getEntryOptions( - entrypoint.entryName, - entrypoint.isMainEntry, - userConfig.runtime, - userConfig.runtimeByEntries, - packageName, - ); - if (runtimeConfig?.masterApp) { - plugins.push({ - name: 'garfish', - path: `@${metaName}/plugin-garfish/runtime`, - config: runtimeConfig?.masterApp || {}, - }); - } - return { entrypoint, plugins }; - }, - resolvedConfig: async config => { - const { resolved } = config; - const { masterApp, router } = getRuntimeConfig(resolved); - const nConfig = { - resolved: { - ...resolved, - }, - }; - if (masterApp) { - const useConfig = api.useConfigContext(); - const baseUrl = useConfig?.server?.baseUrl; - if (Array.isArray(baseUrl)) { - throw new Error( - 'Now Micro-Front-End mode dose not support multiple baseUrl, you can set it as a string', - ); - } - // basename does not exist use router's basename - setRuntimeConfig( - nConfig.resolved, - 'masterApp', - Object.assign( - typeof masterApp === 'object' ? { ...masterApp } : {}, - { - basename: - baseUrl || - router?.historyOptions?.basename || - router?.basename || - '/', - }, - ), - ); - } - logger(`resolvedConfig`, { - output: nConfig.resolved.output, - runtime: nConfig.resolved.runtime, - deploy: nConfig.resolved.deploy, - server: nConfig.resolved.server, + api._internalRuntimePlugins(({ entrypoint, plugins }) => { + const userConfig = api.getNormalizedConfig(); + const { packageName, metaName } = api.getAppContext(); + const runtimeConfig = getEntryOptions( + entrypoint.entryName, + entrypoint.isMainEntry, + userConfig.runtime, + userConfig.runtimeByEntries, + packageName, + ); + if (runtimeConfig?.masterApp) { + plugins.push({ + name: 'garfish', + path: `@${metaName}/plugin-garfish/runtime`, + config: runtimeConfig?.masterApp || {}, }); - return nConfig; - }, - config() { - const useConfig = api.useConfigContext(); - const { metaName, packageName } = api.useAppContext(); - logger('useConfig', useConfig); - - let disableCssExtract = useConfig.output?.disableCssExtract || false; - - // When the micro-frontend application js entry, there is no need to extract css, close cssExtract - if (useConfig.deploy?.microFrontend) { - const { enableHtmlEntry } = getDefaultMicroFrontedConfig( - useConfig.deploy?.microFrontend, + } + return { entrypoint, plugins }; + }); + api.modifyResolvedConfig(config => { + const { masterApp, router } = getRuntimeConfig(config); + if (masterApp) { + const useConfig = api.getConfig(); + const baseUrl = useConfig?.server?.baseUrl; + if (Array.isArray(baseUrl)) { + throw new Error( + 'Now Micro-Front-End mode dose not support multiple baseUrl, you can set it as a string', ); - if (!enableHtmlEntry) { - disableCssExtract = true; - } } - - return { - output: { - disableCssExtract, + // basename does not exist use router's basename + setRuntimeConfig( + config, + 'masterApp', + Object.assign(typeof masterApp === 'object' ? { ...masterApp } : {}, { + basename: + baseUrl || + router?.historyOptions?.basename || + router?.basename || + '/', + }), + ); + } + logger(`resolvedConfig`, { + output: config.output, + runtime: config.runtime, + deploy: config.deploy, + server: config.server, + }); + return config; + }); + api.config(() => { + const useConfig = api.getConfig(); + const { metaName, packageName } = api.getAppContext(); + logger('useConfig', useConfig); + + let disableCssExtract = useConfig.output?.disableCssExtract || false; + + // When the micro-frontend application js entry, there is no need to extract css, close cssExtract + if (useConfig.deploy?.microFrontend) { + const { enableHtmlEntry } = getDefaultMicroFrontedConfig( + useConfig.deploy?.microFrontend, + ); + if (!enableHtmlEntry) { + disableCssExtract = true; + } + } + + return { + output: { + disableCssExtract, + }, + source: { + alias: { + [`@${metaName}/runtime/garfish`]: `@${metaName}/plugin-garfish/runtime`, }, - source: { - alias: { - [`@${metaName}/runtime/garfish`]: `@${metaName}/plugin-garfish/runtime`, + }, + tools: { + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', }, }, - tools: { - devServer: { - headers: { - 'Access-Control-Allow-Origin': '*', - }, - }, - bundlerChain: (chain, { env, CHAIN_ID, bundler }) => { - // add comments avoid sourcemap abnormal - if (bundler.BannerPlugin) { - chain - .plugin('garfish-banner') - .use(bundler.BannerPlugin, [{ banner: 'Micro front-end' }]); + bundlerChain: (chain, { env, CHAIN_ID, bundler }) => { + // add comments avoid sourcemap abnormal + if (bundler.BannerPlugin) { + chain + .plugin('garfish-banner') + .use(bundler.BannerPlugin, [{ banner: 'Micro front-end' }]); + } + + const resolveOptions = api.getNormalizedConfig(); + if (resolveOptions?.deploy?.microFrontend) { + chain.output.libraryTarget('umd'); + + const DEFAULT_ASSET_PREFIX = '/'; + + // Only override assetPrefix when using the default asset prefix, + // this allows user or other plugins to set asset prefix. + const resolvedAssetPrefix = resolveOptions.dev?.assetPrefix; + const isUsingDefaultAssetPrefix = + !useConfig.dev?.assetPrefix && + (!resolvedAssetPrefix || + resolvedAssetPrefix === DEFAULT_ASSET_PREFIX); + + if ( + isUsingDefaultAssetPrefix && + resolveOptions?.server?.port && + env === 'development' + ) { + chain.output.publicPath( + `//localhost:${resolveOptions.server.port}/`, + ); } - const resolveOptions = api.useResolvedConfigContext(); - if (resolveOptions?.deploy?.microFrontend) { - chain.output.libraryTarget('umd'); - - const DEFAULT_ASSET_PREFIX = '/'; - - // Only override assetPrefix when using the default asset prefix, - // this allows user or other plugins to set asset prefix. - const resolvedAssetPrefix = resolveOptions.dev?.assetPrefix; - const isUsingDefaultAssetPrefix = - !useConfig.dev?.assetPrefix && - (!resolvedAssetPrefix || - resolvedAssetPrefix === DEFAULT_ASSET_PREFIX); - - if ( - isUsingDefaultAssetPrefix && - resolveOptions?.server?.port && - env === 'development' - ) { - chain.output.publicPath( - `//localhost:${resolveOptions.server.port}/`, - ); - } - - const { enableHtmlEntry, externalBasicLibrary } = - getDefaultMicroFrontedConfig( - resolveOptions.deploy?.microFrontend, - ); - // external - if (externalBasicLibrary) { - chain.externals(externals); - } - // use html mode - if (!enableHtmlEntry) { - chain.output.filename('index.js'); - chain.plugins.delete(`${CHAIN_ID.PLUGIN.HTML}-main`); - chain.optimization.runtimeChunk(false); - chain.optimization.splitChunks({ - chunks: 'async', - }); - } + const { enableHtmlEntry, externalBasicLibrary } = + getDefaultMicroFrontedConfig( + resolveOptions.deploy?.microFrontend, + ); + // external + if (externalBasicLibrary) { + chain.externals(externals); } - const uniqueName = chain.output.get('uniqueName'); - if (!uniqueName) { - chain.output.uniqueName(packageName); + // use html mode + if (!enableHtmlEntry) { + chain.output.filename('index.js'); + chain.plugins.delete(`${CHAIN_ID.PLUGIN.HTML}-main`); + chain.optimization.runtimeChunk(false); + chain.optimization.splitChunks({ + chunks: 'async', + }); } - const resolveConfig = chain.toConfig(); - logger('bundlerConfig', { - output: resolveConfig.output, - externals: resolveConfig.externals, - env, - alias: resolveConfig.resolve?.alias, - plugins: resolveConfig.plugins, - }); - }, + } + const uniqueName = chain.output.get('uniqueName'); + if (!uniqueName) { + chain.output.uniqueName(packageName); + } + const resolveConfig = chain.toConfig(); + logger('bundlerConfig', { + output: resolveConfig.output, + externals: resolveConfig.externals, + env, + alias: resolveConfig.resolve?.alias, + plugins: resolveConfig.plugins, + }); }, - }; - }, - addRuntimeExports() { - const config = api.useResolvedConfigContext(); - const { masterApp } = getRuntimeConfig(config); - const { internalDirectory, metaName } = api.useAppContext(); - const pluginsExportsUtils = createRuntimeExportsUtils( - internalDirectory, - 'plugins', + }, + }; + }); + api.addRuntimeExports(() => { + const config = api.getNormalizedConfig(); + const { masterApp } = getRuntimeConfig(config); + const { internalDirectory, metaName } = api.useAppContext(); + const pluginsExportsUtils = createRuntimeExportsUtils( + internalDirectory, + 'plugins', + ); + if (masterApp) { + const addExportStatement = `export { default as garfish, default as masterApp } from '@${metaName}/plugin-garfish/runtime'`; + logger('exportStatement', addExportStatement); + pluginsExportsUtils.addExport(addExportStatement); + } + }); + api.generateEntryCode(async ({ entrypoints }) => { + const resolveOptions = api.getNormalizedConfig(); + if (resolveOptions?.deploy?.microFrontend) { + const appContext = api.getAppContext(); + const resolvedConfig = api.getNormalizedConfig(); + const hooks = api.getHooks(); + await generateCode( + entrypoints, + appContext, + resolvedConfig as AppNormalizedConfig, + hooks, ); - if (masterApp) { - const addExportStatement = `export { default as garfish, default as masterApp } from '@${metaName}/plugin-garfish/runtime'`; - logger('exportStatement', addExportStatement); - pluginsExportsUtils.addExport(addExportStatement); - } - }, - async generateEntryCode({ entrypoints }) { - const resolveOptions = api.useResolvedConfigContext(); - if (resolveOptions?.deploy?.microFrontend) { - const appContext = api.useAppContext(); - const resolvedConfig = api.useResolvedConfigContext(); - const { appendEntryCode } = api.useHookRunners(); - await generateCode( - entrypoints, - appContext, - resolvedConfig, - appendEntryCode, - ); - } - }, - }; + } + }); }, }); diff --git a/packages/runtime/plugin-garfish/tests/cli.test.tsx b/packages/runtime/plugin-garfish/tests/cli.test.tsx index 4c5f23d3384e..915adee3718d 100644 --- a/packages/runtime/plugin-garfish/tests/cli.test.tsx +++ b/packages/runtime/plugin-garfish/tests/cli.test.tsx @@ -1,10 +1,12 @@ import '@testing-library/jest-dom'; -import { manager, CliPlugin } from '@modern-js/core'; +import { type AppTools } from '@modern-js/app-tools'; import WebpackChain from '@modern-js/utils/webpack-chain'; -import type { AppUserConfig } from '@modern-js/app-tools'; import { garfishPlugin, externals } from '../src/cli'; -import type { UseConfig } from '../src/cli'; import { getRuntimeConfig, setRuntimeConfig } from '../src/cli/utils'; +import { createPluginManager, Plugin } from '@modern-js/plugin-v2'; +import runtimePlugin from '@modern-js/runtime/cli'; +import { createContext, initPluginAPI } from '@modern-js/plugin-v2/cli'; +import path from 'path'; const CHAIN_ID = { PLUGIN: { @@ -12,41 +14,86 @@ const CHAIN_ID = { }, }; +async function setup(config: any) { + const pluginManager = createPluginManager(); + + pluginManager.addPlugins([runtimePlugin() as Plugin, garfishPlugin() as Plugin]); + const plugins = pluginManager.getPlugins(); + const context = await createContext({ + appContext: { + plugins, + appDirectory: path.join(__dirname, './feature'), + } as any, + config: config, + normalizedConfig: { ...config, plugins: [] } as any, + }); + const pluginAPI = { + ...initPluginAPI({ + context, + pluginManager, + }), + checkEntryPoint: ({ path, entry }: any) => { + return { path, entry }; + }, + modifyEntrypoints: ({ entrypoints }: any) => { + return { entrypoints }; + }, + generateEntryCode: async ({ entrypoints }: any) => {}, + _internalRuntimePlugins: ({ entrypoint, plugins }: any) => { + return { entrypoint, plugins }; + }, + addRuntimeExports: () => {}, + modifyFileSystemRoutes: () => {}, + onBeforeGenerateRoutes: () => {}, + }; + pluginAPI.config(() => { + return config + }) + pluginAPI.modifyResolvedConfig(() => { + return config + }) + for (const plugin of plugins) { + await plugin.setup(pluginAPI); + } + return pluginAPI; +} + describe('plugin-garfish cli', () => { - test('cli garfish basename', async () => { + test('cli garfish instance', async () => { expect(garfishPlugin().name).toBe('@modern-js/plugin-garfish'); + }); - const main = manager.clone().usePlugin(garfishPlugin as CliPlugin); - const runner = await main.init(); - await runner.prepare(); - const configHistoryOptions: any = await runner.resolvedConfig({ - resolved: { - runtime: { - router: { - historyOptions: { basename: '/test' }, - }, - masterApp: {}, + test('test config historyOptions', async () => { + const pluginAPI = await setup({ + runtime: { + router: { + historyOptions: { basename: '/test' }, }, + masterApp: {}, }, - } as any); - - expect(configHistoryOptions.resolved.runtime.masterApp.basename).toBe( + }); + const hooks = pluginAPI.getHooks(); + const configHistory: any = await hooks.modifyResolvedConfig.call({} as any); + expect(configHistory.runtime.masterApp.basename).toBe( '/test', ); + }) - const configHistory: any = await runner.resolvedConfig({ - resolved: { - runtime: { - router: { - basename: '/test2', - }, - masterApp: {}, + test('test config basename', async () => { + const pluginAPI = await setup({ + runtime: { + router: { + basename: '/test2', }, + masterApp: {}, }, - } as any); - - expect(configHistory.resolved.runtime.masterApp.basename).toBe('/test2'); - }); + }); + const hooks = pluginAPI.getHooks(); + const configHistory: any = await hooks.modifyResolvedConfig.call({} as any); + expect(configHistory.runtime.masterApp.basename).toBe( + '/test2', + ); + }) test('cli get runtime config', () => { const runtimeConfig = getRuntimeConfig({ @@ -107,7 +154,10 @@ describe('plugin-garfish cli', () => { }); test('webpack config close external and use js entry', async () => { - const resolveConfig: any = { + const webpackConfig = new WebpackChain(); + function HTMLWebpackPlugin() {} + webpackConfig.plugin('html-main').use(HTMLWebpackPlugin); + const pluginAPI = await setup({ deploy: { microFrontend: { externalBasicLibrary: true, @@ -117,23 +167,10 @@ describe('plugin-garfish cli', () => { server: { port: 8080, }, - }; - - const main = manager - .clone({ - useResolvedConfigContext: () => resolveConfig, - }) - .usePlugin(garfishPlugin as CliPlugin); - - const runner = await main.init(); - await runner.prepare(); - const config: any = await runner.config(); - const webpackConfig = new WebpackChain(); - - function HTMLWebpackPlugin() {} - webpackConfig.plugin('html-main').use(HTMLWebpackPlugin); - - config[0].tools.bundlerChain(webpackConfig, { + }); + const hooks = pluginAPI.getHooks(); + const configs: any = await hooks.config.call(); + configs[5].tools.bundlerChain(webpackConfig, { webpack: jest.fn(), env: 'development', CHAIN_ID, @@ -147,7 +184,6 @@ describe('plugin-garfish cli', () => { }, } }); - const generateConfig = webpackConfig.toConfig(); expect(generateConfig).toMatchSnapshot(); expect(generateConfig).toMatchObject({ @@ -159,31 +195,24 @@ describe('plugin-garfish cli', () => { externals, optimization: { runtimeChunk: false, splitChunks: { chunks: 'async' } }, }); - }); + + }) test('webpack config default micro config', async () => { - const resolveConfig: any = { + const webpackConfig = new WebpackChain(); + function HTMLWebpackPlugin() {} + webpackConfig.plugin('html-main').use(HTMLWebpackPlugin); + const pluginAPI = await setup({ deploy: { microFrontend: true, }, server: { port: '8080', }, - }; - - const main = manager - .clone({ - useResolvedConfigContext: () => resolveConfig, - }) - .usePlugin(garfishPlugin as CliPlugin); - const runner = await main.init(); - await runner.prepare(); - const config: any = await runner.config(); - const webpackConfig = new WebpackChain(); - function HTMLWebpackPlugin() {} - webpackConfig.plugin('html-main').use(HTMLWebpackPlugin); - - config[0].tools.bundlerChain(webpackConfig, { + }); + const hooks = pluginAPI.getHooks(); + const configs: any = await hooks.config.call(); + configs[5].tools.bundlerChain(webpackConfig, { webpack: jest.fn(), env: 'development', CHAIN_ID, @@ -197,9 +226,8 @@ describe('plugin-garfish cli', () => { }, } }); - const generateConfig = webpackConfig.toConfig(); - expect(config[0].tools.devServer).toMatchObject({ + expect(configs[5].tools.devServer).toMatchObject({ headers: { 'Access-Control-Allow-Origin': '*', }, @@ -214,30 +242,21 @@ describe('plugin-garfish cli', () => { }); expect(generateConfig.externals).toBeUndefined(); expect(generateConfig.output!.filename).toBeUndefined(); - }); + }) test('micro fronted default config disableCssExtract false', async () => { - const resolveConfig: Partial = { + const pluginAPI = await setup({ deploy: { microFrontend: {}, }, - }; - - const main = manager - .clone({ - useResolvedConfigContext: () => resolveConfig as any, - useConfigContext: () => resolveConfig, - }) - .usePlugin(garfishPlugin as CliPlugin); - - const runner = await main.init(); - await runner.prepare(); - const config = (await runner.config()) as AppUserConfig[]; - expect(config[0].output!.disableCssExtract).toBe(false); - }); + }); + const hooks = pluginAPI.getHooks(); + const configs: any = await hooks.config.call(); + expect(configs[5].output!.disableCssExtract).toBe(false); + }) test('micro fronted js entry disableCssExtract true', async () => { - const resolveConfig: Partial = { + const pluginAPI = await setup({ output: { disableCssExtract: false, }, @@ -246,32 +265,17 @@ describe('plugin-garfish cli', () => { enableHtmlEntry: false, }, }, - }; - - const main = manager - .clone({ - useResolvedConfigContext: () => resolveConfig as any, - useConfigContext: () => resolveConfig, - }) - .usePlugin(garfishPlugin as CliPlugin); - const runner = await main.init(); - await runner.prepare(); - const config = (await runner.config()) as AppUserConfig[]; - expect(config[0].output!.disableCssExtract).toBe(true); - }); + }); + const hooks = pluginAPI.getHooks(); + const configs: any = await hooks.config.call(); + expect(configs[5].output!.disableCssExtract).toBe(true); + }) test('normal disableCssExtract false', async () => { - const resolveConfig: Partial = {}; + const pluginAPI = await setup({}) + const hooks = pluginAPI.getHooks(); + const configs: any = await hooks.config.call(); + expect(configs[5].output!.disableCssExtract).toBe(false); + }) - const main = manager - .clone({ - useResolvedConfigContext: () => resolveConfig as any, - useConfigContext: () => resolveConfig, - }) - .usePlugin(garfishPlugin as CliPlugin); - const runner = await main.init(); - await runner.prepare(); - const config = (await runner.config()) as AppUserConfig[]; - expect(config[0].output!.disableCssExtract).toBe(false); - }); }); diff --git a/packages/runtime/plugin-router-v5/package.json b/packages/runtime/plugin-router-v5/package.json index cb7e77139fd2..0b24082986cb 100644 --- a/packages/runtime/plugin-router-v5/package.json +++ b/packages/runtime/plugin-router-v5/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@modern-js/plugin": "workspace:*", + "@modern-js/plugin-v2": "workspace:*", "@modern-js/runtime-utils": "workspace:*", "@modern-js/types": "workspace:*", "@modern-js/utils": "workspace:*", diff --git a/packages/runtime/plugin-router-v5/src/cli/index.ts b/packages/runtime/plugin-router-v5/src/cli/index.ts index 16ba925ed631..c9cc4c3e0d4c 100644 --- a/packages/runtime/plugin-router-v5/src/cli/index.ts +++ b/packages/runtime/plugin-router-v5/src/cli/index.ts @@ -1,4 +1,4 @@ -import type { AppTools, CliPlugin } from '@modern-js/app-tools'; +import type { AppTools, CliPluginFuture } from '@modern-js/app-tools'; import { createRuntimeExportsUtils, getEntryOptions, @@ -7,74 +7,72 @@ import { import './types'; import type { ServerRoute } from '@modern-js/types'; -export const routerPlugin = (): CliPlugin => ({ +export const routerPlugin = (): CliPluginFuture => ({ name: '@modern-js/plugin-router-v5', required: ['@modern-js/runtime'], setup: api => { let routerExportsUtils: any; - return { - _internalRuntimePlugins({ entrypoint, plugins }) { - const userConfig = api.useResolvedConfigContext(); - const { serverRoutes, metaName, packageName } = api.useAppContext(); - if (isV5(userConfig)) { - const routerConfig = getEntryOptions( - entrypoint.entryName, - entrypoint.isMainEntry, - userConfig.runtime, - userConfig.runtimeByEntries, - packageName, - )?.router; - const serverBase = serverRoutes - .filter( - (route: ServerRoute) => route.entryName === entrypoint.entryName, - ) - .map(route => route.urlPath) - .sort((a, b) => (a.length - b.length > 0 ? -1 : 1)); - plugins.push({ - name: 'router', - path: `@${metaName}/plugin-router-v5/runtime`, - config: - typeof routerConfig === 'boolean' - ? { serverBase } - : { ...routerConfig, serverBase }, - }); - } - return { entrypoint, plugins }; - }, - config() { - const { internalDirectory, metaName } = api.useAppContext(); - // .modern-js/.runtime-exports/router (legacy) - routerExportsUtils = createRuntimeExportsUtils( - internalDirectory, - 'router', - ); + api._internalRuntimePlugins(({ entrypoint, plugins }) => { + const userConfig = api.getNormalizedConfig(); + const { serverRoutes, metaName, packageName } = api.getAppContext(); + if (isV5(userConfig)) { + const routerConfig = getEntryOptions( + entrypoint.entryName, + entrypoint.isMainEntry, + userConfig.runtime, + userConfig.runtimeByEntries, + packageName, + )?.router; + const serverBase = serverRoutes + .filter( + (route: ServerRoute) => route.entryName === entrypoint.entryName, + ) + .map(route => route.urlPath) + .sort((a, b) => (a.length - b.length > 0 ? -1 : 1)); + plugins.push({ + name: 'router', + path: `@${metaName}/plugin-router-v5/runtime`, + config: + typeof routerConfig === 'boolean' + ? { serverBase } + : { ...routerConfig, serverBase }, + }); + } + return { entrypoint, plugins }; + }); + api.config(() => { + const { internalDirectory, metaName } = api.getAppContext(); + // .modern-js/.runtime-exports/router (legacy) + routerExportsUtils = createRuntimeExportsUtils( + internalDirectory, + 'router', + ); - return { - source: { - alias: { - [`@${metaName}/runtime/router-v5`]: routerExportsUtils.getPath(), - }, + return { + source: { + alias: { + [`@${metaName}/runtime/router-v5`]: routerExportsUtils.getPath(), }, - }; - }, - addRuntimeExports() { - const userConfig = api.useResolvedConfigContext(); - const { internalDirectory, metaName } = api.useAppContext(); - const pluginsExportsUtils = createRuntimeExportsUtils( - internalDirectory, - 'plugins', + }, + }; + }); + api.addRuntimeExports(() => { + const userConfig = api.getNormalizedConfig(); + const { internalDirectory, metaName } = api.getAppContext(); + const pluginsExportsUtils = createRuntimeExportsUtils( + internalDirectory, + 'plugins', + ); + if (isV5(userConfig)) { + pluginsExportsUtils.addExport( + `export { default as router } from '@${metaName}/plugin-router-v5/runtime'`, + ); + routerExportsUtils?.addExport( + `export * from '@${metaName}/plugin-router-v5/runtime'`, ); - if (isV5(userConfig)) { - pluginsExportsUtils.addExport( - `export { default as router } from '@${metaName}/plugin-router-v5/runtime'`, - ); - routerExportsUtils?.addExport( - `export * from '@${metaName}/plugin-router-v5/runtime'`, - ); - } - }, - }; + } + }); }, }); diff --git a/packages/runtime/plugin-router-v5/tests/index.test.ts b/packages/runtime/plugin-router-v5/tests/index.test.ts index 922340dca282..84003b0413c4 100644 --- a/packages/runtime/plugin-router-v5/tests/index.test.ts +++ b/packages/runtime/plugin-router-v5/tests/index.test.ts @@ -1,5 +1,7 @@ -import { AppContext, manager } from '@modern-js/core'; -import RuntimePlugin from '@modern-js/runtime/cli'; +import type { AppTools } from '@modern-js/app-tools'; +import { createPluginManager } from '@modern-js/plugin-v2'; +import { createContext, initPluginAPI } from '@modern-js/plugin-v2/cli'; +import runtimePlugin from '@modern-js/runtime/cli'; import plugin, { useHistory, useParams } from '../src'; import cliPlugin from '../src/cli'; @@ -12,20 +14,51 @@ describe('plugin-router-legacy', () => { }); describe('cli-router-legacy', () => { - const main = manager.clone().usePlugin(RuntimePlugin, cliPlugin as any); - let runner: any; - - beforeAll(async () => { - runner = await main.init(); - }); + const setup = async () => { + const pluginManager = createPluginManager(); + pluginManager.addPlugins([runtimePlugin(), cliPlugin()]); + const plugins = pluginManager.getPlugins(); + const context = await createContext({ + appContext: { + plugins, + } as any, + config: {}, + normalizedConfig: { plugins: [] } as any, + }); + const pluginAPI = { + ...initPluginAPI({ + context, + pluginManager, + }), + checkEntryPoint: ({ path, entry }: any) => { + return { path, entry }; + }, + modifyEntrypoints: ({ entrypoints }: any) => { + return { entrypoints }; + }, + generateEntryCode: async ({ entrypoints }: any) => {}, + _internalRuntimePlugins: ({ entrypoint, plugins }: any) => { + return { entrypoint, plugins }; + }, + addRuntimeExports: () => {}, + modifyFileSystemRoutes: () => {}, + onBeforeGenerateRoutes: () => {}, + }; + context.pluginAPI = pluginAPI; + for (const plugin of plugins) { + await plugin.setup(pluginAPI); + } + return pluginAPI; + }; test('should plugin-router-legacy defined', async () => { expect(cliPlugin).toBeDefined(); }); it('plugin-router-legacy cli config is defined', async () => { - AppContext.set({ metaName: 'modern-js' } as any); - const config = await runner.config(); + const api = await setup(); + api.updateAppContext({ metaName: 'modern-js' } as any); + const config = await api.getHooks().config.call(); expect( config.find( (item: any) => item.source.alias['@modern-js/runtime/plugins'], diff --git a/packages/runtime/plugin-runtime/package.json b/packages/runtime/plugin-runtime/package.json index aea4fa5c0b0d..831e8dc6a620 100644 --- a/packages/runtime/plugin-runtime/package.json +++ b/packages/runtime/plugin-runtime/package.json @@ -196,6 +196,7 @@ "@modern-js-reduck/react": "^1.1.10", "@modern-js-reduck/store": "^1.1.10", "@modern-js/plugin": "workspace:*", + "@modern-js/plugin-v2": "workspace:*", "@modern-js/plugin-data-loader": "workspace:*", "@modern-js/runtime-utils": "workspace:*", "@modern-js/types": "workspace:*", @@ -220,7 +221,6 @@ }, "devDependencies": { "@modern-js/app-tools": "workspace:*", - "@modern-js/core": "workspace:*", "@remix-run/web-fetch": "^4.1.3", "@rsbuild/core": "1.1.10", "@scripts/build": "workspace:*", diff --git a/packages/runtime/plugin-runtime/src/cli/code.ts b/packages/runtime/plugin-runtime/src/cli/code.ts index edf2b4ddc012..68d6b53ae3d7 100644 --- a/packages/runtime/plugin-runtime/src/cli/code.ts +++ b/packages/runtime/plugin-runtime/src/cli/code.ts @@ -1,14 +1,12 @@ import path from 'path'; import type { AppNormalizedConfig, - AppTools, - IAppContext, - NormalizedConfig, - RuntimePlugin, + AppToolsContext, + AppToolsFeatureHooks, + AppToolsNormalizedConfig, } from '@modern-js/app-tools'; -import type { MaybeAsync } from '@modern-js/plugin'; import type { Entrypoint } from '@modern-js/types'; -import { fs, MAIN_ENTRY_NAME } from '@modern-js/utils'; +import { fs } from '@modern-js/utils'; import { ENTRY_BOOTSTRAP_FILE_NAME, ENTRY_POINT_FILE_NAME, @@ -24,7 +22,7 @@ import * as serverTemplate from './template.server'; function getSSRMode( entry: string, - config: AppNormalizedConfig, + config: AppToolsNormalizedConfig, ): 'string' | 'stream' | false { const { ssr, ssrByEntries } = config.server; @@ -49,12 +47,9 @@ function getSSRMode( export const generateCode = async ( entrypoints: Entrypoint[], - appContext: IAppContext, - config: NormalizedConfig, - onCollectRuntimePlugins: (params: { - entrypoint: Entrypoint; - plugins: RuntimePlugin[]; - }) => MaybeAsync<{ entrypoint: Entrypoint; plugins: RuntimePlugin[] }>, + appContext: AppToolsContext<'shared'>, + config: AppToolsNormalizedConfig, + hooks: AppToolsFeatureHooks<'shared'>, ) => { const { mountId } = config.html; const { enableAsyncEntry } = config.source; @@ -75,10 +70,11 @@ export const generateCode = async ( customBootstrap, customServerEntry, } = entrypoint; - const { plugins: runtimePlugins } = await onCollectRuntimePlugins({ - entrypoint, - plugins: [], - }); + const { plugins: runtimePlugins } = + await hooks._internalRuntimePlugins.call({ + entrypoint, + plugins: [], + }); if (isAutoMount) { // index.jsx const indexCode = template.index({ diff --git a/packages/runtime/plugin-runtime/src/cli/index.ts b/packages/runtime/plugin-runtime/src/cli/index.ts index 59814b8bf5ec..363da67e5901 100644 --- a/packages/runtime/plugin-runtime/src/cli/index.ts +++ b/packages/runtime/plugin-runtime/src/cli/index.ts @@ -1,5 +1,5 @@ import path from 'path'; -import type { AppTools, CliPlugin } from '@modern-js/app-tools'; +import type { AppTools, CliPluginFuture } from '@modern-js/app-tools'; import { isReact18 as checkIsReact18, cleanRequireCache, @@ -17,8 +17,8 @@ import { ssrPlugin } from './ssr'; export { isRuntimeEntry } from './entry'; export { statePlugin, ssrPlugin, routerPlugin, documentPlugin }; export const runtimePlugin = (params?: { - plugins?: CliPlugin[]; -}): CliPlugin => ({ + plugins?: CliPluginFuture>[]; +}): CliPluginFuture> => ({ name: '@modern-js/runtime', post: [ '@modern-js/plugin-ssr', @@ -35,144 +35,138 @@ export const runtimePlugin = (params?: { documentPlugin(), ], setup: api => { - return { - checkEntryPoint({ path, entry }) { - return { path, entry: entry || isRuntimeEntry(path) }; - }, - modifyEntrypoints({ entrypoints }) { - const { internalDirectory } = api.useAppContext(); - const { - source: { enableAsyncEntry }, - } = api.useResolvedConfigContext(); - const newEntryPoints = entrypoints.map(entrypoint => { - if (entrypoint.isAutoMount) { - entrypoint.internalEntry = path.resolve( - internalDirectory, - `./${entrypoint.entryName}/${ - enableAsyncEntry - ? ENTRY_BOOTSTRAP_FILE_NAME - : ENTRY_POINT_FILE_NAME - }`, - ); - } - return entrypoint; - }); - return { entrypoints: newEntryPoints }; - }, - async generateEntryCode({ entrypoints }) { - const appContext = api.useAppContext(); - const resolvedConfig = api.useResolvedConfigContext(); - const runners = api.useHookRunners(); - await generateCode( - entrypoints, - appContext, - resolvedConfig, - runners._internalRuntimePlugins, - ); - }, - /* Note that the execution time of the config hook is before prepare. - /* This means that the entry information cannot be obtained in the config hook. - /* Therefore, aliases cannot be set directly in the config. - */ - prepare() { - const { builder, entrypoints, internalDirectory, metaName } = - api.useAppContext(); - builder?.addPlugins([ - builderPluginAlias({ entrypoints, internalDirectory, metaName }), - ]); - }, - config() { - const { appDirectory, metaName, internalDirectory } = - api.useAppContext(); + api.checkEntryPoint(({ path, entry }) => { + return { path, entry: entry || isRuntimeEntry(path) }; + }); - const isReact18 = checkIsReact18(appDirectory); + api.modifyEntrypoints(({ entrypoints }) => { + const { internalDirectory } = api.getAppContext(); + const { + source: { enableAsyncEntry }, + } = api.getNormalizedConfig(); + const newEntryPoints = entrypoints.map(entrypoint => { + if (entrypoint.isAutoMount) { + entrypoint.internalEntry = path.resolve( + internalDirectory, + `./${entrypoint.entryName}/${ + enableAsyncEntry + ? ENTRY_BOOTSTRAP_FILE_NAME + : ENTRY_POINT_FILE_NAME + }`, + ); + } + return entrypoint; + }); + return { entrypoints: newEntryPoints }; + }); + api.generateEntryCode(async ({ entrypoints }) => { + const appContext = api.getAppContext(); + const resolvedConfig = api.getNormalizedConfig(); + const hooks = api.getHooks(); + await generateCode(entrypoints, appContext, resolvedConfig, hooks); + }); - process.env.IS_REACT18 = isReact18.toString(); + /* Note that the execution time of the config hook is before prepare. + /* This means that the entry information cannot be obtained in the config hook. + /* Therefore, aliases cannot be set directly in the config. + */ + api.onPrepare(() => { + const { builder, entrypoints, internalDirectory, metaName } = + api.getAppContext(); + builder?.addPlugins([ + builderPluginAlias({ entrypoints, internalDirectory, metaName }), + ]); + }); - const pluginsExportsUtils = createRuntimeExportsUtils( - internalDirectory, - 'plugins', - ); + api.config(() => { + const { appDirectory, metaName, internalDirectory } = api.getAppContext(); - return { - runtime: {}, - runtimeByEntries: {}, - source: { - alias: { - /** - * twin.macro inserts styled-components into the code during the compilation process - * But it will not be installed under the user project. - * So need to add alias - */ - 'styled-components': require.resolve('styled-components'), - /** - * Compatible with the reference path of the old version of the plugin. - */ - [`@${metaName}/runtime/plugins`]: pluginsExportsUtils.getPath(), - '@meta/runtime/browser$': require.resolve( - '@modern-js/runtime/browser', - ), - '@meta/runtime/react$': require.resolve( - '@modern-js/runtime/react', - ), - '@meta/runtime/context$': require.resolve( - '@modern-js/runtime/context', - ), - '@meta/runtime$': require.resolve('@modern-js/runtime'), - }, - globalVars: { - 'process.env.IS_REACT18': process.env.IS_REACT18, - }, - }, - tools: { - styledComponents: { - // https://github.com/styled-components/babel-plugin-styled-components/issues/287 - topLevelImportPaths: ['@modern-js/runtime/styled'], - }, - bundlerChain: chain => { - chain.module - .rule('modern-entry') - .test(/\.jsx?$/) - .include.add( - path.resolve(appDirectory, 'node_modules', `.${metaName}`), - ) - .end() - .sideEffects(true); - }, + const isReact18 = checkIsReact18(appDirectory); + + process.env.IS_REACT18 = isReact18.toString(); + + const pluginsExportsUtils = createRuntimeExportsUtils( + internalDirectory, + 'plugins', + ); + + return { + runtime: {}, + runtimeByEntries: {}, + source: { + alias: { + /** + * twin.macro inserts styled-components into the code during the compilation process + * But it will not be installed under the user project. + * So need to add alias + */ + 'styled-components': require.resolve('styled-components'), /** - * Add IgnorePlugin to fix react-dom/client import error when use react17 + * Compatible with the reference path of the old version of the plugin. */ - webpackChain: (chain, { webpack }) => { - if (!isReact18) { - chain.plugin('ignore-plugin').use(webpack.IgnorePlugin, [ - { - resourceRegExp: /^react-dom\/client$/, - contextRegExp: /./, - }, - ]); - } - }, - rspack: (_config, { appendPlugins, rspack }) => { - if (!isReact18) { - appendPlugins([ - new rspack.IgnorePlugin({ - resourceRegExp: /^react-dom\/client$/, - contextRegExp: /./, - }), - ]); - } - }, + [`@${metaName}/runtime/plugins`]: pluginsExportsUtils.getPath(), + '@meta/runtime/browser$': require.resolve( + '@modern-js/runtime/browser', + ), + '@meta/runtime/react$': require.resolve('@modern-js/runtime/react'), + '@meta/runtime/context$': require.resolve( + '@modern-js/runtime/context', + ), + '@meta/runtime$': require.resolve('@modern-js/runtime'), }, - }; - }, - async beforeRestart() { - cleanRequireCache([ - require.resolve('../state/cli'), - require.resolve('../router/cli'), - require.resolve('./ssr'), - ]); - }, - }; + globalVars: { + 'process.env.IS_REACT18': process.env.IS_REACT18, + }, + }, + tools: { + styledComponents: { + // https://github.com/styled-components/babel-plugin-styled-components/issues/287 + topLevelImportPaths: ['@modern-js/runtime/styled'], + }, + bundlerChain: chain => { + chain.module + .rule('modern-entry') + .test(/\.jsx?$/) + .include.add( + path.resolve(appDirectory, 'node_modules', `.${metaName}`), + ) + .end() + .sideEffects(true); + }, + /** + * Add IgnorePlugin to fix react-dom/client import error when use react17 + */ + webpackChain: (chain, { webpack }) => { + if (!isReact18) { + chain.plugin('ignore-plugin').use(webpack.IgnorePlugin, [ + { + resourceRegExp: /^react-dom\/client$/, + contextRegExp: /./, + }, + ]); + } + }, + rspack: (_config, { appendPlugins, rspack }) => { + if (!isReact18) { + appendPlugins([ + new rspack.IgnorePlugin({ + resourceRegExp: /^react-dom\/client$/, + contextRegExp: /./, + }), + ]); + } + }, + }, + }; + }); + + api.onBeforeRestart(() => { + cleanRequireCache([ + require.resolve('../state/cli'), + require.resolve('../router/cli'), + require.resolve('./ssr'), + ]); + }); }, }); diff --git a/packages/runtime/plugin-runtime/src/cli/ssr/index.ts b/packages/runtime/plugin-runtime/src/cli/ssr/index.ts index 8cf405cf2ec1..9273f02a03cc 100644 --- a/packages/runtime/plugin-runtime/src/cli/ssr/index.ts +++ b/packages/runtime/plugin-runtime/src/cli/ssr/index.ts @@ -1,15 +1,15 @@ import path from 'path'; import type { - AppNormalizedConfig, AppTools, - CliPlugin, - PluginAPI, + AppToolsNormalizedConfig, + CliPluginFuture, ServerUserConfig, } from '@modern-js/app-tools'; +import type { CLIPluginAPI } from '@modern-js/plugin-v2'; import { LOADABLE_STATS_FILE, isUseSSRBundle } from '@modern-js/utils'; import type { RsbuildPlugin } from '@rsbuild/core'; -const hasStringSSREntry = (userConfig: AppNormalizedConfig): boolean => { +const hasStringSSREntry = (userConfig: AppToolsNormalizedConfig): boolean => { const isStreaming = (ssr: ServerUserConfig['ssr']) => ssr && typeof ssr === 'object' && ssr.mode === 'stream'; @@ -34,7 +34,7 @@ const hasStringSSREntry = (userConfig: AppNormalizedConfig): boolean => { return false; }; -const checkUseStringSSR = (config: AppNormalizedConfig): boolean => { +const checkUseStringSSR = (config: AppToolsNormalizedConfig): boolean => { const { output } = config; // ssg is not support streaming ssr. @@ -42,14 +42,16 @@ const checkUseStringSSR = (config: AppNormalizedConfig): boolean => { return Boolean(output?.ssg) || hasStringSSREntry(config); }; -const ssrBuilderPlugin = (modernAPI: PluginAPI): RsbuildPlugin => ({ +const ssrBuilderPlugin = ( + modernAPI: CLIPluginAPI>, +): RsbuildPlugin => ({ name: '@modern-js/builder-plugin-ssr', setup(api) { api.modifyEnvironmentConfig((config, { name, mergeEnvironmentConfig }) => { const isServerEnvironment = config.output.target === 'node' || name === 'workerSSR'; - const userConfig = modernAPI.useResolvedConfigContext(); + const userConfig = modernAPI.getNormalizedConfig(); const useLoadablePlugin = isUseSSRBundle(userConfig) && @@ -81,78 +83,74 @@ const ssrBuilderPlugin = (modernAPI: PluginAPI): RsbuildPlugin => ({ }, }); -export const ssrPlugin = (): CliPlugin => ({ +export const ssrPlugin = (): CliPluginFuture> => ({ name: '@modern-js/plugin-ssr', required: ['@modern-js/runtime'], setup: api => { - const appContext = api.useAppContext(); - return { - // for bundle - config() { - const { bundlerType = 'webpack' } = api.useAppContext(); - const babelHandler = (() => { - // In webpack build, we should let `useLoader` support CSR & SSR both. - if (bundlerType === 'webpack') { - return (config: any) => { - const userConfig = api.useResolvedConfigContext(); - // Add id for useLoader method, - // The useLoader can be used even if the SSR is not enabled + const appContext = api.getAppContext(); + + api.config(() => { + const { bundlerType = 'webpack' } = api.getAppContext(); + const babelHandler = (() => { + // In webpack build, we should let `useLoader` support CSR & SSR both. + if (bundlerType === 'webpack') { + return (config: any) => { + const userConfig = api.getNormalizedConfig(); + // Add id for useLoader method, + // The useLoader can be used even if the SSR is not enabled + config.plugins?.push( + path.join(__dirname, './babel-plugin-ssr-loader-id'), + ); + + if (isUseSSRBundle(userConfig) && checkUseStringSSR(userConfig)) { + config.plugins?.push(require.resolve('@loadable/babel-plugin')); + } + }; + } else if (bundlerType === 'rspack') { + // In Rspack build, we need transform the babel-loader again. + // It would increase performance overhead, + // so we only use useLoader in CSR on Rspack build temporarily. + return (config: any) => { + const userConfig = api.useResolvedConfigContext(); + if (isUseSSRBundle(userConfig) && checkUseStringSSR(userConfig)) { config.plugins?.push( path.join(__dirname, './babel-plugin-ssr-loader-id'), ); - - if (isUseSSRBundle(userConfig) && checkUseStringSSR(userConfig)) { - config.plugins?.push(require.resolve('@loadable/babel-plugin')); - } - }; - } else if (bundlerType === 'rspack') { - // In Rspack build, we need transform the babel-loader again. - // It would increase performance overhead, - // so we only use useLoader in CSR on Rspack build temporarily. - return (config: any) => { - const userConfig = api.useResolvedConfigContext(); - if (isUseSSRBundle(userConfig) && checkUseStringSSR(userConfig)) { - config.plugins?.push( - path.join(__dirname, './babel-plugin-ssr-loader-id'), - ); - config.plugins?.push(require.resolve('@loadable/babel-plugin')); - } - }; - } - })(); - - return { - builderPlugins: [ssrBuilderPlugin(api)], - source: { - alias: { - // ensure that all packages use the same storage in @modern-js/runtime-utils/node - '@modern-js/runtime-utils/node$': require.resolve( - '@modern-js/runtime-utils/node', - ), - }, + config.plugins?.push(require.resolve('@loadable/babel-plugin')); + } + }; + } + })(); + + return { + builderPlugins: [ssrBuilderPlugin(api)], + source: { + alias: { + // ensure that all packages use the same storage in @modern-js/runtime-utils/node + '@modern-js/runtime-utils/node$': require.resolve( + '@modern-js/runtime-utils/node', + ), }, - tools: { - babel: babelHandler, - bundlerChain: (chain, { isServer }) => { - if (isServer && appContext.moduleType === 'module') { - chain.output - .libraryTarget('module') - .set('chunkFormat', 'module'); - chain.output.library({ - type: 'module', - }); - chain.experiments({ - ...chain.get('experiments'), - outputModule: true, - }); - } - }, + }, + tools: { + babel: babelHandler, + bundlerChain: (chain, { isServer }) => { + if (isServer && appContext.moduleType === 'module') { + chain.output.libraryTarget('module').set('chunkFormat', 'module'); + chain.output.library({ + type: 'module', + }); + chain.experiments({ + ...chain.get('experiments'), + outputModule: true, + }); + } }, - }; - }, - }; + }, + }; + }); }, }); diff --git a/packages/runtime/plugin-runtime/src/document/cli/index.ts b/packages/runtime/plugin-runtime/src/document/cli/index.ts index 3d14c489ae88..d43f7760f83a 100644 --- a/packages/runtime/plugin-runtime/src/document/cli/index.ts +++ b/packages/runtime/plugin-runtime/src/document/cli/index.ts @@ -1,7 +1,7 @@ import path from 'path'; import type { AppTools, - CliPlugin, + CliPluginFuture, NormalizedConfig, } from '@modern-js/app-tools'; import type { Entrypoint } from '@modern-js/types/cli'; @@ -64,7 +64,7 @@ export const getDocumenByEntryName = function ( return docFile || undefined; }; -export const documentPlugin = (): CliPlugin => ({ +export const documentPlugin = (): CliPluginFuture> => ({ name: '@modern-js/plugin-document', pre: ['@modern-js/plugin-analyze'], @@ -92,7 +92,7 @@ export const documentPlugin = (): CliPlugin => ({ templateParameters: Record, ) => { const { entrypoints, internalDirectory, appDirectory } = - api.useAppContext(); + api.getAppContext(); // search the document.[tsx|jsx|js|ts] under entry const documentFilePath = getDocumenByEntryName( entrypoints, @@ -105,10 +105,10 @@ export const documentPlugin = (): CliPlugin => ({ } return async ({ htmlWebpackPlugin }: { [option: string]: any }) => { - const config = api.useResolvedConfigContext(); + const config = api.getNormalizedConfig(); const documentParams = getDocParams({ - config, + config: config as NormalizedConfig, entryName, templateParameters, }); @@ -186,7 +186,7 @@ export const documentPlugin = (): CliPlugin => ({ debug("entry %s's document jsx rendered html: %o", entryName, html); // htmlWebpackPlugin.tags - const { partialsByEntrypoint } = api.useAppContext(); + const { partialsByEntrypoint } = api.getAppContext(); const scripts = [ htmlWebpackPlugin.tags.headTags .filter((item: any) => item.tagName === 'script') @@ -300,52 +300,51 @@ export const documentPlugin = (): CliPlugin => ({ return finalHtml; }; }; - return { - config: () => { - const userConfig = api.useConfigContext(); - if (userConfig.tools?.htmlPlugin === false) { - return {}; - } + api.config(() => { + const userConfig = api.getConfig(); - return { - tools: { - htmlPlugin: (options, entry) => { - // just for reuse the baseParames calculate by builder: - // https://github.com/web-infra-dev/modern.js/blob/1abb452a87ae1adbcf8da47d62c05da39cbe4d69/packages/builder/builder-webpack-provider/src/plugins/html.ts#L69-L103 - const hackParameters: Record = - typeof options?.templateParameters === 'function' - ? options?.templateParameters( - {} as any, - {} as any, - {} as any, - {} as any, - ) - : { ...options?.templateParameters }; + if (userConfig.tools?.htmlPlugin === false) { + return {}; + } - const templateContent = documentEntry( - entry.entryName, - // options, - hackParameters, - ); + return { + tools: { + htmlPlugin: (options, entry) => { + // just for reuse the baseParames calculate by builder: + // https://github.com/web-infra-dev/modern.js/blob/1abb452a87ae1adbcf8da47d62c05da39cbe4d69/packages/builder/builder-webpack-provider/src/plugins/html.ts#L69-L103 + const hackParameters: Record = + typeof options?.templateParameters === 'function' + ? options?.templateParameters( + {} as any, + {} as any, + {} as any, + {} as any, + ) + : { ...options?.templateParameters }; - const documentHtmlOptions = templateContent - ? { - templateContent, - // Note: the behavior of inject/modify tags in afterTemplateExecution hook will not take effect - inject: false, - } - : {}; + const templateContent = documentEntry( + entry.entryName, + // options, + hackParameters, + ); - return { - ...options, - ...documentHtmlOptions, - }; - }, + const documentHtmlOptions = templateContent + ? { + templateContent, + // Note: the behavior of inject/modify tags in afterTemplateExecution hook will not take effect + inject: false, + } + : {}; + + return { + ...options, + ...documentHtmlOptions, + }; }, - }; - }, - }; + }, + }; + }); }, }); diff --git a/packages/runtime/plugin-runtime/src/router/cli/code/index.ts b/packages/runtime/plugin-runtime/src/router/cli/code/index.ts index 3bc815b96f35..df0eb9552b7b 100644 --- a/packages/runtime/plugin-runtime/src/router/cli/code/index.ts +++ b/packages/runtime/plugin-runtime/src/router/cli/code/index.ts @@ -1,6 +1,10 @@ import path from 'path'; -import type { AppNormalizedConfig, AppTools } from '@modern-js/app-tools'; -import type { IAppContext, PluginAPI } from '@modern-js/core'; +import type { + AppNormalizedConfig, + AppTools, + AppToolsContext, +} from '@modern-js/app-tools'; +import type { CLIPluginAPI } from '@modern-js/plugin-v2'; import type { Entrypoint, NestedRouteForCli, @@ -31,10 +35,10 @@ import * as templates from './templates'; import { getServerCombinedModueFile, getServerLoadersFile } from './utils'; export const generateCode = async ( - appContext: IAppContext, + appContext: AppToolsContext<'shared'>, config: AppNormalizedConfig<'shared'>, entrypoints: Entrypoint[], - api: PluginAPI>, + api: CLIPluginAPI>, ) => { const { internalDirectory, @@ -44,7 +48,7 @@ export const generateCode = async ( packageName, } = appContext; - const hookRunners = api.useHookRunners(); + const hooks = api.getHooks(); const isV5 = isRouterV5(config); const getRoutes = isV5 ? getClientRoutesLegacy : getClientRoutes; @@ -63,7 +67,7 @@ export const generateCode = async ( pageRoutesEntry, nestedRoutesEntry, } = entrypoint; - const { metaName } = api.useAppContext(); + const { metaName } = api.getAppContext(); if (isAutoMount) { // generate routes file for file system routes entrypoint. if (pageRoutesEntry || nestedRoutesEntry) { @@ -101,7 +105,7 @@ export const generateCode = async ( } } - const config = api.useResolvedConfigContext(); + const config = api.getNormalizedConfig(); const ssrByRouteIds = config.server.ssrByRouteIds || []; const clonedRoutes = cloneDeep(initialRoutes); @@ -113,7 +117,7 @@ export const generateCode = async ( ) : initialRoutes; - const { routes } = await hookRunners.modifyFileSystemRoutes({ + const { routes } = await hooks.modifyFileSystemRoutes.call({ entrypoint, routes: markedRoutes, }); @@ -143,7 +147,7 @@ export const generateCode = async ( } } - const { code } = await hookRunners.beforeGenerateRoutes({ + const { code } = await hooks.onBeforeGenerateRoutes.call({ entrypoint, code: await templates.fileSystemRoutes({ metaName, @@ -197,7 +201,7 @@ export const generateCode = async ( const serverLoaderCombined = templates.ssrLoaderCombinedModule( entrypoints, entrypoint, - config, + config as AppNormalizedConfig<'shared'>, appContext, ); if (serverLoaderCombined) { diff --git a/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts b/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts index 3fc6ff955f80..ddcbb06257e5 100644 --- a/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts +++ b/packages/runtime/plugin-runtime/src/router/cli/code/templates.ts @@ -1,5 +1,8 @@ import path from 'path'; -import type { AppNormalizedConfig, IAppContext } from '@modern-js/app-tools'; +import type { + AppNormalizedConfig, + AppToolsContext, +} from '@modern-js/app-tools'; import type { Entrypoint, NestedRouteForCli, @@ -452,7 +455,7 @@ export function ssrLoaderCombinedModule( entrypoints: Entrypoint[], entrypoint: Entrypoint, config: AppNormalizedConfig<'shared'>, - appContext: IAppContext, + appContext: AppToolsContext<'shared'>, ) { const { entryName, isMainEntry } = entrypoint; const { packageName, internalDirectory } = appContext; diff --git a/packages/runtime/plugin-runtime/src/router/cli/handler.ts b/packages/runtime/plugin-runtime/src/router/cli/handler.ts index 2c4d2b2fe58f..87c268df6b93 100644 --- a/packages/runtime/plugin-runtime/src/router/cli/handler.ts +++ b/packages/runtime/plugin-runtime/src/router/cli/handler.ts @@ -1,6 +1,6 @@ import path from 'path'; -import type { AppTools } from '@modern-js/app-tools'; -import type { PluginAPI } from '@modern-js/core'; +import type { AppNormalizedConfig, AppTools } from '@modern-js/app-tools'; +import type { CLIPluginAPI } from '@modern-js/plugin-v2'; import type { Entrypoint } from '@modern-js/types'; import { cloneDeep } from '@modern-js/utils/lodash'; import * as templates from './code/templates'; @@ -10,23 +10,28 @@ import { modifyEntrypoints } from './entry'; let originEntrypoints: any[] = []; export async function handleModifyEntrypoints( - api: PluginAPI>, + api: CLIPluginAPI>, entrypoints: Entrypoint[], ) { - const config = api.useResolvedConfigContext(); + const config = api.getNormalizedConfig(); return modifyEntrypoints(entrypoints, config); } export async function handleGeneratorEntryCode( - api: PluginAPI>, + api: CLIPluginAPI>, entrypoints: Entrypoint[], ) { - const appContext = api.useAppContext(); - const { internalDirectory } = api.useAppContext(); - const resolvedConfig = api.useResolvedConfigContext(); + const appContext = api.getAppContext(); + const { internalDirectory } = appContext; + const resolvedConfig = api.getNormalizedConfig(); const { generatorRegisterCode, generateCode } = await import('./code'); originEntrypoints = cloneDeep(entrypoints); - await generateCode(appContext, resolvedConfig, entrypoints, api); + await generateCode( + appContext, + resolvedConfig as AppNormalizedConfig<'shared'>, + entrypoints, + api, + ); await Promise.all( entrypoints.map(async entrypoint => { if (entrypoint.nestedRoutesEntry || entrypoint.pageRoutesEntry) { @@ -48,10 +53,10 @@ export async function handleGeneratorEntryCode( } export async function handleFileChange( - api: PluginAPI>, + api: CLIPluginAPI>, e: any, ) { - const appContext = api.useAppContext(); + const appContext = api.getAppContext(); const { appDirectory, entrypoints } = appContext; const { filename, eventType } = e; const nestedRouteEntries = entrypoints @@ -70,9 +75,14 @@ export async function handleFileChange( isPageFile(absoluteFilePath) && isPageComponentFile(absoluteFilePath); if (isRouteComponent && (eventType === 'add' || eventType === 'unlink')) { - const resolvedConfig = api.useResolvedConfigContext(); + const resolvedConfig = api.getNormalizedConfig(); const { generateCode } = await import('./code'); const entrypoints = cloneDeep(originEntrypoints); - await generateCode(appContext, resolvedConfig, entrypoints, api); + await generateCode( + appContext, + resolvedConfig as AppNormalizedConfig<'shared'>, + entrypoints, + api, + ); } } diff --git a/packages/runtime/plugin-runtime/src/router/cli/index.ts b/packages/runtime/plugin-runtime/src/router/cli/index.ts index ddb569bec088..95d1ed5120f7 100644 --- a/packages/runtime/plugin-runtime/src/router/cli/index.ts +++ b/packages/runtime/plugin-runtime/src/router/cli/index.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import type { AppTools, CliPlugin } from '@modern-js/app-tools'; +import type { AppTools, CliPluginFuture } from '@modern-js/app-tools'; import type { NestedRouteForCli, PageRoute, @@ -23,110 +23,109 @@ import { export { isRouteEntry } from './entry'; export { handleFileChange, handleModifyEntrypoints } from './handler'; -export const routerPlugin = (): CliPlugin> => ({ +export const routerPlugin = (): CliPluginFuture> => ({ name: '@modern-js/plugin-router', required: ['@modern-js/runtime'], setup: api => { const nestedRoutes: Record = {}; const nestedRoutesForServer: Record = {}; - return { - _internalRuntimePlugins({ entrypoint, plugins }) { - const { packageName, serverRoutes, metaName } = api.useAppContext(); - const serverBase = serverRoutes - .filter( - (route: ServerRoute) => route.entryName === entrypoint.entryName, - ) - .map(route => route.urlPath) - .sort((a, b) => (a.length - b.length > 0 ? -1 : 1)); - const userConfig = api.useResolvedConfigContext(); - const routerConfig = getEntryOptions( - entrypoint.entryName, - entrypoint.isMainEntry, - userConfig.runtime, - userConfig.runtimeByEntries, - packageName, - )?.router; - if (routerConfig && !isV5(userConfig)) { - plugins.push({ - name: 'router', - path: `@${metaName}/runtime/router`, - config: - typeof routerConfig === 'boolean' - ? { serverBase } - : { ...routerConfig, serverBase }, - }); - } - return { entrypoint, plugins }; - }, - checkEntryPoint({ path, entry }) { - return { path, entry: entry || isRouteEntry(path) }; - }, - config() { - return { - source: { - include: [ - // react-router v6 is no longer support ie 11 - // so we need to compile these packages to ensure the compatibility - // https://github.com/remix-run/react-router/commit/f6df0697e1b2064a2b3a12e8b39577326fdd945b - /node_modules\/react-router/, - /node_modules\/react-router-dom/, - /node_modules\/@remix-run\/router/, - ], - }, - }; - }, - async modifyEntrypoints({ entrypoints }) { - const newEntryPoints = await handleModifyEntrypoints(api, entrypoints); - return { entrypoints: newEntryPoints }; - }, - async generateEntryCode({ entrypoints }) { - await handleGeneratorEntryCode(api, entrypoints); - }, - addRuntimeExports() { - const userConfig = api.useResolvedConfigContext(); - const { internalDirectory, metaName } = api.useAppContext(); + api._internalRuntimePlugins(({ entrypoint, plugins }) => { + const { packageName, serverRoutes, metaName } = api.getAppContext(); + const serverBase = serverRoutes + .filter( + (route: ServerRoute) => route.entryName === entrypoint.entryName, + ) + .map(route => route.urlPath) + .sort((a, b) => (a.length - b.length > 0 ? -1 : 1)); + const userConfig = api.getNormalizedConfig(); + const routerConfig = getEntryOptions( + entrypoint.entryName, + entrypoint.isMainEntry, + userConfig.runtime, + userConfig.runtimeByEntries, + packageName, + )?.router; + if (routerConfig && !isV5(userConfig)) { + plugins.push({ + name: 'router', + path: `@${metaName}/runtime/router`, + config: + typeof routerConfig === 'boolean' + ? { serverBase } + : { ...routerConfig, serverBase }, + }); + } - const pluginsExportsUtils = createRuntimeExportsUtils( - internalDirectory, - 'plugins', - ); - if (!isV5(userConfig)) { - pluginsExportsUtils.addExport( - `export { default as router } from '@${metaName}/runtime/router'`, - ); - } - }, - async fileChange(e) { - await handleFileChange(api, e); - }, + return { entrypoint, plugins }; + }); + api.checkEntryPoint(({ path, entry }) => { + return { path, entry: entry || isRouteEntry(path) }; + }); + api.config(() => { + return { + source: { + include: [ + // react-router v6 is no longer support ie 11 + // so we need to compile these packages to ensure the compatibility + // https://github.com/remix-run/react-router/commit/f6df0697e1b2064a2b3a12e8b39577326fdd945b + /node_modules\/react-router/, + /node_modules\/react-router-dom/, + /node_modules\/@remix-run\/router/, + ], + }, + }; + }); + api.modifyEntrypoints(async ({ entrypoints }) => { + const newEntryPoints = await handleModifyEntrypoints(api, entrypoints); + return { entrypoints: newEntryPoints }; + }); + api.generateEntryCode(async ({ entrypoints }) => { + await handleGeneratorEntryCode(api, entrypoints); + }); + api.addRuntimeExports(() => { + const userConfig = api.useResolvedConfigContext(); + const { internalDirectory, metaName } = api.useAppContext(); - async modifyFileSystemRoutes({ entrypoint, routes }) { - nestedRoutes[entrypoint.entryName] = routes; - nestedRoutesForServer[entrypoint.entryName] = filterRoutesForServer( - routes as (NestedRouteForCli | PageRoute)[], + const pluginsExportsUtils = createRuntimeExportsUtils( + internalDirectory, + 'plugins', + ); + if (!isV5(userConfig)) { + pluginsExportsUtils.addExport( + `export { default as router } from '@${metaName}/runtime/router'`, ); + } + }); + api.onFileChanged(async e => { + await handleFileChange(api, e); + }); - return { - entrypoint, - routes, - }; - }, + api.modifyFileSystemRoutes(({ entrypoint, routes }) => { + nestedRoutes[entrypoint.entryName] = routes; + nestedRoutesForServer[entrypoint.entryName] = filterRoutesForServer( + routes as (NestedRouteForCli | PageRoute)[], + ); - async beforeGenerateRoutes({ entrypoint, code }) { - const { distDirectory } = api.useAppContext(); + return { + entrypoint, + routes, + }; + }); - await fs.outputJSON( - path.resolve(distDirectory, NESTED_ROUTE_SPEC_FILE), - nestedRoutesForServer, - ); + api.onBeforeGenerateRoutes(async ({ entrypoint, code }) => { + const { distDirectory } = api.getAppContext(); + + await fs.outputJSON( + path.resolve(distDirectory, NESTED_ROUTE_SPEC_FILE), + nestedRoutesForServer, + ); - return { - entrypoint, - code, - }; - }, - }; + return { + entrypoint, + code, + }; + }); }, }); diff --git a/packages/runtime/plugin-runtime/src/state/cli/index.ts b/packages/runtime/plugin-runtime/src/state/cli/index.ts index 22ec50563763..fd972b1d648c 100644 --- a/packages/runtime/plugin-runtime/src/state/cli/index.ts +++ b/packages/runtime/plugin-runtime/src/state/cli/index.ts @@ -1,48 +1,46 @@ -import type { AppTools, CliPlugin } from '@modern-js/app-tools'; +import type { AppTools, CliPluginFuture } from '@modern-js/app-tools'; import { createRuntimeExportsUtils, getEntryOptions } from '@modern-js/utils'; const PLUGIN_IDENTIFIER = 'state'; -export const statePlugin = (): CliPlugin => ({ +export const statePlugin = (): CliPluginFuture> => ({ name: '@modern-js/plugin-state', required: ['@modern-js/runtime'], setup: api => { - return { - _internalRuntimePlugins({ entrypoint, plugins }) { - const { entryName, isMainEntry } = entrypoint; - const userConfig = api.useResolvedConfigContext(); - const { packageName, metaName } = api.useAppContext(); + api._internalRuntimePlugins(({ entrypoint, plugins }) => { + const { entryName, isMainEntry } = entrypoint; + const userConfig = api.useResolvedConfigContext(); + const { packageName, metaName } = api.useAppContext(); - const stateConfig = getEntryOptions( - entryName, - isMainEntry, - userConfig.runtime, - userConfig.runtimeByEntries, - packageName, - )?.state; - if (stateConfig) { - plugins.push({ - name: PLUGIN_IDENTIFIER, - path: `@${metaName}/runtime/model`, - config: typeof stateConfig === 'boolean' ? {} : stateConfig, - }); - } - return { entrypoint, plugins }; - }, - addRuntimeExports() { - const { internalDirectory, metaName } = api.useAppContext(); + const stateConfig = getEntryOptions( + entryName, + isMainEntry, + userConfig.runtime, + userConfig.runtimeByEntries, + packageName, + )?.state; + if (stateConfig) { + plugins.push({ + name: PLUGIN_IDENTIFIER, + path: `@${metaName}/runtime/model`, + config: typeof stateConfig === 'boolean' ? {} : stateConfig, + }); + } + return { entrypoint, plugins }; + }); + api.addRuntimeExports(() => { + const { internalDirectory, metaName } = api.useAppContext(); - const pluginsExportsUtils = createRuntimeExportsUtils( - internalDirectory, - 'plugins', - ); - pluginsExportsUtils.addExport( - `export { default as state } from '@${metaName}/runtime/model'`, - ); - }, - }; + const pluginsExportsUtils = createRuntimeExportsUtils( + internalDirectory, + 'plugins', + ); + pluginsExportsUtils.addExport( + `export { default as state } from '@${metaName}/runtime/model'`, + ); + }); }, }); diff --git a/packages/runtime/plugin-runtime/tests/document/cli.test.tsx b/packages/runtime/plugin-runtime/tests/document/cli.test.tsx index eced202f0fc9..fa678dc35d22 100644 --- a/packages/runtime/plugin-runtime/tests/document/cli.test.tsx +++ b/packages/runtime/plugin-runtime/tests/document/cli.test.tsx @@ -1,46 +1,66 @@ import { existsSync } from 'fs'; import path from 'path'; -import { type CliPlugin, type IAppContext, manager } from '@modern-js/core'; +import { + type CLIPluginAPI, + type Plugin, + createPluginManager, +} from '@modern-js/plugin-v2'; +import { createContext, initPluginAPI } from '@modern-js/plugin-v2/cli'; +import type { AppTools, AppToolsContext } from '@modern-js/app-tools'; import { getBundleEntry } from '../../../../solutions/app-tools/src/plugins/analyze/getBundleEntry'; import { documentPlugin, getDocumenByEntryName } from '../../src/document/cli'; describe('plugin runtime cli', () => { - const main = manager.clone().usePlugin(documentPlugin as CliPlugin); - let runner: any; - + let pluginAPI: CLIPluginAPI; + const setup = async ({ appDirectory }: { appDirectory: string }) => { + const pluginManager = createPluginManager(); + pluginManager.addPlugins([documentPlugin() as Plugin]); + const plugins = pluginManager.getPlugins(); + const context = await createContext({ + appContext: { + appDirectory, + plugins, + } as any, + config: {}, + normalizedConfig: { plugins: [] } as any, + }); + pluginAPI = initPluginAPI({ + context, + pluginManager, + }); + context.pluginAPI = pluginAPI; + for (const plugin of plugins) { + await plugin.setup(pluginAPI); + } + }; beforeAll(async () => { - runner = await main.init(); + await setup({ appDirectory: path.join(__dirname, './feature') }); }); - it('plugin is defined', () => { expect(documentPlugin).toBeDefined(); }); it('plugin-document cli config is defined', async () => { - const config = await runner.config(); + const hooks = pluginAPI.getHooks(); + const config = await hooks.config.call(); expect(config.find((item: any) => item.tools)).toBeTruthy(); expect(config.find((item: any) => item.tools.htmlPlugin)).toBeTruthy(); }); it('plugin-document htmlPlugin can return the right', async () => { - const mockAPI = { - useAppContext: jest.fn((): any => ({ - internalDirectory: path.join(__dirname, './feature'), - appDirectory: path.join(__dirname, './feature'), - entrypoints: [ - { - entryName: 'main', - absoluteEntryDir: path.join(__dirname, './feature'), - }, - ], - })), - }; - const cloned = manager.clone(mockAPI); - cloned.usePlugin(documentPlugin as CliPlugin); - const runner2 = await cloned.init(); - const config = await runner2.config(); - + pluginAPI.updateAppContext({ + internalDirectory: path.join(__dirname, './feature'), + appDirectory: path.join(__dirname, './feature'), + entrypoints: [ + { + entryName: 'main', + absoluteEntryDir: path.join(__dirname, './feature'), + }, + ], + }); + const hooks = pluginAPI.getHooks(); + const config = await hooks.config.call(); const { htmlPlugin } = ( config.find((item: any) => item.tools.htmlPlugin)! as any ).tools; @@ -77,12 +97,13 @@ describe('plugin runtime cli', () => { ).toBeTruthy(); }); it('when user config set empty entries and disableDefaultEntries true, should get the ', async () => { + const hooks: any = pluginAPI.getHooks(); const entries = await getBundleEntry( - runner, + hooks, { internalDirectory: path.join(__dirname, './feature'), appDirectory: path.join(__dirname, './feature'), - } as IAppContext, + } as AppToolsContext<'shared'>, { source: { disableDefaultEntries: true, diff --git a/packages/solutions/app-tools/src/compat/index.ts b/packages/solutions/app-tools/src/compat/index.ts index 857d58553e08..9fa13febd501 100644 --- a/packages/solutions/app-tools/src/compat/index.ts +++ b/packages/solutions/app-tools/src/compat/index.ts @@ -1,12 +1,7 @@ -import { createAsyncHook, createCollectAsyncHook } from '@modern-js/plugin-v2'; -import type { Entrypoint } from '@modern-js/types'; +import { createAsyncHook } from '@modern-js/plugin-v2'; import type { AppTools, CliPluginFuture } from '../types'; import { getHookRunners } from './hooks'; -type AppendEntryCodeFn = (params: { - entrypoint: Entrypoint; - code: string; -}) => string | Promise; type JestConfigFn = ( utils: any, next: (utils: any) => any, @@ -39,7 +34,6 @@ export const compatPlugin = (): CliPluginFuture> => ({ }; }, registryHooks: { - appendEntryCode: createCollectAsyncHook(), jestConfig: createAsyncHook(), afterTest: createAsyncHook(), }, diff --git a/packages/solutions/app-tools/src/types/new.ts b/packages/solutions/app-tools/src/types/new.ts index 27f7ddea21ee..42b2123710d8 100644 --- a/packages/solutions/app-tools/src/types/new.ts +++ b/packages/solutions/app-tools/src/types/new.ts @@ -154,6 +154,7 @@ export type AppToolsExtendContext = { apiDirectory: string; lambdaDirectory: string; serverConfigFile: string; + runtimeConfigFile: string; serverPlugins: ServerPlugin[]; moduleType: 'module' | 'commonjs'; /** Information for entry points */ diff --git a/packages/solutions/app-tools/src/utils/initAppContext.ts b/packages/solutions/app-tools/src/utils/initAppContext.ts index bd3e1ec8cdbc..f7251ce4411c 100644 --- a/packages/solutions/app-tools/src/utils/initAppContext.ts +++ b/packages/solutions/app-tools/src/utils/initAppContext.ts @@ -9,7 +9,7 @@ export const initAppContext = ({ tempDir, }: { appDirectory: string; - runtimeConfigFile: string | false; + runtimeConfigFile: string; options?: { metaName?: string; srcDir?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 810482d60cd9..4a37d51c4315 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2213,9 +2213,9 @@ importers: packages/runtime/plugin-garfish: dependencies: - '@modern-js/plugin': + '@modern-js/plugin-v2': specifier: workspace:* - version: link:../../toolkit/plugin + version: link:../../toolkit/plugin-v2 '@modern-js/runtime-utils': specifier: workspace:* version: link:../../toolkit/runtime-utils @@ -2310,6 +2310,9 @@ importers: '@modern-js/plugin': specifier: workspace:* version: link:../../toolkit/plugin + '@modern-js/plugin-v2': + specifier: workspace:* + version: link:../../toolkit/plugin-v2 '@modern-js/runtime-utils': specifier: workspace:* version: link:../../toolkit/runtime-utils @@ -2419,6 +2422,9 @@ importers: '@modern-js/plugin-data-loader': specifier: workspace:* version: link:../../cli/plugin-data-loader + '@modern-js/plugin-v2': + specifier: workspace:* + version: link:../../toolkit/plugin-v2 '@modern-js/runtime-utils': specifier: workspace:* version: link:../../toolkit/runtime-utils @@ -2471,9 +2477,6 @@ importers: '@modern-js/app-tools': specifier: workspace:* version: link:../../solutions/app-tools - '@modern-js/core': - specifier: workspace:* - version: link:../../cli/core '@remix-run/web-fetch': specifier: ^4.1.3 version: 4.4.2