mirror of https://github.com/alibaba/ice.git
feat: support lazy compile of routes
This commit is contained in:
parent
61d7c95d30
commit
a5d13dcfd3
|
@ -6,9 +6,11 @@ import type { Config } from '@ice/shared-config/types';
|
||||||
import createMockMiddleware from '../../middlewares/mock/createMiddleware.js';
|
import createMockMiddleware from '../../middlewares/mock/createMiddleware.js';
|
||||||
import createRenderMiddleware from '../../middlewares/renderMiddleware.js';
|
import createRenderMiddleware from '../../middlewares/renderMiddleware.js';
|
||||||
import createDataLoaderMiddleware from '../../middlewares/dataLoaderMiddleware.js';
|
import createDataLoaderMiddleware from '../../middlewares/dataLoaderMiddleware.js';
|
||||||
|
import createProxyModuleMiddleware from '../../middlewares/proxyModuleMiddleware.js';
|
||||||
import type { UserConfig } from '../../types/userConfig.js';
|
import type { UserConfig } from '../../types/userConfig.js';
|
||||||
import type RouteManifest from '../../utils/routeManifest.js';
|
import type RouteManifest from '../../utils/routeManifest.js';
|
||||||
import type { GetAppConfig } from '../../types/plugin.js';
|
import type { GetAppConfig } from '../../types/plugin.js';
|
||||||
|
import type Generator from '../../service/runtimeGenerator.js';
|
||||||
|
|
||||||
interface SetupOptions {
|
interface SetupOptions {
|
||||||
userConfig: UserConfig;
|
userConfig: UserConfig;
|
||||||
|
@ -19,6 +21,7 @@ interface SetupOptions {
|
||||||
mock: boolean;
|
mock: boolean;
|
||||||
rootDir: string;
|
rootDir: string;
|
||||||
dataLoaderCompiler?: Compiler;
|
dataLoaderCompiler?: Compiler;
|
||||||
|
generator?: Generator;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupMiddlewares']>[0], {
|
function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupMiddlewares']>[0], {
|
||||||
|
@ -30,8 +33,9 @@ function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupM
|
||||||
mock,
|
mock,
|
||||||
rootDir,
|
rootDir,
|
||||||
dataLoaderCompiler,
|
dataLoaderCompiler,
|
||||||
|
generator,
|
||||||
}: SetupOptions) {
|
}: SetupOptions) {
|
||||||
const { ssr, ssg } = userConfig;
|
const { ssr, ssg, routes } = userConfig;
|
||||||
let renderMode: RenderMode;
|
let renderMode: RenderMode;
|
||||||
// If ssr is set to true, use ssr for preview.
|
// If ssr is set to true, use ssr for preview.
|
||||||
if (ssr) {
|
if (ssr) {
|
||||||
|
@ -56,8 +60,21 @@ function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupM
|
||||||
middlewares.unshift(dataLoaderMiddleware);
|
middlewares.unshift(dataLoaderMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proxyModuleMiddleware = createProxyModuleMiddleware({
|
||||||
|
manifest: routeManifest.getNestedRoute(),
|
||||||
|
rootDir,
|
||||||
|
generator,
|
||||||
|
});
|
||||||
|
|
||||||
// @ts-ignore property of name is exist.
|
// @ts-ignore property of name is exist.
|
||||||
const insertIndex = middlewares.findIndex(({ name }) => name === 'serve-index');
|
const insertIndex = middlewares.findIndex(({ name }) => name === 'serve-index');
|
||||||
|
if (routes?.lazyCompile) {
|
||||||
|
middlewares.splice(
|
||||||
|
insertIndex, 0,
|
||||||
|
proxyModuleMiddleware,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
middlewares.splice(
|
middlewares.splice(
|
||||||
insertIndex, 0,
|
insertIndex, 0,
|
||||||
serverRenderMiddleware,
|
serverRenderMiddleware,
|
||||||
|
|
|
@ -18,6 +18,7 @@ async function bundler(
|
||||||
routeManifest,
|
routeManifest,
|
||||||
appConfig,
|
appConfig,
|
||||||
hasDataLoader,
|
hasDataLoader,
|
||||||
|
generator,
|
||||||
} = options;
|
} = options;
|
||||||
let compiler: MultiCompiler;
|
let compiler: MultiCompiler;
|
||||||
let dataLoaderCompiler: Compiler;
|
let dataLoaderCompiler: Compiler;
|
||||||
|
@ -63,6 +64,7 @@ async function bundler(
|
||||||
hooksAPI,
|
hooksAPI,
|
||||||
taskConfigs,
|
taskConfigs,
|
||||||
rspackConfigs,
|
rspackConfigs,
|
||||||
|
generator,
|
||||||
};
|
};
|
||||||
if (command === 'start') {
|
if (command === 'start') {
|
||||||
// @ts-expect-error dev-server has been pre-packed, so it will have different type.
|
// @ts-expect-error dev-server has been pre-packed, so it will have different type.
|
||||||
|
|
|
@ -16,6 +16,7 @@ const start = async ({
|
||||||
compiler,
|
compiler,
|
||||||
appConfig,
|
appConfig,
|
||||||
hooksAPI,
|
hooksAPI,
|
||||||
|
generator,
|
||||||
}: BuildOptions, dataLoaderCompiler?: Compiler) => {
|
}: BuildOptions, dataLoaderCompiler?: Compiler) => {
|
||||||
const { rootDir, applyHook, commandArgs, userConfig, extendsPluginAPI: { excuteServerEntry } } = context;
|
const { rootDir, applyHook, commandArgs, userConfig, extendsPluginAPI: { excuteServerEntry } } = context;
|
||||||
const customMiddlewares = rspackConfigs[0].devServer?.setupMiddlewares;
|
const customMiddlewares = rspackConfigs[0].devServer?.setupMiddlewares;
|
||||||
|
@ -34,6 +35,7 @@ const start = async ({
|
||||||
mock: commandArgs.mock,
|
mock: commandArgs.mock,
|
||||||
rootDir,
|
rootDir,
|
||||||
dataLoaderCompiler,
|
dataLoaderCompiler,
|
||||||
|
generator,
|
||||||
});
|
});
|
||||||
return customMiddlewares ? customMiddlewares(builtInMiddlewares, devServer) : builtInMiddlewares;
|
return customMiddlewares ? customMiddlewares(builtInMiddlewares, devServer) : builtInMiddlewares;
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { ServerCompiler, GetAppConfig, GetRoutesConfig, GetDataloaderConfig
|
||||||
import type { UserConfig } from '../types/userConfig.js';
|
import type { UserConfig } from '../types/userConfig.js';
|
||||||
import type RouteManifest from '../utils/routeManifest.js';
|
import type RouteManifest from '../utils/routeManifest.js';
|
||||||
import type ServerRunner from '../service/ServerRunner.js';
|
import type ServerRunner from '../service/ServerRunner.js';
|
||||||
|
import type Generator from '../service/runtimeGenerator.js';
|
||||||
|
|
||||||
export type Context = DefaultContext<Config, ExtendsPluginAPI>;
|
export type Context = DefaultContext<Config, ExtendsPluginAPI>;
|
||||||
|
|
||||||
|
@ -19,9 +20,11 @@ export interface BuildOptions {
|
||||||
appConfig: BundlerOptions['appConfig'];
|
appConfig: BundlerOptions['appConfig'];
|
||||||
hooksAPI: BundlerOptions['hooksAPI'];
|
hooksAPI: BundlerOptions['hooksAPI'];
|
||||||
taskConfigs: BundlerOptions['taskConfigs'];
|
taskConfigs: BundlerOptions['taskConfigs'];
|
||||||
|
generator: Generator;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BundlerOptions {
|
export interface BundlerOptions {
|
||||||
|
generator: Generator;
|
||||||
taskConfigs: TaskConfig<Config>[];
|
taskConfigs: TaskConfig<Config>[];
|
||||||
spinner: ora.Ora;
|
spinner: ora.Ora;
|
||||||
hooksAPI: {
|
hooksAPI: {
|
||||||
|
|
|
@ -27,6 +27,7 @@ export async function startDevServer(
|
||||||
routeManifest,
|
routeManifest,
|
||||||
userConfig,
|
userConfig,
|
||||||
appConfig,
|
appConfig,
|
||||||
|
generator,
|
||||||
} = options;
|
} = options;
|
||||||
const routePaths = routeManifest.getFlattenRoute().sort((a, b) =>
|
const routePaths = routeManifest.getFlattenRoute().sort((a, b) =>
|
||||||
// Sort by length, shortest path first.
|
// Sort by length, shortest path first.
|
||||||
|
@ -40,6 +41,7 @@ export async function startDevServer(
|
||||||
...defaultDevServerConfig,
|
...defaultDevServerConfig,
|
||||||
setupMiddlewares: (middlewares, devServer) => {
|
setupMiddlewares: (middlewares, devServer) => {
|
||||||
const builtInMiddlewares = getMiddlewares(middlewares, {
|
const builtInMiddlewares = getMiddlewares(middlewares, {
|
||||||
|
generator,
|
||||||
userConfig,
|
userConfig,
|
||||||
routeManifest,
|
routeManifest,
|
||||||
getAppConfig: hooksAPI.getAppConfig,
|
getAppConfig: hooksAPI.getAppConfig,
|
||||||
|
|
|
@ -221,6 +221,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
||||||
const { userConfig } = ctx;
|
const { userConfig } = ctx;
|
||||||
const { routes: routesConfig, server, syntaxFeatures, polyfill } = userConfig;
|
const { routes: routesConfig, server, syntaxFeatures, polyfill } = userConfig;
|
||||||
|
|
||||||
|
|
||||||
const coreEnvKeys = getCoreEnvKeys();
|
const coreEnvKeys = getCoreEnvKeys();
|
||||||
|
|
||||||
const routesInfo = await generateRoutesInfo(rootDir, routesConfig, routeManifest.getRoutesDefinitions());
|
const routesInfo = await generateRoutesInfo(rootDir, routesConfig, routeManifest.getRoutesDefinitions());
|
||||||
|
@ -252,6 +253,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
||||||
const { routeImports, routeDefinition } = getRoutesDefinition({
|
const { routeImports, routeDefinition } = getRoutesDefinition({
|
||||||
manifest: routesInfo.routes,
|
manifest: routesInfo.routes,
|
||||||
lazy,
|
lazy,
|
||||||
|
compileRoutes: routesConfig.lazyCompile ? [] : undefined,
|
||||||
});
|
});
|
||||||
const loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
|
const loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
|
||||||
const hasDataLoader = Boolean(userConfig.dataLoader) && loaderExports;
|
const hasDataLoader = Boolean(userConfig.dataLoader) && loaderExports;
|
||||||
|
@ -301,6 +303,9 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
||||||
generator.addRenderFile('core/entry.server.ts.ejs', FALLBACK_ENTRY, { hydrate: false });
|
generator.addRenderFile('core/entry.server.ts.ejs', FALLBACK_ENTRY, { hydrate: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (routesConfig?.lazyCompile) {
|
||||||
|
generator.addRenderFile('core/empty.tsx.ejs', 'empty.tsx');
|
||||||
|
}
|
||||||
if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) {
|
if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) {
|
||||||
const {
|
const {
|
||||||
packageName,
|
packageName,
|
||||||
|
@ -401,6 +406,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
||||||
userConfig,
|
userConfig,
|
||||||
configFile,
|
configFile,
|
||||||
hasDataLoader,
|
hasDataLoader,
|
||||||
|
generator,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
if (command === 'test') {
|
if (command === 'test') {
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import type { ExpressRequestHandler, Middleware } from 'webpack-dev-server';
|
||||||
|
import type { NestedRouteManifest } from '@ice/route-manifest';
|
||||||
|
import { getRoutesDefinition } from '../routes.js';
|
||||||
|
import { RUNTIME_TMP_DIR } from '../constant.js';
|
||||||
|
import type Generator from '../service/runtimeGenerator.js';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
manifest: NestedRouteManifest[];
|
||||||
|
generator: Generator;
|
||||||
|
rootDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createRenderMiddleware(options: Options): Middleware {
|
||||||
|
const {
|
||||||
|
manifest,
|
||||||
|
generator,
|
||||||
|
rootDir,
|
||||||
|
} = options;
|
||||||
|
const accessedPath = new Set<string>();
|
||||||
|
|
||||||
|
const middleware: ExpressRequestHandler = async function (req, res, next) {
|
||||||
|
if (req.path === '/proxy-module') {
|
||||||
|
if (!accessedPath.has(req.query.pathname)) {
|
||||||
|
accessedPath.add(req.query.pathname);
|
||||||
|
const { routeImports, routeDefinition } = getRoutesDefinition({
|
||||||
|
manifest,
|
||||||
|
lazy: true,
|
||||||
|
compileRoutes: Array.from(accessedPath),
|
||||||
|
});
|
||||||
|
const templateDir = path.join(__dirname, '../../templates/core/');
|
||||||
|
generator.renderFile(
|
||||||
|
path.join(templateDir, 'routes.tsx.ejs'),
|
||||||
|
path.join(rootDir, RUNTIME_TMP_DIR, 'routes.tsx'),
|
||||||
|
{ routeImports, routeDefinition },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send('');
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'proxy-module',
|
||||||
|
middleware,
|
||||||
|
};
|
||||||
|
}
|
|
@ -69,10 +69,11 @@ interface GetDefinationOptions {
|
||||||
lazy?: boolean;
|
lazy?: boolean;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
matchRoute?: (route: NestedRouteManifest) => boolean;
|
matchRoute?: (route: NestedRouteManifest) => boolean;
|
||||||
|
compileRoutes?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoutesDefinition(options: GetDefinationOptions) {
|
export function getRoutesDefinition(options: GetDefinationOptions) {
|
||||||
const { manifest, lazy = false, depth = 0, matchRoute = () => true } = options;
|
const { manifest, lazy = false, depth = 0, matchRoute = () => true, compileRoutes } = options;
|
||||||
const routeImports: string[] = [];
|
const routeImports: string[] = [];
|
||||||
const routeDefinition = manifest.reduce((prev, route) => {
|
const routeDefinition = manifest.reduce((prev, route) => {
|
||||||
if (!matchRoute(route)) {
|
if (!matchRoute(route)) {
|
||||||
|
@ -80,10 +81,11 @@ export function getRoutesDefinition(options: GetDefinationOptions) {
|
||||||
}
|
}
|
||||||
const { children, path: routePath, index, componentName, file, id, layout, exports } = route;
|
const { children, path: routePath, index, componentName, file, id, layout, exports } = route;
|
||||||
const componentPath = id.startsWith('__') ? file : getFilePath(file);
|
const componentPath = id.startsWith('__') ? file : getFilePath(file);
|
||||||
|
const proxyModule = './empty';
|
||||||
let loadStatement = '';
|
let loadStatement = '';
|
||||||
if (lazy) {
|
if (lazy) {
|
||||||
loadStatement = `import(/* webpackChunkName: "p_${componentName}" */ '${formatPath(componentPath)}')`;
|
const filePath = compileRoutes ? (compileRoutes.includes(`/${routePath || ''}`) ? componentPath : proxyModule) : componentPath;
|
||||||
|
loadStatement = `import(/* webpackChunkName: "p_${componentName}" */ '${formatPath(filePath)}')`;
|
||||||
} else {
|
} else {
|
||||||
const routeSpecifier = formatRouteSpecifier(id);
|
const routeSpecifier = formatRouteSpecifier(id);
|
||||||
routeImports.push(`import * as ${routeSpecifier} from '${formatPath(componentPath)}';`);
|
routeImports.push(`import * as ${routeSpecifier} from '${formatPath(componentPath)}';`);
|
||||||
|
@ -128,6 +130,7 @@ export function getRoutesDefinition(options: GetDefinationOptions) {
|
||||||
lazy,
|
lazy,
|
||||||
depth: depth + 1,
|
depth: depth + 1,
|
||||||
matchRoute,
|
matchRoute,
|
||||||
|
compileRoutes,
|
||||||
});
|
});
|
||||||
routeImports.push(...res.routeImports);
|
routeImports.push(...res.routeImports);
|
||||||
routeProperties.push(`children: [${res.routeDefinition}]`);
|
routeProperties.push(`children: [${res.routeDefinition}]`);
|
||||||
|
|
|
@ -153,6 +153,10 @@ export interface UserConfig {
|
||||||
* inject initial route path for each route html.
|
* inject initial route path for each route html.
|
||||||
*/
|
*/
|
||||||
injectInitialEntry?: boolean;
|
injectInitialEntry?: boolean;
|
||||||
|
/**
|
||||||
|
* Enable lazy compile for routes.
|
||||||
|
*/
|
||||||
|
lazyCompile?: boolean;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Add ice.js plugin to customize framework config.
|
* Add ice.js plugin to customize framework config.
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'ice';
|
||||||
|
|
||||||
|
const ProxyModule = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`/proxy-module?pathname=${location.pathname}`);
|
||||||
|
}, []);
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProxyModule;
|
Loading…
Reference in New Issue