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 createRenderMiddleware from '../../middlewares/renderMiddleware.js';
|
||||
import createDataLoaderMiddleware from '../../middlewares/dataLoaderMiddleware.js';
|
||||
import createProxyModuleMiddleware from '../../middlewares/proxyModuleMiddleware.js';
|
||||
import type { UserConfig } from '../../types/userConfig.js';
|
||||
import type RouteManifest from '../../utils/routeManifest.js';
|
||||
import type { GetAppConfig } from '../../types/plugin.js';
|
||||
import type Generator from '../../service/runtimeGenerator.js';
|
||||
|
||||
interface SetupOptions {
|
||||
userConfig: UserConfig;
|
||||
|
@ -19,6 +21,7 @@ interface SetupOptions {
|
|||
mock: boolean;
|
||||
rootDir: string;
|
||||
dataLoaderCompiler?: Compiler;
|
||||
generator?: Generator;
|
||||
}
|
||||
|
||||
function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupMiddlewares']>[0], {
|
||||
|
@ -30,8 +33,9 @@ function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupM
|
|||
mock,
|
||||
rootDir,
|
||||
dataLoaderCompiler,
|
||||
generator,
|
||||
}: SetupOptions) {
|
||||
const { ssr, ssg } = userConfig;
|
||||
const { ssr, ssg, routes } = userConfig;
|
||||
let renderMode: RenderMode;
|
||||
// If ssr is set to true, use ssr for preview.
|
||||
if (ssr) {
|
||||
|
@ -56,8 +60,21 @@ function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupM
|
|||
middlewares.unshift(dataLoaderMiddleware);
|
||||
}
|
||||
|
||||
const proxyModuleMiddleware = createProxyModuleMiddleware({
|
||||
manifest: routeManifest.getNestedRoute(),
|
||||
rootDir,
|
||||
generator,
|
||||
});
|
||||
|
||||
// @ts-ignore property of name is exist.
|
||||
const insertIndex = middlewares.findIndex(({ name }) => name === 'serve-index');
|
||||
if (routes?.lazyCompile) {
|
||||
middlewares.splice(
|
||||
insertIndex, 0,
|
||||
proxyModuleMiddleware,
|
||||
);
|
||||
}
|
||||
|
||||
middlewares.splice(
|
||||
insertIndex, 0,
|
||||
serverRenderMiddleware,
|
||||
|
|
|
@ -18,6 +18,7 @@ async function bundler(
|
|||
routeManifest,
|
||||
appConfig,
|
||||
hasDataLoader,
|
||||
generator,
|
||||
} = options;
|
||||
let compiler: MultiCompiler;
|
||||
let dataLoaderCompiler: Compiler;
|
||||
|
@ -63,6 +64,7 @@ async function bundler(
|
|||
hooksAPI,
|
||||
taskConfigs,
|
||||
rspackConfigs,
|
||||
generator,
|
||||
};
|
||||
if (command === 'start') {
|
||||
// @ts-expect-error dev-server has been pre-packed, so it will have different type.
|
||||
|
|
|
@ -16,6 +16,7 @@ const start = async ({
|
|||
compiler,
|
||||
appConfig,
|
||||
hooksAPI,
|
||||
generator,
|
||||
}: BuildOptions, dataLoaderCompiler?: Compiler) => {
|
||||
const { rootDir, applyHook, commandArgs, userConfig, extendsPluginAPI: { excuteServerEntry } } = context;
|
||||
const customMiddlewares = rspackConfigs[0].devServer?.setupMiddlewares;
|
||||
|
@ -34,6 +35,7 @@ const start = async ({
|
|||
mock: commandArgs.mock,
|
||||
rootDir,
|
||||
dataLoaderCompiler,
|
||||
generator,
|
||||
});
|
||||
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 RouteManifest from '../utils/routeManifest.js';
|
||||
import type ServerRunner from '../service/ServerRunner.js';
|
||||
import type Generator from '../service/runtimeGenerator.js';
|
||||
|
||||
export type Context = DefaultContext<Config, ExtendsPluginAPI>;
|
||||
|
||||
|
@ -19,9 +20,11 @@ export interface BuildOptions {
|
|||
appConfig: BundlerOptions['appConfig'];
|
||||
hooksAPI: BundlerOptions['hooksAPI'];
|
||||
taskConfigs: BundlerOptions['taskConfigs'];
|
||||
generator: Generator;
|
||||
}
|
||||
|
||||
export interface BundlerOptions {
|
||||
generator: Generator;
|
||||
taskConfigs: TaskConfig<Config>[];
|
||||
spinner: ora.Ora;
|
||||
hooksAPI: {
|
||||
|
|
|
@ -27,6 +27,7 @@ export async function startDevServer(
|
|||
routeManifest,
|
||||
userConfig,
|
||||
appConfig,
|
||||
generator,
|
||||
} = options;
|
||||
const routePaths = routeManifest.getFlattenRoute().sort((a, b) =>
|
||||
// Sort by length, shortest path first.
|
||||
|
@ -40,6 +41,7 @@ export async function startDevServer(
|
|||
...defaultDevServerConfig,
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
const builtInMiddlewares = getMiddlewares(middlewares, {
|
||||
generator,
|
||||
userConfig,
|
||||
routeManifest,
|
||||
getAppConfig: hooksAPI.getAppConfig,
|
||||
|
|
|
@ -221,6 +221,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
const { userConfig } = ctx;
|
||||
const { routes: routesConfig, server, syntaxFeatures, polyfill } = userConfig;
|
||||
|
||||
|
||||
const coreEnvKeys = getCoreEnvKeys();
|
||||
|
||||
const routesInfo = await generateRoutesInfo(rootDir, routesConfig, routeManifest.getRoutesDefinitions());
|
||||
|
@ -252,6 +253,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
const { routeImports, routeDefinition } = getRoutesDefinition({
|
||||
manifest: routesInfo.routes,
|
||||
lazy,
|
||||
compileRoutes: routesConfig.lazyCompile ? [] : undefined,
|
||||
});
|
||||
const loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
|
||||
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 });
|
||||
}
|
||||
|
||||
if (routesConfig?.lazyCompile) {
|
||||
generator.addRenderFile('core/empty.tsx.ejs', 'empty.tsx');
|
||||
}
|
||||
if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) {
|
||||
const {
|
||||
packageName,
|
||||
|
@ -401,6 +406,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
userConfig,
|
||||
configFile,
|
||||
hasDataLoader,
|
||||
generator,
|
||||
};
|
||||
try {
|
||||
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;
|
||||
depth?: number;
|
||||
matchRoute?: (route: NestedRouteManifest) => boolean;
|
||||
compileRoutes?: string[];
|
||||
}
|
||||
|
||||
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 routeDefinition = manifest.reduce((prev, 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 componentPath = id.startsWith('__') ? file : getFilePath(file);
|
||||
|
||||
const proxyModule = './empty';
|
||||
let loadStatement = '';
|
||||
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 {
|
||||
const routeSpecifier = formatRouteSpecifier(id);
|
||||
routeImports.push(`import * as ${routeSpecifier} from '${formatPath(componentPath)}';`);
|
||||
|
@ -128,6 +130,7 @@ export function getRoutesDefinition(options: GetDefinationOptions) {
|
|||
lazy,
|
||||
depth: depth + 1,
|
||||
matchRoute,
|
||||
compileRoutes,
|
||||
});
|
||||
routeImports.push(...res.routeImports);
|
||||
routeProperties.push(`children: [${res.routeDefinition}]`);
|
||||
|
|
|
@ -153,6 +153,10 @@ export interface UserConfig {
|
|||
* inject initial route path for each route html.
|
||||
*/
|
||||
injectInitialEntry?: boolean;
|
||||
/**
|
||||
* Enable lazy compile for routes.
|
||||
*/
|
||||
lazyCompile?: boolean;
|
||||
};
|
||||
/**
|
||||
* 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