feat: support lazy compile of routes

This commit is contained in:
ClarkXia 2024-08-27 16:10:33 +08:00
parent 61d7c95d30
commit a5d13dcfd3
10 changed files with 107 additions and 4 deletions

View File

@ -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,

View File

@ -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.

View File

@ -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;
},

View File

@ -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: {

View File

@ -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,

View File

@ -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') {

View File

@ -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,
};
}

View File

@ -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}]`);

View File

@ -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.

View File

@ -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;