Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: support lazy compile of routes #6974

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smooth-rocks-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/app': patch
---

feat: support lazy compile of routes
23 changes: 22 additions & 1 deletion packages/ice/src/bundler/config/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,7 +20,9 @@ interface SetupOptions {
excuteServerEntry: () => Promise<any>;
mock: boolean;
rootDir: string;
open?: boolean | string;
dataLoaderCompiler?: Compiler;
generator?: Generator;
}

function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupMiddlewares']>[0], {
Expand All @@ -30,8 +34,10 @@ function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupM
mock,
rootDir,
dataLoaderCompiler,
generator,
open,
}: 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) {
Expand All @@ -56,6 +62,21 @@ function setupMiddlewares(middlewares: Parameters<DevServerConfiguration['setupM
middlewares.unshift(dataLoaderMiddleware);
}

if (routes?.lazyCompile) {
const proxyModuleMiddleware = createProxyModuleMiddleware({
manifest: routeManifest.getNestedRoute(),
rootDir,
generator,
defaultPath: typeof open === 'string' ? open : '/',
});
// @ts-ignore property of name is exist.
const staticIndex = middlewares.findIndex(({ name }) => name === 'express-static');
middlewares.splice(
staticIndex, 0,
proxyModuleMiddleware,
);
}

// @ts-ignore property of name is exist.
const insertIndex = middlewares.findIndex(({ name }) => name === 'serve-index');
middlewares.splice(
Expand Down
2 changes: 2 additions & 0 deletions packages/ice/src/bundler/rspack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ async function bundler(
routeManifest,
appConfig,
hasDataLoader,
generator,
} = options;
let compiler: MultiCompiler;
let dataLoaderCompiler: Compiler;
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions packages/ice/src/bundler/rspack/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,8 +33,10 @@ const start = async ({
taskConfig: webTaskConfig,
excuteServerEntry,
mock: commandArgs.mock,
open: commandArgs.open,
rootDir,
dataLoaderCompiler,
generator,
});
return customMiddlewares ? customMiddlewares(builtInMiddlewares, devServer) : builtInMiddlewares;
},
Expand Down
3 changes: 3 additions & 0 deletions packages/ice/src/bundler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

Expand All @@ -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: {
Expand Down
3 changes: 3 additions & 0 deletions packages/ice/src/bundler/webpack/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -40,12 +41,14 @@ export async function startDevServer(
...defaultDevServerConfig,
setupMiddlewares: (middlewares, devServer) => {
const builtInMiddlewares = getMiddlewares(middlewares, {
generator,
userConfig,
routeManifest,
getAppConfig: hooksAPI.getAppConfig,
taskConfig: webTaskConfig,
excuteServerEntry,
mock: commandArgs.mock,
open: commandArgs.open,
rootDir,
});
return customMiddlewares ? customMiddlewares(builtInMiddlewares, devServer) : builtInMiddlewares;
Expand Down
11 changes: 11 additions & 0 deletions packages/ice/src/createService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import rspackBundler from './bundler/rspack/index.js';
import getDefaultTaskConfig from './plugins/task.js';
import { multipleServerEntry, renderMultiEntry } from './utils/multipleEntry.js';
import hasDocument from './utils/hasDocument.js';
import { addLeadingSlash } from './utils/slash.js';

const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
Expand All @@ -66,6 +67,10 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
templates: [coreTemplate],
});

if (commandArgs.open) {
commandArgs.open = typeof commandArgs.open === 'string' ? addLeadingSlash(commandArgs.open) : commandArgs.open;
}

const { addWatchEvent, removeWatchEvent } = createWatch({
watchDir: rootDir,
command,
Expand Down Expand Up @@ -221,6 +226,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());
Expand Down Expand Up @@ -252,6 +258,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
const { routeImports, routeDefinition } = getRoutesDefinition({
manifest: routesInfo.routes,
lazy,
compileRoutes: routesConfig?.lazyCompile && command === 'start' ? [commandArgs.open || '/'] : undefined,
});
const loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
const hasDataLoader = Boolean(userConfig.dataLoader) && loaderExports;
Expand Down Expand Up @@ -301,6 +308,9 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
generator.addRenderFile('core/entry.server.ts.ejs', FALLBACK_ENTRY, { hydrate: false });
}

if (routesConfig?.lazyCompile && command === 'start') {
generator.addRenderFile('core/empty.tsx.ejs', 'empty.tsx');
}
if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) {
const {
packageName,
Expand Down Expand Up @@ -401,6 +411,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
userConfig,
configFile,
hasDataLoader,
generator,
};
try {
if (command === 'test') {
Expand Down
54 changes: 54 additions & 0 deletions packages/ice/src/middlewares/proxyModuleMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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;
defaultPath: string;
}

export default function createRenderMiddleware(options: Options): Middleware {
const {
manifest,
generator,
rootDir,
defaultPath,
} = options;
const accessedPath = new Set<string>(defaultPath);

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,
};
}
11 changes: 8 additions & 3 deletions packages/ice/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,25 @@ 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)) {
return prev;
}
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 && !layout
? (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)}';`);
Expand Down Expand Up @@ -128,6 +132,7 @@ export function getRoutesDefinition(options: GetDefinationOptions) {
lazy,
depth: depth + 1,
matchRoute,
compileRoutes,
});
routeImports.push(...res.routeImports);
routeProperties.push(`children: [${res.routeDefinition}]`);
Expand Down
4 changes: 4 additions & 0 deletions packages/ice/src/types/userConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions packages/ice/src/utils/slash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const addLeadingSlash = (path: string) => {
return path.charAt(0) === '/' ? path : `/${path}`;
};
12 changes: 12 additions & 0 deletions packages/ice/templates/core/empty.tsx.ejs
Original file line number Diff line number Diff line change
@@ -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