diff --git a/.changeset/healthy-rocks-fetch.md b/.changeset/healthy-rocks-fetch.md new file mode 100644 index 000000000..8ce206b1b --- /dev/null +++ b/.changeset/healthy-rocks-fetch.md @@ -0,0 +1,5 @@ +--- +'@ice/plugin-icestark': minor +--- + +feat: support framework provider diff --git a/.changeset/long-maps-search.md b/.changeset/long-maps-search.md new file mode 100644 index 000000000..ef6984d9e --- /dev/null +++ b/.changeset/long-maps-search.md @@ -0,0 +1,5 @@ +--- +'@ice/bundles': patch +--- + +feat: export ModuleNotFoundError of webpack diff --git a/.changeset/slow-jokes-speak.md b/.changeset/slow-jokes-speak.md new file mode 100644 index 000000000..c7b9aa33e --- /dev/null +++ b/.changeset/slow-jokes-speak.md @@ -0,0 +1,5 @@ +--- +'@ice/plugin-icestark': minor +--- + +feat: support custom AppRoute diff --git a/packages/bundles/webpack/bundle.js b/packages/bundles/webpack/bundle.js index 27e0b3b14..919ca1e80 100644 --- a/packages/bundles/webpack/bundle.js +++ b/packages/bundles/webpack/bundle.js @@ -18,6 +18,24 @@ module.exports = { StringXor: require('webpack/lib/util/StringXor'), NormalModule: require('webpack/lib/NormalModule'), EntryDependency: require('webpack/lib/dependencies/EntryDependency'), + ModuleNotFoundError: require('webpack/lib/ModuleNotFoundError'), + LazySet: require('webpack/lib/util/LazySet'), + makeSerializable: require('webpack/lib/util/makeSerializable'), + SortableSet: require('webpack/lib/util/SortableSet'), + StaticExportsDependency: require('webpack/lib/dependencies/StaticExportsDependency'), + ModuleFactory: require('webpack/lib/ModuleFactory'), + ModuleDependency: require('webpack/lib/dependencies/ModuleDependency'), + createSchemaValidation: require('webpack/lib/util/create-schema-validation'), + extractUrlAndGlobal: require('webpack/lib/util/extractUrlAndGlobal'), + Compilation: require('webpack/lib/Compilation'), + semver: require('webpack/lib/util/semver'), + WebpackError: require('webpack/lib/WebpackError'), + comparators: require('webpack/lib/util/comparators'), + StartupEntrypointRuntimeModule: require('webpack/lib/runtime/StartupEntrypointRuntimeModule'), + SetHelpers: require('webpack/lib/util/SetHelpers'), + ChunkHelpers: require('webpack/lib/javascript/ChunkHelpers'), + HotUpdateChunk: require('webpack/lib/HotUpdateChunk'), + fs: require('webpack/lib/util/fs'), sources: require('webpack').sources, webpack: require('webpack'), package: { diff --git a/packages/bundles/webpack/packages/ChunkHelpers.js b/packages/bundles/webpack/packages/ChunkHelpers.js new file mode 100644 index 000000000..808451418 --- /dev/null +++ b/packages/bundles/webpack/packages/ChunkHelpers.js @@ -0,0 +1 @@ +module.exports = require('./bundle').ChunkHelpers; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/Compilation.js b/packages/bundles/webpack/packages/Compilation.js new file mode 100644 index 000000000..86ec44745 --- /dev/null +++ b/packages/bundles/webpack/packages/Compilation.js @@ -0,0 +1 @@ +module.exports = require('./bundle').Compilation; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/HotUpdateChunk.js b/packages/bundles/webpack/packages/HotUpdateChunk.js new file mode 100644 index 000000000..57353f3c9 --- /dev/null +++ b/packages/bundles/webpack/packages/HotUpdateChunk.js @@ -0,0 +1 @@ +module.exports = require('./bundle').HotUpdateChunk; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/LazySet.js b/packages/bundles/webpack/packages/LazySet.js new file mode 100644 index 000000000..345f52c23 --- /dev/null +++ b/packages/bundles/webpack/packages/LazySet.js @@ -0,0 +1 @@ +module.exports = require('./bundle').LazySet; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/ModuleDependency.js b/packages/bundles/webpack/packages/ModuleDependency.js new file mode 100644 index 000000000..fafe24be9 --- /dev/null +++ b/packages/bundles/webpack/packages/ModuleDependency.js @@ -0,0 +1 @@ +module.exports = require('./bundle').ModuleDependency; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/ModuleFactory.js b/packages/bundles/webpack/packages/ModuleFactory.js new file mode 100644 index 000000000..2fc048401 --- /dev/null +++ b/packages/bundles/webpack/packages/ModuleFactory.js @@ -0,0 +1 @@ +module.exports = require('./bundle').ModuleFactory; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/ModuleNotFoundError.js b/packages/bundles/webpack/packages/ModuleNotFoundError.js new file mode 100644 index 000000000..ab4d2d761 --- /dev/null +++ b/packages/bundles/webpack/packages/ModuleNotFoundError.js @@ -0,0 +1 @@ +module.exports = require('./bundle').ModuleNotFoundError; diff --git a/packages/bundles/webpack/packages/SetHelpers.js b/packages/bundles/webpack/packages/SetHelpers.js new file mode 100644 index 000000000..59e3639ae --- /dev/null +++ b/packages/bundles/webpack/packages/SetHelpers.js @@ -0,0 +1 @@ +module.exports = require('./bundle').SetHelpers; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/SortableSet.js b/packages/bundles/webpack/packages/SortableSet.js new file mode 100644 index 000000000..e6f2688d6 --- /dev/null +++ b/packages/bundles/webpack/packages/SortableSet.js @@ -0,0 +1 @@ +module.exports = require('./bundle').SortableSet; diff --git a/packages/bundles/webpack/packages/StartupEntrypointRuntimeModule.js b/packages/bundles/webpack/packages/StartupEntrypointRuntimeModule.js new file mode 100644 index 000000000..249a832f2 --- /dev/null +++ b/packages/bundles/webpack/packages/StartupEntrypointRuntimeModule.js @@ -0,0 +1 @@ +module.exports = require('./bundle').StartupEntrypointRuntimeModule; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/StaticExportsDependency.js b/packages/bundles/webpack/packages/StaticExportsDependency.js new file mode 100644 index 000000000..f27782b17 --- /dev/null +++ b/packages/bundles/webpack/packages/StaticExportsDependency.js @@ -0,0 +1 @@ +module.exports = require('./bundle').StaticExportsDependency; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/WebpackError.js b/packages/bundles/webpack/packages/WebpackError.js new file mode 100644 index 000000000..80f2d5b42 --- /dev/null +++ b/packages/bundles/webpack/packages/WebpackError.js @@ -0,0 +1 @@ +module.exports = require('./bundle').WebpackError; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/comparators.js b/packages/bundles/webpack/packages/comparators.js new file mode 100644 index 000000000..6df85b871 --- /dev/null +++ b/packages/bundles/webpack/packages/comparators.js @@ -0,0 +1 @@ +module.exports = require('./bundle').comparators; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/create-schema-validation.js b/packages/bundles/webpack/packages/create-schema-validation.js new file mode 100644 index 000000000..19f4f1005 --- /dev/null +++ b/packages/bundles/webpack/packages/create-schema-validation.js @@ -0,0 +1 @@ +module.exports = require('./bundle').createSchemaValidation; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/extractUrlAndGlobal.js b/packages/bundles/webpack/packages/extractUrlAndGlobal.js new file mode 100644 index 000000000..1ead8e30b --- /dev/null +++ b/packages/bundles/webpack/packages/extractUrlAndGlobal.js @@ -0,0 +1 @@ +module.exports = require('./bundle').extractUrlAndGlobal; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/fs.js b/packages/bundles/webpack/packages/fs.js new file mode 100644 index 000000000..ec52150b4 --- /dev/null +++ b/packages/bundles/webpack/packages/fs.js @@ -0,0 +1 @@ +module.exports = require('./bundle').fs; diff --git a/packages/bundles/webpack/packages/makeSerializable.js b/packages/bundles/webpack/packages/makeSerializable.js new file mode 100644 index 000000000..d2ab18fe2 --- /dev/null +++ b/packages/bundles/webpack/packages/makeSerializable.js @@ -0,0 +1 @@ +module.exports = require('./bundle').makeSerializable; \ No newline at end of file diff --git a/packages/bundles/webpack/packages/semver.js b/packages/bundles/webpack/packages/semver.js new file mode 100644 index 000000000..d44274d97 --- /dev/null +++ b/packages/bundles/webpack/packages/semver.js @@ -0,0 +1 @@ +module.exports = require('./bundle').semver; \ No newline at end of file diff --git a/packages/ice/src/requireHook.ts b/packages/ice/src/requireHook.ts index cc4a891c5..d5e7be557 100644 --- a/packages/ice/src/requireHook.ts +++ b/packages/ice/src/requireHook.ts @@ -12,41 +12,56 @@ export function getFileName(filePath: string) { return filePath.split('/').slice(-1)[0]; } +const webpackPlugins = [ + // plugins require the same webpack instance + 'webpack/lib/LibraryTemplatePlugin', + 'webpack/lib/node/NodeTargetPlugin', + 'webpack/lib/node/NodeTemplatePlugin', + 'webpack/lib/NormalModule', + 'webpack/lib/optimize/LimitChunkCountPlugin', + 'webpack/lib/SingleEntryPlugin', + 'webpack/lib/webworker/WebWorkerTemplatePlugin', + 'webpack/lib/node/NodeEnvironmentPlugin', + 'webpack/lib/ModuleFilenameHelpers', + 'webpack/lib/GraphHelpers', + 'webpack/lib/ExternalsPlugin', + 'webpack/lib/web/FetchCompileAsyncWasmPlugin', + 'webpack/lib/web/FetchCompileWasmPlugin', + 'webpack/lib/runtime/StartupChunkDependenciesPlugin', + 'webpack/lib/javascript/JavascriptModulesPlugin', + 'webpack/lib/javascript/StartupHelpers', + 'webpack/lib/util/identifier', + 'webpack/lib/util/compileBooleanMatcher', + 'webpack/lib/ModuleNotFoundError', + 'webpack/lib/util/LazySet', + 'webpack/lib/util/fs', + 'webpack/lib/util/makeSerializable', + 'webpack/lib/util/SortableSet', + 'webpack/lib/dependencies/StaticExportsDependency', + 'webpack/lib/dependencies/EntryDependency', + 'webpack/lib/ModuleFactory', + 'webpack/lib/dependencies/ModuleDependency', + 'webpack/lib/util/create-schema-validation', + 'webpack/lib/util/extractUrlAndGlobal', + 'webpack/lib/Compilation', + 'webpack/lib/util/semver', + 'webpack/lib/WebpackError', + 'webpack/lib/util/comparators', + 'webpack/lib/runtime/StartupEntrypointRuntimeModule', + 'webpack/lib/util/SetHelpers', + 'webpack/lib/javascript/ChunkHelpers', + 'webpack/lib/HotUpdateChunk', +]; + export function getHookFiles() { - const webpackPlugins = [ - // plugins require the same webpack instance - 'webpack/lib/LibraryTemplatePlugin', - 'webpack/lib/node/NodeTargetPlugin', - 'webpack/lib/node/NodeTemplatePlugin', - 'webpack/lib/NormalModule', - 'webpack/lib/optimize/LimitChunkCountPlugin', - 'webpack/lib/SingleEntryPlugin', - 'webpack/lib/webworker/WebWorkerTemplatePlugin', - 'webpack/lib/node/NodeEnvironmentPlugin', - 'webpack/lib/ModuleFilenameHelpers', - 'webpack/lib/GraphHelpers', - 'webpack/lib/ExternalsPlugin', - 'webpack/lib/web/FetchCompileAsyncWasmPlugin', - 'webpack/lib/web/FetchCompileWasmPlugin', - 'webpack/lib/runtime/StartupChunkDependenciesPlugin', - 'webpack/lib/javascript/JavascriptModulesPlugin', - 'webpack/lib/javascript/StartupHelpers', - 'webpack/lib/util/identifier', - 'webpack/lib/util/compileBooleanMatcher', - ]; const webpackDir = path.join(require.resolve('@ice/bundles/compiled/webpack'), '../'); - const pluginMap = webpackPlugins.map((pluginPath) => { - return [ - pluginPath, - pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util)\/)?/, webpackDir), - ]; - }); - const pluginMapWithJs = webpackPlugins.map((pluginPath) => { - return [ - `${pluginPath}.js`, - pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util)\/)?/, webpackDir), - ]; - }); + const createPluginMapping = (pluginPath: string, withJsExtension = false) => [ + withJsExtension ? `${pluginPath}.js` : pluginPath, + pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util|dependencies)\/)?/, webpackDir), + ]; + + const pluginMap = webpackPlugins.map(pluginPath => createPluginMapping(pluginPath)); + const pluginMapWithJs = webpackPlugins.map(pluginPath => createPluginMapping(pluginPath, true)); return [ ['webpack', `${webpackDir}webpack-lib`], @@ -69,7 +84,9 @@ function hijackWebpack() { const resolveFilename = mod._resolveFilename; mod._resolveFilename = function (request: string, parent: any, isMain: boolean, options: any) { const hookResolved = hookPropertyMap.get(request); - if (hookResolved) request = hookResolved; + if (hookResolved) { + request = hookResolved; + } return resolveFilename.call(mod, request, parent, isMain, options); }; } diff --git a/packages/plugin-icestark/Context.d.ts b/packages/plugin-icestark/Context.d.ts new file mode 100644 index 000000000..6a2482c98 --- /dev/null +++ b/packages/plugin-icestark/Context.d.ts @@ -0,0 +1 @@ +export * from './esm/runtime/Context'; \ No newline at end of file diff --git a/packages/plugin-icestark/package.json b/packages/plugin-icestark/package.json index 623c34dd1..384e0c5a0 100644 --- a/packages/plugin-icestark/package.json +++ b/packages/plugin-icestark/package.json @@ -18,6 +18,11 @@ "import": "./esm/index.js", "default": "./esm/index.js" }, + "./Context": { + "types": "./esm/runtime/Context.d.ts", + "import": "./esm/runtime/Context.js", + "default": "./esm/runtime/Context.js" + }, "./esm/runtime/child": { "types": "./esm/runtime/child.d.ts", "import": "./esm/runtime/child.js", diff --git a/packages/plugin-icestark/src/index.ts b/packages/plugin-icestark/src/index.ts index 048091f5d..10f5d1343 100644 --- a/packages/plugin-icestark/src/index.ts +++ b/packages/plugin-icestark/src/index.ts @@ -9,12 +9,12 @@ const PLUGIN_NAME = '@ice/plugin-icestark'; const plugin: Plugin = ({ type, library }) => ({ name: PLUGIN_NAME, setup: ({ onGetConfig, context, generator, modifyUserConfig }) => { + const libraryName = library || context.pkg?.name as string || 'microApp'; onGetConfig((config) => { config.configureWebpack ??= []; config.configureWebpack.push((webpackConfig) => { if (type === 'child') { - const { pkg } = context; - webpackConfig.output.library = library || pkg.name as string || 'microApp'; + webpackConfig.output.library = libraryName; webpackConfig.output.libraryTarget = 'umd'; } return webpackConfig; @@ -34,6 +34,10 @@ const plugin: Plugin = ({ type, library }) => ({ if (!window.ICESTARK?.root && !window.__POWERED_BY_QIANKUN__) { root = render(); } +// Set library name +if (typeof window !== 'undefined' && window.ICESTARK) { + window.ICESTARK.library = ${JSON.stringify(libraryName)}; +} // For qiankun lifecycle validation. export async function bootstrap(props) { diff --git a/packages/plugin-icestark/src/runtime/Context.tsx b/packages/plugin-icestark/src/runtime/Context.tsx new file mode 100644 index 000000000..7545be0df --- /dev/null +++ b/packages/plugin-icestark/src/runtime/Context.tsx @@ -0,0 +1,7 @@ +import { createContext, useContext } from 'react'; + +export const FrameworkContext = createContext({}); + +export const useFrameworkContext = (): T => { + return useContext(FrameworkContext) as T; +}; diff --git a/packages/plugin-icestark/src/runtime/child.tsx b/packages/plugin-icestark/src/runtime/child.tsx index 11dc16936..f1062b3d5 100644 --- a/packages/plugin-icestark/src/runtime/child.tsx +++ b/packages/plugin-icestark/src/runtime/child.tsx @@ -1,13 +1,19 @@ +import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import type { RuntimePlugin } from '@ice/runtime/types'; import type { LifecycleOptions } from '../types'; +import { FrameworkContext } from './Context.js'; const runtime: RuntimePlugin = ({ setRender }, runtimeOptions) => { if (runtimeOptions?.container) { setRender((_, element) => { // Replace render root when app rendered as a child app. const root = ReactDOM.createRoot(runtimeOptions.container); - root.render(element); + root.render( + + {element} + , + ); return root; }); } diff --git a/packages/plugin-icestark/src/runtime/framework.tsx b/packages/plugin-icestark/src/runtime/framework.tsx index e8743b990..fffc2484f 100644 --- a/packages/plugin-icestark/src/runtime/framework.tsx +++ b/packages/plugin-icestark/src/runtime/framework.tsx @@ -1,100 +1,98 @@ import * as React from 'react'; import { AppRouter, AppRoute } from '@ice/stark'; import type { RuntimePlugin, ClientAppRouterProps } from '@ice/runtime/types'; -import type { RouteInfo, AppConfig } from '../types'; +import type { AppRouterProps } from '@ice/stark/lib/AppRouter'; +import type { RouteInfo, AppConfig, FrameworkConfig } from '../types'; const { useState, useEffect } = React; const runtime: RuntimePlugin = ({ getAppRouter, setAppRouter, appContext }) => { const { appExport, appData } = appContext; const OriginalRouter = getAppRouter(); - const { layout, getApps, appRouter } = appExport?.icestark || {}; + const { layout, getApps, appRouter, AppRoute: CustomizeAppRoute } = (appExport?.icestark || {}) as FrameworkConfig; - if (getApps) { - const FrameworkRouter = (props: ClientAppRouterProps) => { - const [routeInfo, setRouteInfo] = useState({}); - const [appEnter, setAppEnter] = useState({}); - const [appLeave, setAppLeave] = useState({}); - const [apps, setApps] = useState([]); - const FrameworkLayout = layout || (({ children }) => (<>{children})); - const appInfo = { - pathname: routeInfo.pathname || - (typeof window !== 'undefined' && window.location.pathname), - routeInfo, - appEnter, - appLeave, - updateApps: setApps, - }; - useEffect(() => { - (async () => { - const appList = await getApps(appData); - setApps(appList); - })(); - }, []); - - function handleRouteChange(pathname: string, query: Record, hash: string, routeType: string) { - setRouteInfo({ pathname, query, hash, routeType }); - } - - function handleAppLeave(config: AppConfig) { - setAppLeave(config); - } - - function handleAppEnter(config: AppConfig) { - setAppEnter(config); - } - return ( - - {apps && ( - - {apps.map((item: AppConfig, idx: number) => { - return ( - - ); - })} - { - const { routerContext } = props; - routerContext.routes = [ - ...routerContext.routes, - { - path: '*', - Component: () => ( - process.env.NODE_ENV === 'development' - ?
Add $.tsx to folder pages as a 404 component
- : null - ), - }, - ]; - const routerProps = { - ...props, - routerContext, - }; - return ; - }} - /> -
- )} -
- ); - }; - setAppRouter(FrameworkRouter); - } else { + if (!getApps) { console.warn(` [plugin-icestark]: appConfig.icestark.getApps should be not empty if this is an framework app. see https://ice.work/docs/guide/advanced/icestark/ `); + return; } + + const FrameworkRouter = (props: ClientAppRouterProps) => { + const [routeInfo, setRouteInfo] = useState({}); + const [appEnter, setAppEnter] = useState({}); + const [appLeave, setAppLeave] = useState({}); + const [apps, setApps] = useState(null); + const FrameworkLayout = layout || React.Fragment; + const appInfo = { + pathname: routeInfo.pathname || (typeof window !== 'undefined' ? window.location.pathname : ''), + routeInfo, + appEnter, + appLeave, + updateApps: setApps, + }; + + useEffect(() => { + const fetchApps = async () => { + try { + const appList = await getApps(appData); + setApps(appList); + } catch (error) { + console.error('[plugin-icestark]: Failed to fetch apps', error); + } + }; + + fetchApps(); + }, []); + + const handleRouteChange: AppRouterProps['onRouteChange'] = (pathname, query, hash, routeType) => { + setRouteInfo({ pathname, query, hash, routeType }); + }; + + const handleAppLeave: AppRouterProps['onAppLeave'] = (config) => setAppLeave(config); + const handleAppEnter: AppRouterProps['onAppEnter'] = (config) => setAppEnter(config); + const AppRouteComponent = CustomizeAppRoute || AppRoute; + + const appRouterProps: AppRouterProps = { + ...appRouter, + onRouteChange: handleRouteChange, + onAppEnter: handleAppEnter, + onAppLeave: handleAppLeave, + }; + return ( + + {apps && ( + + {apps?.map((item: AppConfig, idx: number) => ( + + ))} + { + const { routerContext } = props; + routerContext.routes = [ + ...routerContext.routes, + { + path: '*', + Component: () => ( + process.env.NODE_ENV === 'development' + ?
Add $.tsx to folder pages as a 404 component
+ : null + ), + }, + ]; + return ; + }} + /> +
+ )} +
+ ); + }; + + setAppRouter(FrameworkRouter); }; export default runtime; diff --git a/packages/plugin-icestark/src/types.ts b/packages/plugin-icestark/src/types.ts index 905dc42ba..92acc6e6b 100644 --- a/packages/plugin-icestark/src/types.ts +++ b/packages/plugin-icestark/src/types.ts @@ -1,10 +1,11 @@ import type { ComponentType } from 'react'; import type { CompatibleAppConfig } from '@ice/stark/lib/AppRoute'; import type { AppRouterProps } from '@ice/stark/lib/AppRouter'; +import type { AppRoute } from '@ice/stark'; export interface RouteInfo { pathname?: string; - query?: Record; + query?: object; hash?: string; routeType?: string; } @@ -17,6 +18,7 @@ export interface FrameworkConfig { getApps?: (data?: any) => (AppConfig[] | Promise); appRouter?: Omit; layout?: ComponentType; + AppRoute?: typeof AppRoute; } export interface LifecycleOptions {