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,7 +12,6 @@ export function getFileName(filePath: string) {
|
|||
return filePath.split('/').slice(-1)[0];
|
||||
}
|
||||
|
||||
export function getHookFiles() {
|
||||
const webpackPlugins = [
|
||||
// plugins require the same webpack instance
|
||||
'webpack/lib/LibraryTemplatePlugin',
|
||||
|
|
@ -33,20 +32,36 @@ export function getHookFiles() {
|
|||
'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 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 createPluginMapping = (pluginPath: string, withJsExtension = false) => [
|
||||
withJsExtension ? `${pluginPath}.js` : pluginPath,
|
||||
pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util|dependencies)\/)?/, webpackDir),
|
||||
];
|
||||
});
|
||||
const pluginMapWithJs = webpackPlugins.map((pluginPath) => {
|
||||
return [
|
||||
`${pluginPath}.js`,
|
||||
pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util)\/)?/, 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,67 +1,74 @@
|
|||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 [apps, setApps] = useState<AppConfig[] | null>(null);
|
||||
const FrameworkLayout = layout || React.Fragment;
|
||||
const appInfo = {
|
||||
pathname: routeInfo.pathname ||
|
||||
(typeof window !== 'undefined' && window.location.pathname),
|
||||
pathname: routeInfo.pathname || (typeof window !== 'undefined' ? window.location.pathname : ''),
|
||||
routeInfo,
|
||||
appEnter,
|
||||
appLeave,
|
||||
updateApps: setApps,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const fetchApps = async () => {
|
||||
try {
|
||||
const appList = await getApps(appData);
|
||||
setApps(appList);
|
||||
})();
|
||||
} catch (error) {
|
||||
console.error('[plugin-icestark]: Failed to fetch apps', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchApps();
|
||||
}, []);
|
||||
|
||||
function handleRouteChange(pathname: string, query: Record<string, string>, hash: string, routeType: string) {
|
||||
const handleRouteChange: AppRouterProps['onRouteChange'] = (pathname, query, hash, routeType) => {
|
||||
setRouteInfo({ pathname, query, hash, routeType });
|
||||
}
|
||||
};
|
||||
|
||||
function handleAppLeave(config: AppConfig) {
|
||||
setAppLeave(config);
|
||||
}
|
||||
const handleAppLeave: AppRouterProps['onAppLeave'] = (config) => setAppLeave(config);
|
||||
const handleAppEnter: AppRouterProps['onAppEnter'] = (config) => setAppEnter(config);
|
||||
const AppRouteComponent = CustomizeAppRoute || AppRoute;
|
||||
|
||||
function handleAppEnter(config: AppConfig) {
|
||||
setAppEnter(config);
|
||||
}
|
||||
const appRouterProps: AppRouterProps = {
|
||||
...appRouter,
|
||||
onRouteChange: handleRouteChange,
|
||||
onAppEnter: handleAppEnter,
|
||||
onAppLeave: handleAppLeave,
|
||||
};
|
||||
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="/"
|
||||
<AppRouter {...appRouterProps}>
|
||||
{apps?.map((item: AppConfig, idx: number) => (
|
||||
<AppRouteComponent key={idx} {...item} />
|
||||
))}
|
||||
<AppRouteComponent
|
||||
activePath="/"
|
||||
location={props.location}
|
||||
render={() => {
|
||||
const { routerContext } = props;
|
||||
|
|
@ -76,11 +83,7 @@ const runtime: RuntimePlugin = ({ getAppRouter, setAppRouter, appContext }) => {
|
|||
),
|
||||
},
|
||||
];
|
||||
const routerProps = {
|
||||
...props,
|
||||
routerContext,
|
||||
};
|
||||
return <OriginalRouter {...routerProps} />;
|
||||
return <OriginalRouter {...props} routerContext={routerContext} />;
|
||||
}}
|
||||
/>
|
||||
</AppRouter>
|
||||
|
|
@ -88,13 +91,8 @@ const runtime: RuntimePlugin = ({ getAppRouter, setAppRouter, appContext }) => {
|
|||
</FrameworkLayout>
|
||||
);
|
||||
};
|
||||
|
||||
setAppRouter(FrameworkRouter);
|
||||
} else {
|
||||
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/
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
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