mirror of https://github.com/alibaba/ice.git
Feat: optimize runtime size (#6848)
* feat: optimize runtime size * fix: log error instead of throw * chore: fix test case * chore: clean up useless params
This commit is contained in:
parent
ee1496261e
commit
44ef63fcf1
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,38 +73,23 @@ 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,
|
||||
});
|
||||
addTargetExport: () => {
|
||||
logger.error('`addTargetExport` is deprecated, please use `addExport` instead.');
|
||||
},
|
||||
addExportTypes: (declarationData: Omit<DeclarationData, 'declarationType'>) => {
|
||||
generator.addDeclaration('frameworkTypes', {
|
||||
...declarationData,
|
||||
declarationType: DeclarationType.NORMAL,
|
||||
});
|
||||
addExportTypes: (declarationData: DeclarationData) => {
|
||||
generator.addDeclaration('frameworkTypes', declarationData);
|
||||
},
|
||||
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,
|
||||
|
|
@ -114,17 +97,11 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
|
|||
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 +216,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();
|
||||
|
||||
|
|
@ -286,8 +263,7 @@ 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,
|
||||
routeImports,
|
||||
routeDefinition,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
const { specifier, source, alias, type } = data;
|
||||
const isDefaultImport = !Array.isArray(specifier);
|
||||
const specifiers = isDefaultImport ? [specifier] : specifier;
|
||||
const symbol = type ? ';' : ',';
|
||||
|
||||
moduleId++;
|
||||
const moduleName = `${target}Module${moduleId}`;
|
||||
targetImportDeclarations.push(`if (import.meta.target === '${target}') {
|
||||
${specifiers.map(item => `${item} = ${moduleName}.${item};`).join('\n ')}
|
||||
}
|
||||
`);
|
||||
if (specifier) {
|
||||
importDeclarations.push(`import ${type ? 'type ' : ''}${isDefaultImport ? specifier : `{ ${specifiers.map(specifierStr => ((alias && alias[specifierStr]) ? `${specifierStr} as ${alias[specifierStr]}` : specifierStr)).join(', ')} }`} from '${source}';`);
|
||||
|
||||
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');
|
||||
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;
|
||||
const { specifier, alias, source } = item;
|
||||
|
||||
if (isDeclarationData(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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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,11 @@ export interface ExtendsPluginAPI {
|
|||
registerTask: RegisterTask<Config>;
|
||||
generator: {
|
||||
addExport: AddExport;
|
||||
addTargetExport: AddTargetExport;
|
||||
/**
|
||||
* @deprecated
|
||||
* API will be removed in the next major version.
|
||||
*/
|
||||
addTargetExport: () => void;
|
||||
addExportTypes: AddExport;
|
||||
addRuntimeOptions: AddExport;
|
||||
removeRuntimeOptions: RemoveExport;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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,16 +98,12 @@ 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 = {
|
||||
|
|
@ -141,25 +111,16 @@ async function renderEntry(
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@ import '<%= globalStyle %>'
|
|||
<% } -%>
|
||||
import { definePageConfig, defineRunApp } from './type-defines';
|
||||
<%- framework.imports %>
|
||||
|
||||
<%- framework.variablesStr %>
|
||||
<%- framework.targetImport %>
|
||||
|
||||
export {
|
||||
definePageConfig,
|
||||
defineRunApp,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
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', () => {
|
||||
|
|
@ -11,7 +10,6 @@ describe('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', () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from './esm/Document';
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export { renderToResponse, renderToHTML, renderToEntry } from './runServerApp.js';
|
||||
export { renderToResponse, renderToHTML } from './runServerApp.js';
|
||||
export * from './index.js';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import * as htmlparser2 from 'htmlparser2';
|
||||
import ejs from 'ejs';
|
||||
import fse from 'fs-extra';
|
||||
import __createElement from './domRender.js';
|
||||
import { generateSourceMap } from './sourcemap.js';
|
||||
|
||||
let dirname;
|
||||
if (typeof __dirname === 'string') {
|
||||
dirname = __dirname;
|
||||
} else {
|
||||
dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
}
|
||||
|
||||
export async function renderHTMLToJS(html, {
|
||||
prependCode = '',
|
||||
}) {
|
||||
let jsOutput = '';
|
||||
const dom = htmlparser2.parseDocument(html);
|
||||
const sourceMapInfo = {
|
||||
sourceMapFileList: [],
|
||||
extraLine: prependCode.split('\n').length,
|
||||
extraColumn: 0,
|
||||
};
|
||||
|
||||
let headElement;
|
||||
let bodyElement;
|
||||
function findElement(node) {
|
||||
if (headElement && bodyElement) return;
|
||||
|
||||
if (node.name === 'head') {
|
||||
headElement = node;
|
||||
} else if (node.name === 'body') {
|
||||
bodyElement = node;
|
||||
}
|
||||
|
||||
const {
|
||||
children = [],
|
||||
} = node;
|
||||
children.forEach(findElement);
|
||||
}
|
||||
findElement(dom);
|
||||
|
||||
const extraScript = [];
|
||||
function parse(node) {
|
||||
const {
|
||||
name,
|
||||
attribs,
|
||||
data,
|
||||
children,
|
||||
} = node;
|
||||
let resChildren = [];
|
||||
|
||||
if (children) {
|
||||
if (name === 'script' && children[0] && children[0].data) {
|
||||
extraScript.push(`(function(){${children[0].data}})();`);
|
||||
// The path of sourcemap file.
|
||||
if (attribs['data-sourcemap']) {
|
||||
sourceMapInfo.sourceMapFileList.push(attribs['data-sourcemap']);
|
||||
}
|
||||
|
||||
delete attribs['data-sourcemap'];
|
||||
} else {
|
||||
resChildren = node.children.map(parse);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tagName: name,
|
||||
attributes: attribs,
|
||||
children: resChildren,
|
||||
text: data,
|
||||
};
|
||||
}
|
||||
|
||||
const head = parse(headElement);
|
||||
const body = parse(bodyElement);
|
||||
|
||||
const templateContent = fse.readFileSync(path.join(dirname, '../templates/js-entry.js.ejs'), 'utf-8');
|
||||
jsOutput = ejs.render(templateContent, {
|
||||
createElement: __createElement,
|
||||
head,
|
||||
body,
|
||||
extraScript,
|
||||
prependCode,
|
||||
});
|
||||
|
||||
// Generate sourcemap for entry js.
|
||||
const sourceMap = await generateSourceMap(sourceMapInfo);
|
||||
|
||||
return {
|
||||
jsOutput,
|
||||
sourceMap,
|
||||
};
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import fse from 'fs-extra';
|
||||
import { SourceMapConsumer, SourceMapGenerator } from 'source-map';
|
||||
|
||||
// Starting with extra script, it's a fixed line.
|
||||
const BASE_LINE = 28;
|
||||
// Starting with end of '(function(){', it's a fixed column.
|
||||
const BASE_COLUMN = 12;
|
||||
|
||||
export async function generateSourceMap({
|
||||
sourceMapFileList = [],
|
||||
extraLine = 0,
|
||||
extraColumn = 0,
|
||||
}) {
|
||||
if (!sourceMapFileList.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const generator = new SourceMapGenerator({
|
||||
file: '',
|
||||
sourceRoot: '',
|
||||
});
|
||||
|
||||
await Promise.all(sourceMapFileList.map((sourceMapFile) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!fse.existsSync(sourceMapFile)) {
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
const content = fse.readFileSync(sourceMapFile, 'utf-8');
|
||||
const contentLines = content.split('\n').length;
|
||||
SourceMapConsumer.with(content, null, consumer => {
|
||||
// Set content by source.
|
||||
consumer.sources.forEach((source) => {
|
||||
generator.setSourceContent(source, consumer.sourceContentFor(source));
|
||||
});
|
||||
|
||||
// Get each map from script, and set it to the new map.
|
||||
consumer.eachMapping((mapping) => {
|
||||
// No need to add mapping if no name and no line or no column.
|
||||
if (!mapping.name) return;
|
||||
|
||||
generator.addMapping({
|
||||
generated: {
|
||||
line: mapping.generatedLine + BASE_LINE + extraLine + contentLines,
|
||||
column: mapping.generatedColumn + BASE_COLUMN + extraColumn,
|
||||
},
|
||||
original: {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn,
|
||||
},
|
||||
source: mapping.source,
|
||||
name: mapping.name,
|
||||
});
|
||||
});
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
return generator.toString();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
<%- prependCode %>
|
||||
(function () {
|
||||
<%- createElement %>
|
||||
<% if (head && head.children) {-%>
|
||||
<%- JSON.stringify(head.children) %>.forEach(ele => {
|
||||
__ICE__CREATE_ELEMENT(ele, document.head);
|
||||
});
|
||||
<% } -%>
|
||||
<% if (body && body.children) {-%>
|
||||
<%- JSON.stringify(body.children) %>.forEach(ele => {
|
||||
__ICE__CREATE_ELEMENT(ele, document.body);
|
||||
});
|
||||
<% } -%>
|
||||
})();
|
||||
<% if (extraScript) {-%>
|
||||
<% extraScript.forEach((script, index) => { -%>
|
||||
<%- script %>
|
||||
<% }) -%>
|
||||
<% } -%>
|
||||
|
|
@ -2356,27 +2356,15 @@ importers:
|
|||
abortcontroller-polyfill:
|
||||
specifier: 1.7.5
|
||||
version: 1.7.5
|
||||
ejs:
|
||||
specifier: ^3.1.6
|
||||
version: 3.1.8
|
||||
fs-extra:
|
||||
specifier: ^10.0.0
|
||||
version: 10.1.0
|
||||
history:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
htmlparser2:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
react-router-dom:
|
||||
specifier: 6.21.3
|
||||
version: 6.21.3(react-dom@18.2.0)(react@18.2.0)
|
||||
semver:
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.0
|
||||
source-map:
|
||||
specifier: ^0.7.4
|
||||
version: 0.7.4
|
||||
devDependencies:
|
||||
'@remix-run/web-fetch':
|
||||
specifier: ^4.3.3
|
||||
|
|
@ -21203,6 +21191,7 @@ packages:
|
|||
/source-map@0.7.4:
|
||||
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/source-map@0.8.0-beta.0:
|
||||
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
||||
|
|
|
|||
Loading…
Reference in New Issue