Skip to content

Commit

Permalink
feat: support split page chunk in cjs format (#6843)
Browse files Browse the repository at this point in the history
* feat: support spilt page chunk in cjs format

* fix: escape route path

* fix: multipile entry for server compile

* chore: changelog
  • Loading branch information
ClarkXia authored May 6, 2024
1 parent 7bd238a commit 11a87dc
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-dodos-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/app': patch
---

feat: support spilt page chunk in cjs format
8 changes: 8 additions & 0 deletions packages/ice/src/bundler/config/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import ServerRunnerPlugin from '../../webpack/ServerRunnerPlugin.js';
import { IMPORT_META_RENDERER, IMPORT_META_TARGET, WEB } from '../../constant.js';
import getServerCompilerPlugin from '../../utils/getServerCompilerPlugin.js';
import ReCompilePlugin from '../../webpack/ReCompilePlugin.js';
import getEntryPoints from '../../utils/getEntryPoints.js';
import { multipleServerEntry } from '../../utils/multipleEntry.js';
import type ServerRunner from '../../service/ServerRunner';
import type ServerCompileTask from '../../utils/ServerCompileTask.js';
import type { ServerCompiler, UserConfig } from '../../types';
Expand All @@ -28,6 +30,8 @@ interface ServerPluginOptions {
serverEntry?: string;
ensureRoutesConfig: () => Promise<void>;
userConfig?: UserConfig;
getFlattenRoutes?: () => string[];
command?: string;
}
export const getServerPlugin = ({
serverRunner,
Expand All @@ -39,6 +43,8 @@ export const getServerPlugin = ({
outputDir,
serverCompileTask,
userConfig,
getFlattenRoutes,
command,
}: ServerPluginOptions) => {
if (serverRunner) {
return new ServerRunnerPlugin(serverRunner, ensureRoutesConfig);
Expand All @@ -51,6 +57,8 @@ export const getServerPlugin = ({
serverCompileTask,
userConfig,
ensureRoutesConfig,
entryPoints: multipleServerEntry(userConfig, command)
? getEntryPoints(rootDir, getFlattenRoutes(), serverEntry) : undefined,
runtimeDefineVars: {
[IMPORT_META_TARGET]: JSON.stringify(target),
[IMPORT_META_RENDERER]: JSON.stringify('server'),
Expand Down
4 changes: 4 additions & 0 deletions packages/ice/src/bundler/rspack/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ const getConfig: GetConfig = async (context, options, rspack) => {
const {
rootDir,
userConfig,
command,
extendsPluginAPI: {
serverCompileTask,
getRoutesFile,
getFlattenRoutes,
},
} = context;
const { reCompile, ensureRoutesConfig } = getRouteExportConfig(rootDir);
Expand All @@ -57,6 +59,8 @@ const getConfig: GetConfig = async (context, options, rspack) => {
outputDir,
serverCompileTask,
userConfig,
getFlattenRoutes,
command,
}),
// Add ReCompile plugin when routes config changed.
getReCompilePlugin(reCompile, routeManifest),
Expand Down
3 changes: 3 additions & 0 deletions packages/ice/src/bundler/webpack/getWebpackConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const getWebpackConfig: GetWebpackConfig = async (context, options) => {
configFilePath,
extendsPluginAPI: {
serverCompileTask,
getFlattenRoutes,
getRoutesFile,
generator,
},
Expand Down Expand Up @@ -96,6 +97,8 @@ const getWebpackConfig: GetWebpackConfig = async (context, options) => {
outputDir,
serverCompileTask,
userConfig,
command,
getFlattenRoutes,
});
if (serverCompilerPlugin) {
webpackConfig.plugins.push(serverCompilerPlugin);
Expand Down
16 changes: 15 additions & 1 deletion packages/ice/src/createService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import addPolyfills from './utils/runtimePolyfill.js';
import webpackBundler from './bundler/webpack/index.js';
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';

const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -248,7 +249,10 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
const iceRuntimePath = '@ice/runtime';
// Only when code splitting use the default strategy or set to `router`, the router will be lazy loaded.
const lazy = [true, 'chunks', 'page', 'page-vendors'].includes(userConfig.codeSplitting);
const { routeImports, routeDefinition } = getRoutesDefinition(routesInfo.routes, lazy);
const { routeImports, routeDefinition } = getRoutesDefinition({
manifest: routesInfo.routes,
lazy,
});
const loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
const hasDataLoader = Boolean(userConfig.dataLoader) && loaderExports;
// add render data
Expand All @@ -272,6 +276,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
hasDataLoader,
routeImports,
routeDefinition,
routesFile: './routes',
});
dataCache.set('routes', JSON.stringify(routesInfo));
dataCache.set('hasExportAppData', hasExportAppData ? 'true' : '');
Expand Down Expand Up @@ -309,6 +314,15 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
});
}

if (multipleServerEntry(userConfig, command)) {
renderMultiEntry({
generator,
renderRoutes: routeManifest.getFlattenRoute(),
routesManifest: routesInfo.routes,
lazy,
});
}

// render template before webpack compile
const renderStart = new Date().getTime();
generator.render();
Expand Down
5 changes: 4 additions & 1 deletion packages/ice/src/getWatchEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ const getWatchEvents = (options: Options): WatchEvent[] => {
async (eventName: string) => {
if (eventName === 'add' || eventName === 'unlink' || eventName === 'change') {
const routesRenderData = await generateRoutesInfo(rootDir, routesConfig);
const { routeImports, routeDefinition } = getRoutesDefinition(routesRenderData.routes, lazyRoutes);
const { routeImports, routeDefinition } = getRoutesDefinition({
manifest: routesRenderData.routes,
lazy: lazyRoutes,
});
const stringifiedData = JSON.stringify(routesRenderData);
if (cache.get('routes') !== stringifiedData) {
cache.set('routes', stringifiedData);
Expand Down
22 changes: 19 additions & 3 deletions packages/ice/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,20 @@ function getFilePath(file: string) {
return formatPath(path.isAbsolute(file) ? file : `@/pages/${file}`.replace(new RegExp(`${path.extname(file)}$`), ''));
}

export function getRoutesDefinition(nestRouteManifest: NestedRouteManifest[], lazy = false, depth = 0) {
interface GetDefinationOptions {
manifest: NestedRouteManifest[];
lazy?: boolean;
depth?: number;
matchRoute?: (route: NestedRouteManifest) => boolean;
}

export function getRoutesDefinition(options: GetDefinationOptions) {
const { manifest, lazy = false, depth = 0, matchRoute = () => true } = options;
const routeImports: string[] = [];
const routeDefinition = nestRouteManifest.reduce((prev, route) => {
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);

Expand Down Expand Up @@ -112,7 +123,12 @@ export function getRoutesDefinition(nestRouteManifest: NestedRouteManifest[], la
routeProperties.push('layout: true,');
}
if (children) {
const res = getRoutesDefinition(children, lazy, depth + 1);
const res = getRoutesDefinition({
manifest: children,
lazy,
depth: depth + 1,
matchRoute,
});
routeImports.push(...res.routeImports);
routeProperties.push(`children: [${res.routeDefinition}]`);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ice/src/types/userConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export interface UserConfig {
/**
* bundle server code as a single file
*/
bundle?: boolean;
bundle?: boolean | 'page';
/**
* ignore file when bundle server code, module return empty when match
*/
Expand Down
8 changes: 6 additions & 2 deletions packages/ice/src/utils/generateEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,13 @@ const writeFile = async (file: string, content: string) => {
await fse.writeFile(file, content);
};

function formatFilePath(routePath: string, type: 'js' | 'html' | 'js.map'): string {
export function escapeRoutePath(str: string) {
// Win32 do not support file name start with ':' and '*'.
return routePath === '/' ? `index.${type}` : `${routePath.replace(/\/(:|\*)/g, '/$')}.${type}`;
return str.replace(/\/(:|\*)/g, '/$');
}

function formatFilePath(routePath: string, type: 'js' | 'html' | 'js.map'): string {
return routePath === '/' ? `index.${type}` : `${escapeRoutePath(routePath)}.${type}`;
}

async function generateFilePath(
Expand Down
17 changes: 17 additions & 0 deletions packages/ice/src/utils/getEntryPoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as path from 'path';
import { RUNTIME_TMP_DIR } from '../constant.js';
import getServerEntry from './getServerEntry.js';
import { formatServerEntry } from './multipleEntry.js';

function getEntryPoints(rootDir: string, routes: string[], mainEntry: string) {
const serverEntry: Record<string, string> = {};
routes.forEach((route) => {
serverEntry[`pages${route === '/' ? '/index' : route}`] =
path.join(rootDir, RUNTIME_TMP_DIR, formatServerEntry(route));
});
serverEntry.index = getServerEntry(rootDir, mainEntry);

return serverEntry;
}

export default getEntryPoints;
6 changes: 4 additions & 2 deletions packages/ice/src/utils/getServerCompilerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface Options {
serverCompileTask: ExtendsPluginAPI['serverCompileTask'];
ensureRoutesConfig: () => Promise<void>;
runtimeDefineVars: Record<string, string>;
entryPoints?: Record<string, string>;
}

function getServerCompilerPlugin(serverCompiler: ServerCompiler, options: Options) {
Expand All @@ -24,15 +25,16 @@ function getServerCompilerPlugin(serverCompiler: ServerCompiler, options: Option
serverCompileTask,
ensureRoutesConfig,
runtimeDefineVars,
entryPoints,
} = options;
const entryPoint = getServerEntry(rootDir, serverEntry);
const { ssg, ssr, server: { format } } = userConfig;
const isEsm = userConfig?.server?.format === 'esm';

return new ServerCompilerPlugin(
serverCompiler,
[
{
entryPoints: { index: entryPoint },
entryPoints: entryPoints || { index: getServerEntry(rootDir, serverEntry) },
outdir: path.join(outputDir, SERVER_OUTPUT_DIR),
splitting: isEsm,
format,
Expand Down
56 changes: 56 additions & 0 deletions packages/ice/src/utils/multipleEntry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import matchRoutes from '@ice/runtime/matchRoutes';
import type { NestedRouteManifest } from '@ice/route-manifest';
import type { CommandName } from 'build-scripts';
import { getRoutesDefinition } from '../routes.js';
import type Generator from '../service/runtimeGenerator.js';
import type { UserConfig } from '../types/userConfig.js';
import { escapeRoutePath } from './generateEntry.js';

interface Options {
renderRoutes: string[];
routesManifest: NestedRouteManifest[];
generator: Generator;
lazy: boolean;
}

export const multipleServerEntry = (userConfig: UserConfig, command: CommandName): boolean => {
return userConfig?.server?.bundle === 'page' &&
userConfig.server.format === 'cjs' &&
command === 'build';
};

export const formatRoutePath = (route: string) => {
return escapeRoutePath(route)
.replace(/^\//, '').replace(/\//g, '_');
};

export const formatServerEntry = (route: string) => {
return `server.entry.${formatRoutePath(route) || 'index'}.ts`;
};

export function renderMultiEntry(options: Options) {
const { renderRoutes, routesManifest, generator, lazy } = options;
renderRoutes.forEach((route) => {
const routeId = formatRoutePath(route);
generator.addRenderFile(
'core/entry.server.ts.ejs',
formatServerEntry(route),
{
routesFile: `./routes.${routeId}`,
},
);
// Generate route file for each route.
const matches = matchRoutes(routesManifest, route);
const { routeImports, routeDefinition } = getRoutesDefinition({
manifest: routesManifest,
lazy,
matchRoute: (routeItem) => {
return matches.some((match) => match.route.id === routeItem.id);
},
});
generator.addRenderFile('core/routes.tsx.ejs', `routes.${routeId}.tsx`, {
routeImports,
routeDefinition,
});
});
}
2 changes: 1 addition & 1 deletion packages/ice/templates/core/entry.server.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { RenderToPipeableStreamOptions } from 'react-dom/server';
// @ts-ignore
import assetsManifest from 'virtual:assets-manifest.json';
<% if (hydrate) {-%>
import createRoutes from './routes';
import createRoutes from '<%- routesFile %>';
<% } else { -%>
import routesManifest from './route-manifest.json';
<% } -%>
Expand Down

0 comments on commit 11a87dc

Please sign in to comment.