Skip to content

Commit

Permalink
Showing 36 changed files with 267 additions and 790 deletions.
10 changes: 10 additions & 0 deletions packages/ice/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 3.4.7

### Patch Changes

- d5c378b6: fix: reduce bundle size by remove runtime module
- 77155bab: feat: remove runtime code when loaders is not export
- Updated dependencies [d5c378b6]
- Updated dependencies [77155bab]
- @ice/runtime@1.4.5

## 3.4.6

### Patch Changes
4 changes: 2 additions & 2 deletions packages/ice/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ice/app",
"version": "3.4.6",
"version": "3.4.7",
"description": "provide scripts and configuration used by web framework ice",
"type": "module",
"main": "./esm/index.js",
@@ -49,7 +49,7 @@
"dependencies": {
"@ice/bundles": "0.2.6",
"@ice/route-manifest": "1.2.2",
"@ice/runtime": "^1.4.3",
"@ice/runtime": "^1.4.5",
"@ice/shared-config": "1.2.6",
"@ice/webpack-config": "1.1.13",
"@ice/rspack-config": "1.1.6",
4 changes: 1 addition & 3 deletions packages/ice/src/bundler/config/output.ts
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ async function buildCustomOutputs(
bundleOptions: Pick<BundlerOptions, 'userConfig' | 'appConfig' | 'routeManifest'>,
) {
const { userConfig, appConfig, routeManifest } = bundleOptions;
const { ssg, output: { distType, prependCode } } = userConfig;
const { ssg } = userConfig;
const routeType = appConfig?.router?.type;
const {
outputPaths = [],
@@ -51,8 +51,6 @@ async function buildCustomOutputs(
documentOnly: !ssg,
renderMode: ssg ? 'SSG' : undefined,
routeType: appConfig?.router?.type,
distType,
prependCode,
routeManifest,
});
if (routeType === 'memory' && userConfig?.routes?.injectInitialEntry) {
9 changes: 9 additions & 0 deletions packages/ice/src/constant.ts
Original file line number Diff line number Diff line change
@@ -70,6 +70,15 @@ export const RUNTIME_EXPORTS = [
'usePageLifecycle',
'unstable_useDocumentData',
'dynamic',
// Document API
'Meta',
'Title',
'Links',
'Scripts',
'FirstChunkCache',
'Data',
'Main',
'usePageAssets',
],
alias: {
usePublicAppContext: 'useAppContext',
65 changes: 22 additions & 43 deletions packages/ice/src/createService.ts
Original file line number Diff line number Diff line change
@@ -7,15 +7,12 @@ import { Context } from 'build-scripts';
import type { CommandArgs, CommandName } from 'build-scripts';
import type { Config } from '@ice/shared-config/types';
import type { AppConfig } from '@ice/runtime/types';
import fse from 'fs-extra';
import webpack from '@ice/bundles/compiled/webpack/index.js';
import type {
DeclarationData,
PluginData,
ExtendsPluginAPI,
TargetDeclarationData,
} from './types/index.js';
import { DeclarationType } from './types/index.js';
import Generator from './service/runtimeGenerator.js';
import { createServerCompiler } from './service/serverCompiler.js';
import createWatch from './service/watchSource.js';
@@ -41,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 hasDocument from './utils/hasDocument.js';

const require = createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -75,56 +73,32 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
let entryCode = 'render();';

const generatorAPI = {
addExport: (declarationData: Omit<DeclarationData, 'declarationType'>) => {
generator.addDeclaration('framework', {
...declarationData,
declarationType: DeclarationType.NORMAL,
});
addExport: (declarationData: DeclarationData) => {
generator.addDeclaration('framework', declarationData);
},
addTargetExport: (declarationData: Omit<TargetDeclarationData, 'declarationType'>) => {
generator.addDeclaration('framework', {
...declarationData,
declarationType: DeclarationType.TARGET,
});
addExportTypes: (declarationData: DeclarationData) => {
generator.addDeclaration('frameworkTypes', declarationData);
},
addExportTypes: (declarationData: Omit<DeclarationData, 'declarationType'>) => {
generator.addDeclaration('frameworkTypes', {
...declarationData,
declarationType: DeclarationType.NORMAL,
});
},
addRuntimeOptions: (declarationData: Omit<DeclarationData, 'declarationType'>) => {
generator.addDeclaration('runtimeOptions', {
...declarationData,
declarationType: DeclarationType.NORMAL,
});
addRuntimeOptions: (declarationData: DeclarationData) => {
generator.addDeclaration('runtimeOptions', declarationData);
},
removeRuntimeOptions: (removeSource: string | string[]) => {
generator.removeDeclaration('runtimeOptions', removeSource);
},
addRouteTypes: (declarationData: Omit<DeclarationData, 'declarationType'>) => {
generator.addDeclaration('routeConfigTypes', {
...declarationData,
declarationType: DeclarationType.NORMAL,
});
addRouteTypes: (declarationData: DeclarationData) => {
generator.addDeclaration('routeConfigTypes', declarationData);
},
addRenderFile: generator.addRenderFile,
addRenderTemplate: generator.addTemplateFiles,
addEntryCode: (callback: (originalCode: string) => string) => {
entryCode = callback(entryCode);
},
addEntryImportAhead: (declarationData: Pick<DeclarationData, 'source'>) => {
generator.addDeclaration('entry', {
...declarationData,
declarationType: DeclarationType.NORMAL,
});
generator.addDeclaration('entry', declarationData);
},
modifyRenderData: generator.modifyRenderData,
addDataLoaderImport: (declarationData: DeclarationData) => {
generator.addDeclaration('dataLoaderImport', {
...declarationData,
declarationType: DeclarationType.NORMAL,
});
generator.addDeclaration('dataLoaderImport', declarationData);
},
getExportList: (registerKey: string) => {
return generator.getExportList(registerKey);
@@ -239,7 +213,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt

// get userConfig after setup because of userConfig maybe modified by plugins
const { userConfig } = ctx;
const { routes: routesConfig, server, syntaxFeatures, polyfill, output: { distType } } = userConfig;
const { routes: routesConfig, server, syntaxFeatures, polyfill } = userConfig;

const coreEnvKeys = getCoreEnvKeys();

@@ -270,6 +244,8 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
// 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 loaderExports = hasExportAppData || Boolean(routesInfo.loaders);
const hasDataLoader = Boolean(userConfig.dataLoader) && loaderExports;
// add render data
generator.setRenderData({
...routesInfo,
@@ -286,16 +262,15 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
// Enable react-router for web as default.
enableRoutes: true,
entryCode,
jsOutput: distType.includes('javascript'),
hasDocument: fse.existsSync(path.join(rootDir, 'src/document.tsx')) || fse.existsSync(path.join(rootDir, 'src/document.jsx')) || fse.existsSync(path.join(rootDir, 'src/document.js')),
hasDocument: hasDocument(rootDir),
dataLoader: userConfig.dataLoader,
hasDataLoader,
routeImports,
routeDefinition,
});
dataCache.set('routes', JSON.stringify(routesInfo));
dataCache.set('hasExportAppData', hasExportAppData ? 'true' : '');

const hasDataLoader = Boolean(userConfig.dataLoader) && (hasExportAppData || Boolean(routesInfo.loaders));
// Render exports files if route component export dataLoader / pageConfig.
renderExportsTemplate(
{
@@ -378,8 +353,12 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
);

const appConfig: AppConfig = (await getAppConfig()).default;

updateRuntimeEnv(appConfig, { disableRouter });
updateRuntimeEnv(appConfig, {
disableRouter,
// The optimization for runtime size should only be enabled in production mode.
routesConfig: command !== 'build' || routesInfo.routesExports.length > 0,
dataLoader: command !== 'build' || loaderExports,
});

return {
run: async () => {
27 changes: 1 addition & 26 deletions packages/ice/src/plugins/web/index.ts
Original file line number Diff line number Diff line change
@@ -6,33 +6,8 @@ import { logger } from '../../utils/logger.js';

const plugin: Plugin = () => ({
name: 'plugin-web',
setup: ({ registerTask, onHook, context, generator }) => {
setup: ({ registerTask, onHook, context }) => {
const { commandArgs, command, userConfig } = context;

generator.addTargetExport({
specifier: [
'Meta',
'Title',
'Links',
'Scripts',
'FirstChunkCache',
'Data',
'Main',
'usePageAssets',
],
types: [
'MetaType',
'TitleType',
'LinksType',
'ScriptsType',
'FirstChunkCacheType',
'DataType',
'MainType',
],
source: '@ice/runtime',
target: 'web',
});

const removeExportExprs = ['serverDataLoader', 'staticDataLoader'];
// Remove dataLoader exports only when build in production
// and configure to generate data-loader.js.
17 changes: 11 additions & 6 deletions packages/ice/src/routes.ts
Original file line number Diff line number Diff line change
@@ -36,6 +36,15 @@ export async function generateRoutesInfo(
}
});

const routesExports = [];
const configExport = generateRouteConfig(routes, 'pageConfig', (str, imports) => {
routesExports.push(...imports);
return `${str}
export default {
${imports.map(([routeId, importKey]) => `'${routeId}': ${importKey},`).join('\n ')}
};`;
});

return {
routesCount,
routeManifest,
@@ -46,12 +55,8 @@ export default {
${imports.map(([routeId, importKey]) => `'${routeId}': ${importKey},`).join('\n ')}
};` : '';
}),
routesConfig: generateRouteConfig(routes, 'pageConfig', (str, imports) => {
return `${str}
export default {
${imports.map(([routeId, importKey]) => `'${routeId}': ${importKey},`).join('\n ')}
};`;
}),
routesConfig: configExport,
routesExports,
};
}

118 changes: 31 additions & 87 deletions packages/ice/src/service/runtimeGenerator.ts
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ import type {
RenderTemplate,
RenderData,
DeclarationData,
TargetDeclarationData,
Registration,
TemplateOptions,
} from '../types/generator.js';
@@ -37,76 +36,36 @@ interface Options {
templates?: (string | TemplateOptions)[];
}

function isDeclarationData(data: TargetDeclarationData | DeclarationData): data is DeclarationData {
return data.declarationType === 'normal';
}

function isTargetDeclarationData(data: TargetDeclarationData | DeclarationData): data is TargetDeclarationData {
return data.declarationType === 'target';
}

export function generateDeclaration(exportList: Array<TargetDeclarationData | DeclarationData>) {
const targetImportDeclarations: Array<string> = [];
export function generateDeclaration(exportList: DeclarationData[]) {
const importDeclarations: Array<string> = [];
const exportDeclarations: Array<string> = [];
const exportNames: Array<string> = [];
const variables: Map<string, string> = new Map();

let moduleId = 0;
exportList.forEach(data => {
// Deal with target.
if (isTargetDeclarationData(data)) {
const { specifier, source, target, types = [] } = data;
const isDefaultImport = !Array.isArray(specifier);
const specifiers = isDefaultImport ? [specifier] : specifier;
const arrTypes: Array<string> = Array.isArray(types) ? types : [types];

moduleId++;
const moduleName = `${target}Module${moduleId}`;
targetImportDeclarations.push(`if (import.meta.target === '${target}') {
${specifiers.map(item => `${item} = ${moduleName}.${item};`).join('\n ')}
}
`);

importDeclarations.push(`import ${isDefaultImport ? moduleName : `* as ${moduleName}`} from '${source}';`);

if (arrTypes.length) {
importDeclarations.push(`import type { ${arrTypes.join(', ')}} from '${source}';`);
}

specifiers.forEach((specifierStr, index) => {
if (!variables.has(specifierStr)) {
variables.set(specifierStr, arrTypes[index] || 'any');
const { specifier, source, alias, type } = data;
const isDefaultImport = !Array.isArray(specifier);
const specifiers = isDefaultImport ? [specifier] : specifier;
const symbol = type ? ';' : ',';

if (specifier) {
importDeclarations.push(`import ${type ? 'type ' : ''}${isDefaultImport ? specifier : `{ ${specifiers.map(specifierStr => ((alias && alias[specifierStr]) ? `${specifierStr} as ${alias[specifierStr]}` : specifierStr)).join(', ')} }`} from '${source}';`);

specifiers.forEach((specifierStr) => {
if (alias && alias[specifierStr]) {
exportDeclarations.push(`${alias[specifierStr]}${symbol}`);
exportNames.push(alias[specifierStr]);
} else {
exportDeclarations.push(`${specifierStr}${symbol}`);
exportNames.push(specifierStr);
}
});
} else if (isDeclarationData(data)) {
const { specifier, source, alias, type } = data;
const isDefaultImport = !Array.isArray(specifier);
const specifiers = isDefaultImport ? [specifier] : specifier;
const symbol = type ? ';' : ',';

if (specifier) {
importDeclarations.push(`import ${type ? 'type ' : ''}${isDefaultImport ? specifier : `{ ${specifiers.map(specifierStr => ((alias && alias[specifierStr]) ? `${specifierStr} as ${alias[specifierStr]}` : specifierStr)).join(', ')} }`} from '${source}';`);

specifiers.forEach((specifierStr) => {
if (alias && alias[specifierStr]) {
exportDeclarations.push(`${alias[specifierStr]}${symbol}`);
exportNames.push(alias[specifierStr]);
} else {
exportDeclarations.push(`${specifierStr}${symbol}`);
exportNames.push(specifierStr);
}
});
} else {
importDeclarations.push(`import '${source}';`);
}
} else {
importDeclarations.push(`import '${source}';`);
}
});

return {
targetImportStr: targetImportDeclarations.join('\n'),
importStr: importDeclarations.join('\n'),
targetExportStr: Array.from(variables.keys()).join(',\n '),
/**
* Add two whitespace character in order to get the formatted code. For example:
* export {
@@ -116,39 +75,30 @@ export function generateDeclaration(exportList: Array<TargetDeclarationData | De
*/
exportStr: exportDeclarations.join('\n '),
exportNames,
variablesStr: Array.from(variables.entries()).map(item => `let ${item[0]}: ${item[1]};`).join('\n'),
};
}

export function checkExportData(
currentList: (DeclarationData | TargetDeclarationData)[],
exportData: (DeclarationData | TargetDeclarationData) | (DeclarationData | TargetDeclarationData)[],
currentList: DeclarationData[],
exportData: DeclarationData | DeclarationData[],
apiName: string,
) {
(Array.isArray(exportData) ? exportData : [exportData]).forEach((data) => {
const exportNames = (Array.isArray(data.specifier) ? data.specifier : [data.specifier]).map((specifierStr) => {
if (isDeclarationData(data)) {
return data?.alias?.[specifierStr] || specifierStr;
} else {
return specifierStr;
}
return data?.alias?.[specifierStr] || specifierStr;
});
currentList.forEach((item) => {
if (isTargetDeclarationData(item)) return;

if (isDeclarationData(item)) {
const { specifier, alias, source } = item;
const { specifier, alias, source } = item;

// check exportName and specifier
const currentExportNames = (Array.isArray(specifier) ? specifier : [specifier]).map((specifierStr) => {
return alias?.[specifierStr] || specifierStr || source;
});
// check exportName and specifier
const currentExportNames = (Array.isArray(specifier) ? specifier : [specifier]).map((specifierStr) => {
return alias?.[specifierStr] || specifierStr || source;
});

if (currentExportNames.some((name) => exportNames.includes(name))) {
logger.error('specifier:', specifier, 'alias:', alias);
logger.error('duplicate with', data);
throw new Error(`duplicate export data added by ${apiName}`);
}
if (currentExportNames.some((name) => exportNames.includes(name))) {
logger.error('specifier:', specifier, 'alias:', alias);
logger.error('duplicate with', data);
throw new Error(`duplicate export data added by ${apiName}`);
}
});
});
@@ -253,18 +203,12 @@ export default class Generator {
importStr,
exportStr,
exportNames,
targetExportStr,
targetImportStr,
variablesStr,
} = generateDeclaration(exportList);
const [importStrKey, exportStrKey, targetImportStrKey, targetExportStrKey] = dataKeys;
const [importStrKey, exportStrKey] = dataKeys;
return {
[importStrKey]: importStr,
[exportStrKey]: exportStr,
exportNames,
variablesStr,
[targetImportStrKey]: targetImportStr,
[targetExportStrKey]: targetExportStr,
};
};

204 changes: 0 additions & 204 deletions packages/ice/src/service/webpackCompiler.ts

This file was deleted.

14 changes: 0 additions & 14 deletions packages/ice/src/types/generator.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
export enum DeclarationType {
NORMAL = 'normal',
TARGET = 'target',
}

export interface DeclarationData {
specifier?: string | string[];
source: string;
type?: boolean;
alias?: Record<string, string>;
declarationType?: DeclarationType;
}

export interface TargetDeclarationData {
specifier: string | string[];
source: string;
target: string;
types?: string | string[];
declarationType?: DeclarationType;
}

export type RenderData = Record<string, any>;
6 changes: 2 additions & 4 deletions packages/ice/src/types/plugin.ts
Original file line number Diff line number Diff line change
@@ -7,17 +7,16 @@ import type { Config } from '@ice/shared-config/types';
import type { AppConfig, AssetsManifest } from '@ice/runtime/types';
import type ServerCompileTask from '../utils/ServerCompileTask.js';
import type { CreateLogger } from '../utils/logger.js';
import type { DeclarationData, TargetDeclarationData, AddRenderFile, AddTemplateFiles, ModifyRenderData, AddDataLoaderImport, Render } from './generator.js';
import type { DeclarationData, AddRenderFile, AddTemplateFiles, ModifyRenderData, AddDataLoaderImport, Render } from './generator.js';

export type { CreateLoggerReturnType } from '../utils/logger.js';

type AddExport = (exportData: DeclarationData) => void;
type AddTargetExport = (exportData: TargetDeclarationData) => void;
type AddEntryCode = (callback: (code: string) => string) => void;
type AddEntryImportAhead = (exportData: Pick<DeclarationData, 'source'>) => void;
type RemoveExport = (removeSource: string | string[]) => void;
type EventName = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
type GetExportList = (key: string, target?: string) => (DeclarationData | TargetDeclarationData)[];
type GetExportList = (key: string, target?: string) => DeclarationData[];

type ServerCompilerBuildOptions = Pick<
esbuild.BuildOptions,
@@ -138,7 +137,6 @@ export interface ExtendsPluginAPI {
registerTask: RegisterTask<Config>;
generator: {
addExport: AddExport;
addTargetExport: AddTargetExport;
addExportTypes: AddExport;
addRuntimeOptions: AddExport;
removeRuntimeOptions: RemoveExport;
8 changes: 8 additions & 0 deletions packages/ice/src/types/userConfig.ts
Original file line number Diff line number Diff line change
@@ -58,7 +58,15 @@ export interface UserConfig {
abortcontroller?: boolean | string;
};
output?: {
/**
* @deprecated
* output. distType is deprecated, it will be removed in the future.
*/
distType: Array<DistType> | DistType;
/**
* @deprecated
* output.prependCode is deprecated, it will be removed in the future.
*/
prependCode?: string;
};
/**
47 changes: 4 additions & 43 deletions packages/ice/src/utils/generateEntry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as path from 'path';
import fse from 'fs-extra';
import type { ServerContext, RenderMode, AppConfig, DistType } from '@ice/runtime';
import type { UserConfig } from '../types/userConfig.js';
import type { ServerContext, RenderMode, AppConfig } from '@ice/runtime';
import dynamicImport from './dynamicImport.js';
import { logger } from './logger.js';
import type RouteManifest from './routeManifest.js';
@@ -13,8 +12,6 @@ interface Options {
documentOnly: boolean;
routeType: AppConfig['router']['type'];
renderMode?: RenderMode;
distType: UserConfig['output']['distType'];
prependCode: string;
routeManifest: RouteManifest;
}

@@ -30,13 +27,10 @@ export default async function generateEntry(options: Options): Promise<EntryResu
documentOnly,
renderMode,
routeType,
prependCode = '',
routeManifest,
} = options;

const distType = typeof options.distType === 'string' ? [options.distType] : options.distType;

let serverEntry;
let serverEntry: string;
try {
serverEntry = await dynamicImport(entry);
} catch (error) {
@@ -51,9 +45,7 @@ export default async function generateEntry(options: Options): Promise<EntryResu
const routePath = paths[i];
const {
htmlOutput,
jsOutput,
sourceMap,
} = await renderEntry({ routePath, serverEntry, documentOnly, renderMode, distType, prependCode });
} = await renderEntry({ routePath, serverEntry, documentOnly, renderMode });
const generateOptions = { rootDir, routePath, outputDir };
if (htmlOutput) {
const path = await generateFilePath({ ...generateOptions, type: 'html' });
@@ -63,24 +55,6 @@ export default async function generateEntry(options: Options): Promise<EntryResu
);
outputPaths.push(path);
}

if (sourceMap) {
const path = await generateFilePath({ ...generateOptions, type: 'js.map' });
await writeFile(
path,
sourceMap,
);
outputPaths.push(path);
}

if (jsOutput) {
const path = await generateFilePath({ ...generateOptions, type: 'js' });
await writeFile(
path,
jsOutput,
);
outputPaths.push(path);
}
}

return {
@@ -124,42 +98,29 @@ async function renderEntry(
routePath,
serverEntry,
documentOnly,
distType = ['html'],
prependCode = '',
renderMode,
}: {
routePath: string;
serverEntry: any;
documentOnly: boolean;
distType?: DistType;
renderMode?: RenderMode;
prependCode?: string;
},
) {
const serverContext: ServerContext = {
req: {
url: routePath,
} as any,
};

// renderToEntry exported when disType includes javascript .
const render = distType.includes('javascript') ? serverEntry.renderToEntry : serverEntry.renderToHTML;
const {
value,
jsOutput,
sourceMap,
} = await render(serverContext, {
} = await serverEntry.renderToHTML(serverContext, {
renderMode,
documentOnly,
routePath,
serverOnlyBasename: '/',
distType,
prependCode,
});

return {
htmlOutput: value,
jsOutput,
sourceMap,
};
}
9 changes: 9 additions & 0 deletions packages/ice/src/utils/hasDocument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as path from 'path';
import fg from 'fast-glob';

export default function hasDocument(rootDir: string) {
const document = fg.sync('document.{tsx,ts,jsx.js}', {
cwd: path.join(rootDir, 'src'),
});
return document.length > 0;
}
28 changes: 24 additions & 4 deletions packages/ice/src/utils/runtimeEnv.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ export interface Envs {
}
interface EnvOptions {
disableRouter: boolean;
routesConfig: boolean;
dataLoader: boolean;
}

const expandedEnvs = {};
@@ -50,24 +52,42 @@ export async function setEnv(
process.env.ICE_CORE_ROUTER = 'true';
process.env.ICE_CORE_ERROR_BOUNDARY = 'true';
process.env.ICE_CORE_INITIAL_DATA = 'true';
// Set to false for compatibility with the old version.
process.env.ICE_CORE_REMOVE_DATA_LOADER = 'false';

process.env.ICE_CORE_REMOVE_ROUTES_CONFIG = 'false';

// set ssr and ssg env to false, for remove dead code in CSR.
process.env.ICE_CORE_SSG = 'false';
process.env.ICE_CORE_SSR = 'false';
}

export const updateRuntimeEnv = (appConfig: AppConfig, options: EnvOptions) => {
const { disableRouter } = options;
const { disableRouter, dataLoader, routesConfig } = options;
if (!appConfig?.app?.errorBoundary) {
process.env['ICE_CORE_ERROR_BOUNDARY'] = 'false';
process.env.ICE_CORE_ERROR_BOUNDARY = 'false';
}
if (disableRouter) {
process.env['ICE_CORE_ROUTER'] = 'false';
process.env.ICE_CORE_ROUTER = 'false';
}
if (!dataLoader) {
process.env.ICE_CORE_REMOVE_DATA_LOADER = 'true';
}
if (!routesConfig) {
process.env.ICE_CORE_REMOVE_ROUTES_CONFIG = 'true';
}
};

export function getCoreEnvKeys() {
return ['ICE_CORE_MODE', 'ICE_CORE_ROUTER', 'ICE_CORE_ERROR_BOUNDARY', 'ICE_CORE_INITIAL_DATA', 'ICE_CORE_DEV_PORT'];
return [
'ICE_CORE_MODE',
'ICE_CORE_ROUTER',
'ICE_CORE_ERROR_BOUNDARY',
'ICE_CORE_INITIAL_DATA',
'ICE_CORE_DEV_PORT',
'ICE_CORE_REMOVE_ROUTES_CONFIG',
'ICE_CORE_REMOVE_DATA_LOADER',
];
}

export function getExpandedEnvs(): Record<string, string> {
7 changes: 4 additions & 3 deletions packages/ice/templates/core/entry.client.tsx.ejs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import * as app from '@/app';
import createRoutes from './routes';
<% } -%>
<%- runtimeOptions.imports %>
<% if (dataLoaderImport.imports) {-%><%-dataLoaderImport.imports%><% } -%>
<% if (dataLoaderImport.imports && hasDataLoader) {-%><%-dataLoaderImport.imports%><% } -%>
import type { RunClientAppOptions } from '@ice/runtime';

const getRouterBasename = () => {
@@ -19,7 +19,7 @@ const getRouterBasename = () => {
// Otherwise chunk of route component will pack @ice/jsx-runtime and depend on framework bundle.
const App = <></>;

<% if (!dataLoaderImport.imports) {-%>
<% if (!dataLoaderImport.imports && hasDataLoader) {-%>
let dataLoaderFetcher = (options) => {
return window.fetch(options.url, options);
}
@@ -39,8 +39,9 @@ const renderOptions: RunClientAppOptions = {
basename: getRouterBasename(),
hydrate: <%- hydrate %>,
memoryRouter: <%- memoryRouter || false %>,
<% if (hasDataLoader) { -%>
dataLoaderFetcher,
dataLoaderDecorator,
dataLoaderDecorator,<% } -%>
runtimeOptions: {
<% if (runtimeOptions.exports) { -%>
<%- runtimeOptions.exports %>
13 changes: 1 addition & 12 deletions packages/ice/templates/core/entry.server.ts.ejs
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import * as Document from '@/document';
<% } else { -%>
import * as Document from './document';
<% } -%>
import type { RenderMode, DistType } from '@ice/runtime';
import type { RenderMode } from '@ice/runtime';
import type { RenderToPipeableStreamOptions } from 'react-dom/server';
// @ts-ignore
import assetsManifest from 'virtual:assets-manifest.json';
@@ -49,7 +49,6 @@ interface RenderOptions {
serverOnlyBasename?: string;
routePath?: string;
disableFallback?: boolean;
distType?: DistType;
publicPath?: string;
serverData?: any;
streamOptions?: RenderToPipeableStreamOptions;
@@ -71,16 +70,6 @@ export async function renderToResponse(requestContext, options: RenderOptions =
return runtime.renderToResponse(requestContext, mergedOptions);
}

<% if (jsOutput) { -%>
export async function renderToEntry(requestContext, options: RenderOptions = {}) {
const { renderMode = 'SSR' } = options;
setRuntimeEnv(renderMode);
const mergedOptions = mergeOptions(options);
return await runtime.renderToEntry(requestContext, mergedOptions);
}
<% } -%>

function mergeOptions(options) {
const { renderMode = 'SSR', basename, publicPath } = options;

4 changes: 0 additions & 4 deletions packages/ice/templates/core/index.ts.ejs
Original file line number Diff line number Diff line change
@@ -3,10 +3,6 @@ import '<%= globalStyle %>'
<% } -%>
import { definePageConfig, defineRunApp } from './type-defines';
<%- framework.imports %>

<%- framework.variablesStr %>
<%- framework.targetImport %>

export {
definePageConfig,
defineRunApp,
7 changes: 0 additions & 7 deletions packages/ice/tests/generator.test.ts
Original file line number Diff line number Diff line change
@@ -3,15 +3,13 @@
*/
import { expect, it, describe } from 'vitest';
import { generateDeclaration, checkExportData, removeDeclarations } from '../src/service/runtimeGenerator';
import { DeclarationType } from '../src/types/generator';

describe('generateDeclaration', () => {
it('basic usage', () => {
const { importStr, exportStr } = generateDeclaration([{
source: 'react-router',
specifier: 'Router',
type: false,
declarationType: DeclarationType.NORMAL,
}]);
expect(importStr).toBe('import Router from \'react-router\';');
expect(exportStr).toBe('Router,');
@@ -21,7 +19,6 @@ describe('generateDeclaration', () => {
source: 'react-router',
specifier: 'Router',
type: true,
declarationType: DeclarationType.NORMAL,
}]);
expect(importStr).toBe('import type Router from \'react-router\';');
expect(exportStr).toBe('Router;');
@@ -30,7 +27,6 @@ describe('generateDeclaration', () => {
const { importStr, exportStr } = generateDeclaration([{
source: 'react-router',
specifier: ['Switch', 'Route'],
declarationType: DeclarationType.NORMAL,
}]);
expect(importStr).toBe('import { Switch, Route } from \'react-router\';');
expect(exportStr).toBe(['Switch,', 'Route,'].join('\n '));
@@ -43,7 +39,6 @@ describe('generateDeclaration', () => {
alias: {
Helmet: 'Head',
},
declarationType: DeclarationType.NORMAL,
}]);
expect(importStr).toBe('import { Helmet as Head } from \'react-helmet\';');
expect(exportStr).toBe('Head,');
@@ -53,11 +48,9 @@ describe('generateDeclaration', () => {
const defaultExportData = [{
source: 'react-router',
specifier: ['Switch', 'Route'],
declarationType: DeclarationType.NORMAL,
}, {
source: 'react-helmet',
specifier: 'Helmet',
declarationType: DeclarationType.NORMAL,
}];

describe('checkExportData', () => {
4 changes: 2 additions & 2 deletions packages/plugin-i18n/package.json
Original file line number Diff line number Diff line change
@@ -56,8 +56,8 @@
"webpack-dev-server": "4.15.0"
},
"peerDependencies": {
"@ice/app": "^3.4.6",
"@ice/runtime": "^1.4.3"
"@ice/app": "^3.4.7",
"@ice/runtime": "^1.4.5"
},
"publishConfig": {
"access": "public"
3 changes: 2 additions & 1 deletion packages/rax-compat/src/create-class.ts
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ function collateMixins(mixins: any) {
}

for (let key in mixin) {
// eslint-disable-next-line no-prototype-builtins
if (mixin.hasOwnProperty(key) && key !== 'mixins') {
(keyed[key] || (keyed[key] = [])).push(mixin[key]);
}
@@ -46,7 +47,7 @@ function flattenHooks(key: string, hooks: Array<any>) {
let ret;
for (let i = 0; i < hooks.length; i++) {
// @ts-ignore
let r = hooks[i].apply(this, arguments);
let r = hooks[i].apply(this, arguments); // eslint-disable-line prefer-rest-params
if (r) {
if (!ret) ret = {};
Object.assign(ret, r);
7 changes: 7 additions & 0 deletions packages/runtime/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @ice/runtime

## 1.4.5

### Patch Changes

- d5c378b6: fix: reduce bundle size by remove runtime module
- 77155bab: feat: remove runtime code when loaders is not export

## 1.4.4

- chore: add ts type for `@ice/runtime/data-loader`
1 change: 1 addition & 0 deletions packages/runtime/document.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './esm/Document';
11 changes: 4 additions & 7 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ice/runtime",
"version": "1.4.4",
"version": "1.4.5",
"description": "Runtime module for ice.js",
"type": "module",
"types": "./esm/index.d.ts",
@@ -21,7 +21,8 @@
"./react": "./esm/react.js",
"./react/jsx-runtime": "./esm/jsx-runtime.js",
"./react/jsx-dev-runtime": "./esm/jsx-dev-runtime.js",
"./data-loader": "./esm/dataLoader.js"
"./data-loader": "./esm/dataLoader.js",
"./document": "./esm/Document.js"
},
"files": [
"esm",
@@ -57,13 +58,9 @@
"@ice/shared": "^1.0.2",
"@remix-run/router": "1.14.2",
"abortcontroller-polyfill": "1.7.5",
"ejs": "^3.1.6",
"fs-extra": "^10.0.0",
"history": "^5.3.0",
"htmlparser2": "^8.0.1",
"react-router-dom": "6.21.3",
"semver": "^7.4.0",
"source-map": "^0.7.4"
"semver": "^7.4.0"
},
"peerDependencies": {
"react": "^18.1.0",
18 changes: 10 additions & 8 deletions packages/runtime/src/appData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AppExport, AppData, RequestContext } from './types.js';
import type { AppExport, AppData, RequestContext, Loader } from './types.js';
import { callDataLoader } from './dataLoader.js';

/**
@@ -18,15 +18,17 @@ async function getAppData(appExport: AppExport, requestContext?: RequestContext)
return null;
}

let loader;
if (process.env.ICE_CORE_REMOVE_DATA_LOADER !== 'true') {
let loader: Loader;

if (typeof appDataLoaderConfig === 'function' || Array.isArray(appDataLoaderConfig)) {
loader = appDataLoaderConfig;
} else {
loader = appDataLoaderConfig.loader;
}
if (typeof appDataLoaderConfig === 'function' || Array.isArray(appDataLoaderConfig)) {
loader = appDataLoaderConfig;
} else {
loader = appDataLoaderConfig.loader;
}

return await callDataLoader(loader, requestContext);
return await callDataLoader(loader, requestContext);
}
}

export {
2 changes: 1 addition & 1 deletion packages/runtime/src/index.server.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { renderToResponse, renderToHTML, renderToEntry } from './runServerApp.js';
export { renderToResponse, renderToHTML } from './runServerApp.js';
export * from './index.js';
2 changes: 0 additions & 2 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ import type {
AppProvider,
RouteWrapper,
RenderMode,
DistType,
Loader,
RouteWrapperConfig,
} from './types.js';
@@ -160,7 +159,6 @@ export type {
AppProvider,
RouteWrapper,
RenderMode,
DistType,
Loader,
RunClientAppOptions,
MetaType,
96 changes: 0 additions & 96 deletions packages/runtime/src/renderHTMLToJS.tsx

This file was deleted.

162 changes: 89 additions & 73 deletions packages/runtime/src/routes.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,16 @@ import React, { Suspense } from 'react';
import { useRouteError, defer, Await as ReactRouterAwait } from 'react-router-dom';
// eslint-disable-next-line camelcase
import type { UNSAFE_DeferredData, LoaderFunctionArgs } from '@remix-run/router';
import type { RouteItem, RouteModules, RenderMode, RequestContext, ComponentModule, DataLoaderConfig } from './types.js';
import type {
RouteItem,
RouteModules,
RenderMode,
RequestContext,
ComponentModule,
DataLoaderConfig,
DataLoaderOptions,
Loader,
} from './types.js';
import RouteWrapper from './RouteWrapper.js';
import { useAppContext } from './AppContext.js';
import { callDataLoader } from './dataLoader.js';
@@ -145,104 +154,111 @@ function getClientLoaderContext(url: string): RequestContext | null {
}

export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction {
let dataLoaderConfig: DataLoaderConfig;
const { dataLoader, pageConfig, staticDataLoader, serverDataLoader } = options.module;
const { requestContext: defaultRequestContext, renderMode, routeId } = options;
const globalLoader = (typeof window !== 'undefined' && (window as any).__ICE_DATA_LOADER__) ? (window as any).__ICE_DATA_LOADER__ : null;

let dataLoaderConfig: DataLoaderConfig;
if (globalLoader) {
dataLoaderConfig = globalLoader.getLoader(routeId);
} else if (renderMode === 'SSG') {
dataLoaderConfig = staticDataLoader;
} else if (renderMode === 'SSR') {
dataLoaderConfig = serverDataLoader || dataLoader;
} else {
dataLoaderConfig = dataLoader;
if (process.env.ICE_CORE_REMOVE_DATA_LOADER !== 'true') {
if (globalLoader) {
dataLoaderConfig = globalLoader.getLoader(routeId);
} else if (renderMode === 'SSG') {
dataLoaderConfig = staticDataLoader;
} else if (renderMode === 'SSR') {
dataLoaderConfig = serverDataLoader || dataLoader;
} else {
dataLoaderConfig = dataLoader;
}
}

if (!dataLoaderConfig) {
return async () => {
const loaderData = {
pageConfig: pageConfig ? pageConfig({}) : {},
};
if (import.meta.renderer === 'client') {
if (import.meta.renderer === 'client' && process.env.ICE_CORE_REMOVE_ROUTES_CONFIG !== 'true') {
await updateRoutesConfig(loaderData);
}
return loaderData;
};
}

let loader;
let loaderOptions;

// Compat dataLoaderConfig not return by defineDataLoader.
if (typeof dataLoaderConfig === 'function' || Array.isArray(dataLoaderConfig)) {
loader = dataLoaderConfig;
} else {
loader = dataLoaderConfig.loader;
loaderOptions = dataLoaderConfig.options;
}
// if ICE_CORE_REMOVE_DATA_LOADER is true, dataLoaderConfig should be null and it already return above.
if (process.env.ICE_CORE_REMOVE_DATA_LOADER !== 'true') {
let loader: Loader;
let loaderOptions: DataLoaderOptions;

const getData = (requestContext: RequestContext) => {
let routeData: any;
if (globalLoader) {
routeData = globalLoader.getData(routeId, { renderMode, requestContext });
// Compat dataLoaderConfig not return by defineDataLoader.
if (typeof dataLoaderConfig === 'function' || Array.isArray(dataLoaderConfig)) {
loader = dataLoaderConfig;
} else {
routeData = callDataLoader(loader, requestContext);
loader = dataLoaderConfig.loader;
loaderOptions = dataLoaderConfig.options;
}
return routeData;
};

// Async dataLoader.
if (loaderOptions?.defer) {
if (process.env.ICE_CORE_ROUTER === 'true') {
return async (params) => {
const promise = getData(import.meta.renderer === 'client' ? getClientLoaderContext(params.request.url) : defaultRequestContext);

return defer({
data: promise,
// Call pageConfig without data.
pageConfig: pageConfig ? pageConfig({}) : {},
});
};
} else {
throw new Error('DataLoader: defer is not supported in single router mode.');
}
}
// Await dataLoader before render.
return async (params) => {
const result = getData(import.meta.renderer === 'client' && params ? getClientLoaderContext(params.request.url) : defaultRequestContext);
let routeData;
try {
if (Array.isArray(result)) {
routeData = await Promise.all(result);
} else if (result instanceof Promise) {
routeData = await result;
} else {
routeData = result;
const getData = (requestContext: RequestContext) => {
let routeData: any;
if (process.env.ICE_CORE_REMOVE_DATA_LOADER !== 'true') {
if (globalLoader) {
routeData = globalLoader.getData(routeId, { renderMode, requestContext });
} else {
routeData = callDataLoader(loader, requestContext);
}
}
} catch (error) {
if (import.meta.renderer === 'client') {
console.error('DataLoader: getData error.\n', error);
routeData = {
message: 'DataLoader: getData error.',
error,
return routeData;
};

// Async dataLoader.
if (loaderOptions?.defer) {
if (process.env.ICE_CORE_ROUTER === 'true') {
return async (params) => {
const promise = getData(import.meta.renderer === 'client' ? getClientLoaderContext(params.request.url) : defaultRequestContext);

return defer({
data: promise,
// Call pageConfig without data.
pageConfig: pageConfig ? pageConfig({}) : {},
});
};
} else {
// Throw to trigger downgrade.
throw error;
throw new Error('DataLoader: defer is not supported in single router mode.');
}
}
// Await dataLoader before render.
return async (params) => {
const result = getData(import.meta.renderer === 'client' && params ? getClientLoaderContext(params.request.url) : defaultRequestContext);
let routeData;
try {
if (Array.isArray(result)) {
routeData = await Promise.all(result);
} else if (result instanceof Promise) {
routeData = await result;
} else {
routeData = result;
}
} catch (error) {
if (import.meta.renderer === 'client') {
console.error('DataLoader: getData error.\n', error);
routeData = {
message: 'DataLoader: getData error.',
error,
};
} else {
// Throw to trigger downgrade.
throw error;
}
}

const routeConfig = pageConfig ? pageConfig({ data: routeData }) : {};
const loaderData = {
data: routeData,
pageConfig: routeConfig,
const routeConfig = pageConfig ? pageConfig({ data: routeData }) : {};
const loaderData = {
data: routeData,
pageConfig: routeConfig,
};
// Update routes config when render mode is CSR.
if (import.meta.renderer === 'client' && process.env.ICE_CORE_REMOVE_ROUTES_CONFIG !== 'true') {
await updateRoutesConfig(loaderData);
}
return loaderData;
};
// Update routes config when render mode is CSR.
if (import.meta.renderer === 'client') {
await updateRoutesConfig(loaderData);
}
return loaderData;
};
}
}
6 changes: 4 additions & 2 deletions packages/runtime/src/runClientApp.tsx
Original file line number Diff line number Diff line change
@@ -101,8 +101,10 @@ export default async function runClientApp(options: RunClientAppOptions) {
await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean));
}

dataLoaderFetcher && setFetcher(dataLoaderFetcher);
dataLoaderDecorator && setDecorator(dataLoaderDecorator);
if (process.env.ICE_CORE_REMOVE_DATA_LOADER !== 'true') {
dataLoaderFetcher && setFetcher(dataLoaderFetcher);
dataLoaderDecorator && setDecorator(dataLoaderDecorator);
}

if (!appData) {
appData = await getAppData(app, requestContext);
39 changes: 0 additions & 39 deletions packages/runtime/src/runServerApp.tsx
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@ import getRequestContext from './requestContext.js';
import matchRoutes from './matchRoutes.js';
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
import ServerRouter from './ServerRouter.js';
import { renderHTMLToJS } from './renderHTMLToJS.js';
import addLeadingSlash from './utils/addLeadingSlash.js';

export interface RenderOptions {
@@ -53,8 +52,6 @@ export interface RenderOptions {
[key: string]: PageConfig;
};
runtimeOptions?: Record<string, any>;
distType?: Array<'html' | 'javascript'>;
prependCode?: string;
serverData?: any;
streamOptions?: RenderToPipeableStreamOptions;
}
@@ -70,42 +67,6 @@ interface Response {
headers?: Record<string, string>;
}

/**
* Render and send the result with both entry bundle and html.
*/
export async function renderToEntry(
requestContext: ServerContext,
renderOptions: RenderOptions,
) {
const result = await renderToHTML(requestContext, renderOptions);
const { value } = result;

let jsOutput;
let sourceMap;
const {
distType = ['html'],
prependCode = '',
} = renderOptions;
if (value && distType.includes('javascript')) {
const res = await renderHTMLToJS(value, {
prependCode,
});
jsOutput = res.jsOutput;
sourceMap = res.sourceMap;
}

let htmlOutput;
if (distType.includes('html')) {
htmlOutput = result;
}

return {
...htmlOutput,
jsOutput,
sourceMap,
};
}

/**
* Render and return the result as html string.
*/
7 changes: 6 additions & 1 deletion packages/runtime/src/singleRouter.tsx
Original file line number Diff line number Diff line change
@@ -305,7 +305,12 @@ export const useRevalidator = () => {
throw new Error('useRevalidator is not supported in single router mode');
};

export const getSingleRoute = async (routes: RouteItem[], basename: string, location: Partial<Location> | string, routeModuleCache = {}) => {
export const getSingleRoute = async (
routes: RouteItem[],
basename: string,
location: Partial<Location> | string,
routeModuleCache = {},
) => {
const matchedRoutes = matchRoutes(routes, location, basename);
const routeModules = await loadRouteModules(matchedRoutes.map(({ route }) => route), routeModuleCache);
let loaders = [];
62 changes: 0 additions & 62 deletions packages/runtime/src/sourcemap.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/runtime/src/types.ts
Original file line number Diff line number Diff line change
@@ -300,8 +300,6 @@ export interface RouteMatch {

export type RenderMode = 'SSR' | 'SSG' | 'CSR';

export type DistType = Array<'html' | 'javascript'>;

declare global {
interface ImportMeta {
// The build target for ice.js
19 changes: 0 additions & 19 deletions packages/runtime/templates/js-entry.js.ejs

This file was deleted.

15 changes: 2 additions & 13 deletions pnpm-lock.yaml

0 comments on commit 5e3bed3

Please sign in to comment.