mirror of https://github.com/alibaba/ice.git
				
				
				
			Feat: enhance plugin icestark and refactor webpack bundle exports (#7068)
* feat: support custom AppRoute * feat: support framework provider * fix: failed to resolve context * fix: add export type * fix: add the library name to global * feat: add export of webpack * fix: require hooks * fix: export webpack module * feat: add export of webpack * fix: support resolve webpack * fix: revert bundles version * fix: update hook path * fix: render AppRouter * fix: init default value
This commit is contained in:
		
							parent
							
								
									b930600a17
								
							
						
					
					
						commit
						2f73084d61
					
				|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| '@ice/plugin-icestark': minor | ||||
| --- | ||||
| 
 | ||||
| feat: support framework provider | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| '@ice/bundles': patch | ||||
| --- | ||||
| 
 | ||||
| feat: export ModuleNotFoundError of webpack | ||||
|  | @ -0,0 +1,5 @@ | |||
| --- | ||||
| '@ice/plugin-icestark': minor | ||||
| --- | ||||
| 
 | ||||
| feat: support custom AppRoute | ||||
|  | @ -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: { | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').ChunkHelpers; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').Compilation; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').HotUpdateChunk; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').LazySet; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').ModuleDependency; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').ModuleFactory; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').ModuleNotFoundError; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').SetHelpers; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').SortableSet; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').StartupEntrypointRuntimeModule; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').StaticExportsDependency; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').WebpackError; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').comparators; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').createSchemaValidation; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').extractUrlAndGlobal; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').fs; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').makeSerializable; | ||||
|  | @ -0,0 +1 @@ | |||
| module.exports = require('./bundle').semver; | ||||
|  | @ -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); | ||||
|   }; | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| export * from './esm/runtime/Context'; | ||||
|  | @ -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", | ||||
|  |  | |||
|  | @ -9,12 +9,12 @@ const PLUGIN_NAME = '@ice/plugin-icestark'; | |||
| const plugin: Plugin<PluginOptions> = ({ 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<PluginOptions> = ({ 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) { | ||||
|  |  | |||
|  | @ -0,0 +1,7 @@ | |||
| import { createContext, useContext } from 'react'; | ||||
| 
 | ||||
| export const FrameworkContext = createContext({}); | ||||
| 
 | ||||
| export const useFrameworkContext = <T extends object>(): T => { | ||||
|   return useContext(FrameworkContext) as T; | ||||
| }; | ||||
|  | @ -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<LifecycleOptions> = ({ 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( | ||||
|         <FrameworkContext.Provider value={{ ...(runtimeOptions.customProps || {}) }}> | ||||
|           {element} | ||||
|         </FrameworkContext.Provider>, | ||||
|       ); | ||||
|       return root; | ||||
|     }); | ||||
|   } | ||||
|  |  | |||
|  | @ -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<RouteInfo>({}); | ||||
|       const [appEnter, setAppEnter] = useState<AppConfig>({}); | ||||
|       const [appLeave, setAppLeave] = useState<AppConfig>({}); | ||||
|       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<string, string>, hash: string, routeType: string) { | ||||
|         setRouteInfo({ pathname, query, hash, routeType }); | ||||
|       } | ||||
| 
 | ||||
|       function handleAppLeave(config: AppConfig) { | ||||
|         setAppLeave(config); | ||||
|       } | ||||
| 
 | ||||
|       function handleAppEnter(config: AppConfig) { | ||||
|         setAppEnter(config); | ||||
|       } | ||||
|       return ( | ||||
|         <FrameworkLayout {...appInfo}> | ||||
|           {apps && ( | ||||
|             <AppRouter | ||||
|               {...appRouter} | ||||
|               onRouteChange={handleRouteChange} | ||||
|               onAppEnter={handleAppEnter} | ||||
|               onAppLeave={handleAppLeave} | ||||
|             > | ||||
|               {apps.map((item: AppConfig, idx: number) => { | ||||
|                 return ( | ||||
|                   <AppRoute | ||||
|                     key={idx} | ||||
|                     {...item} | ||||
|                   /> | ||||
|                 ); | ||||
|               })} | ||||
|               <AppRoute | ||||
|                 path="/" | ||||
|                 location={props.location} | ||||
|                 render={() => { | ||||
|                   const { routerContext } = props; | ||||
|                   routerContext.routes = [ | ||||
|                     ...routerContext.routes, | ||||
|                     { | ||||
|                       path: '*', | ||||
|                       Component: () => ( | ||||
|                         process.env.NODE_ENV === 'development' | ||||
|                           ? <div>Add $.tsx to folder pages as a 404 component</div> | ||||
|                           : null | ||||
|                         ), | ||||
|                     }, | ||||
|                   ]; | ||||
|                   const routerProps = { | ||||
|                     ...props, | ||||
|                     routerContext, | ||||
|                   }; | ||||
|                   return <OriginalRouter {...routerProps} />; | ||||
|                 }} | ||||
|               /> | ||||
|             </AppRouter> | ||||
|           )} | ||||
|         </FrameworkLayout> | ||||
|       ); | ||||
|     }; | ||||
|     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<RouteInfo>({}); | ||||
|     const [appEnter, setAppEnter] = useState<AppConfig>({}); | ||||
|     const [appLeave, setAppLeave] = useState<AppConfig>({}); | ||||
|     const [apps, setApps] = useState<AppConfig[] | null>(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 ( | ||||
|       <FrameworkLayout {...appInfo}> | ||||
|         {apps && ( | ||||
|           <AppRouter {...appRouterProps}> | ||||
|             {apps?.map((item: AppConfig, idx: number) => ( | ||||
|               <AppRouteComponent key={idx} {...item} /> | ||||
|             ))} | ||||
|             <AppRouteComponent | ||||
|               activePath="/" | ||||
|               location={props.location} | ||||
|               render={() => { | ||||
|                 const { routerContext } = props; | ||||
|                 routerContext.routes = [ | ||||
|                   ...routerContext.routes, | ||||
|                   { | ||||
|                     path: '*', | ||||
|                     Component: () => ( | ||||
|                       process.env.NODE_ENV === 'development' | ||||
|                         ? <div>Add $.tsx to folder pages as a 404 component</div> | ||||
|                         : null | ||||
|                     ), | ||||
|                   }, | ||||
|                 ]; | ||||
|                 return <OriginalRouter {...props} routerContext={routerContext} />; | ||||
|               }} | ||||
|             /> | ||||
|           </AppRouter> | ||||
|         )} | ||||
|       </FrameworkLayout> | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   setAppRouter(FrameworkRouter); | ||||
| }; | ||||
| 
 | ||||
| export default runtime; | ||||
|  |  | |||
|  | @ -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<string, string>; | ||||
|   query?: object; | ||||
|   hash?: string; | ||||
|   routeType?: string; | ||||
| } | ||||
|  | @ -17,6 +18,7 @@ export interface FrameworkConfig { | |||
|   getApps?: (data?: any) => (AppConfig[] | Promise<AppConfig[]>); | ||||
|   appRouter?: Omit<AppRouterProps, 'onRouteChange' | 'onAppEnter' | 'onAppLeave'>; | ||||
|   layout?: ComponentType<any>; | ||||
|   AppRoute?: typeof AppRoute; | ||||
| } | ||||
| 
 | ||||
| export interface LifecycleOptions { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue