From ee1496261ee1ba586b4586583dc0f939d394eb6f Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 29 Mar 2024 16:49:12 +0800 Subject: [PATCH 1/8] chore: fix lint --- packages/rax-compat/src/create-class.ts | 3 ++- packages/runtime/src/singleRouter.tsx | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/rax-compat/src/create-class.ts b/packages/rax-compat/src/create-class.ts index be8d1dac9c..09eaac1f88 100644 --- a/packages/rax-compat/src/create-class.ts +++ b/packages/rax-compat/src/create-class.ts @@ -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) { 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); diff --git a/packages/runtime/src/singleRouter.tsx b/packages/runtime/src/singleRouter.tsx index fda96b0f85..6d6a347244 100644 --- a/packages/runtime/src/singleRouter.tsx +++ b/packages/runtime/src/singleRouter.tsx @@ -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 | string, routeModuleCache = {}) => { +export const getSingleRoute = async ( + routes: RouteItem[], + basename: string, + location: Partial | string, + routeModuleCache = {}, +) => { const matchedRoutes = matchRoutes(routes, location, basename); const routeModules = await loadRouteModules(matchedRoutes.map(({ route }) => route), routeModuleCache); let loaders = []; From 44ef63fcf1b9f6e3d7778c59e12a2092ef664f59 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Tue, 2 Apr 2024 16:25:08 +0800 Subject: [PATCH 2/8] Feat: optimize runtime size (#6848) * feat: optimize runtime size * fix: log error instead of throw * chore: fix test case * chore: clean up useless params --- packages/ice/src/bundler/config/output.ts | 4 +- packages/ice/src/constant.ts | 9 ++ packages/ice/src/createService.ts | 54 +++----- packages/ice/src/plugins/web/index.ts | 27 +--- packages/ice/src/service/runtimeGenerator.ts | 118 +++++------------- packages/ice/src/types/generator.ts | 14 --- packages/ice/src/types/plugin.ts | 11 +- packages/ice/src/types/userConfig.ts | 8 ++ packages/ice/src/utils/generateEntry.ts | 47 +------ packages/ice/src/utils/hasDocument.ts | 9 ++ .../ice/templates/core/entry.server.ts.ejs | 13 +- packages/ice/templates/core/index.ts.ejs | 4 - packages/ice/tests/generator.test.ts | 7 -- packages/runtime/document.d.ts | 1 + packages/runtime/package.json | 9 +- packages/runtime/src/index.server.ts | 2 +- packages/runtime/src/index.ts | 2 - packages/runtime/src/renderHTMLToJS.tsx | 96 -------------- packages/runtime/src/runServerApp.tsx | 39 ------ packages/runtime/src/sourcemap.ts | 62 --------- packages/runtime/src/types.ts | 2 - packages/runtime/templates/js-entry.js.ejs | 19 --- pnpm-lock.yaml | 13 +- 23 files changed, 92 insertions(+), 478 deletions(-) create mode 100644 packages/ice/src/utils/hasDocument.ts create mode 100644 packages/runtime/document.d.ts delete mode 100644 packages/runtime/src/renderHTMLToJS.tsx delete mode 100644 packages/runtime/src/sourcemap.ts delete mode 100644 packages/runtime/templates/js-entry.js.ejs diff --git a/packages/ice/src/bundler/config/output.ts b/packages/ice/src/bundler/config/output.ts index 7474461665..56ecbd9393 100644 --- a/packages/ice/src/bundler/config/output.ts +++ b/packages/ice/src/bundler/config/output.ts @@ -39,7 +39,7 @@ async function buildCustomOutputs( bundleOptions: Pick, ) { 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) { diff --git a/packages/ice/src/constant.ts b/packages/ice/src/constant.ts index 02a1d54684..29af00da67 100644 --- a/packages/ice/src/constant.ts +++ b/packages/ice/src/constant.ts @@ -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', diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index f37ee2bad6..7e08dcacc4 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -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) => { - generator.addDeclaration('framework', { - ...declarationData, - declarationType: DeclarationType.NORMAL, - }); + addExport: (declarationData: DeclarationData) => { + generator.addDeclaration('framework', declarationData); }, - addTargetExport: (declarationData: Omit) => { - generator.addDeclaration('framework', { - ...declarationData, - declarationType: DeclarationType.TARGET, - }); + addTargetExport: () => { + logger.error('`addTargetExport` is deprecated, please use `addExport` instead.'); }, - addExportTypes: (declarationData: Omit) => { - generator.addDeclaration('frameworkTypes', { - ...declarationData, - declarationType: DeclarationType.NORMAL, - }); + addExportTypes: (declarationData: DeclarationData) => { + generator.addDeclaration('frameworkTypes', declarationData); }, - addRuntimeOptions: (declarationData: Omit) => { - 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) => { - 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) => { - 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, diff --git a/packages/ice/src/plugins/web/index.ts b/packages/ice/src/plugins/web/index.ts index 4f2c090a1e..92bd6d1b35 100644 --- a/packages/ice/src/plugins/web/index.ts +++ b/packages/ice/src/plugins/web/index.ts @@ -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. diff --git a/packages/ice/src/service/runtimeGenerator.ts b/packages/ice/src/service/runtimeGenerator.ts index 4caf8a1083..d9c39cb070 100644 --- a/packages/ice/src/service/runtimeGenerator.ts +++ b/packages/ice/src/service/runtimeGenerator.ts @@ -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) { - const targetImportDeclarations: Array = []; +export function generateDeclaration(exportList: DeclarationData[]) { const importDeclarations: Array = []; const exportDeclarations: Array = []; const exportNames: Array = []; - const variables: Map = 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 = 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 `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, }; }; diff --git a/packages/ice/src/types/generator.ts b/packages/ice/src/types/generator.ts index 41ae88d903..1095dafb25 100644 --- a/packages/ice/src/types/generator.ts +++ b/packages/ice/src/types/generator.ts @@ -1,22 +1,8 @@ -export enum DeclarationType { - NORMAL = 'normal', - TARGET = 'target', -} - export interface DeclarationData { specifier?: string | string[]; source: string; type?: boolean; alias?: Record; - declarationType?: DeclarationType; -} - -export interface TargetDeclarationData { - specifier: string | string[]; - source: string; - target: string; - types?: string | string[]; - declarationType?: DeclarationType; } export type RenderData = Record; diff --git a/packages/ice/src/types/plugin.ts b/packages/ice/src/types/plugin.ts index 49eeb78236..d08fc3ce84 100644 --- a/packages/ice/src/types/plugin.ts +++ b/packages/ice/src/types/plugin.ts @@ -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) => 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; generator: { addExport: AddExport; - addTargetExport: AddTargetExport; + /** + * @deprecated + * API will be removed in the next major version. + */ + addTargetExport: () => void; addExportTypes: AddExport; addRuntimeOptions: AddExport; removeRuntimeOptions: RemoveExport; diff --git a/packages/ice/src/types/userConfig.ts b/packages/ice/src/types/userConfig.ts index 727b8465b0..f924fdc789 100644 --- a/packages/ice/src/types/userConfig.ts +++ b/packages/ice/src/types/userConfig.ts @@ -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; + /** + * @deprecated + * output.prependCode is deprecated, it will be removed in the future. + */ prependCode?: string; }; /** diff --git a/packages/ice/src/utils/generateEntry.ts b/packages/ice/src/utils/generateEntry.ts index 87f802799e..20c8743909 100644 --- a/packages/ice/src/utils/generateEntry.ts +++ b/packages/ice/src/utils/generateEntry.ts @@ -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 0; +} diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs index 00d0322ebf..eb62643ce8 100644 --- a/packages/ice/templates/core/entry.server.ts.ejs +++ b/packages/ice/templates/core/entry.server.ts.ejs @@ -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; diff --git a/packages/ice/templates/core/index.ts.ejs b/packages/ice/templates/core/index.ts.ejs index 8031f0a295..5097948827 100644 --- a/packages/ice/templates/core/index.ts.ejs +++ b/packages/ice/templates/core/index.ts.ejs @@ -3,10 +3,6 @@ import '<%= globalStyle %>' <% } -%> import { definePageConfig, defineRunApp } from './type-defines'; <%- framework.imports %> - -<%- framework.variablesStr %> -<%- framework.targetImport %> - export { definePageConfig, defineRunApp, diff --git a/packages/ice/tests/generator.test.ts b/packages/ice/tests/generator.test.ts index cca2b1ccf7..1c5eb0248a 100644 --- a/packages/ice/tests/generator.test.ts +++ b/packages/ice/tests/generator.test.ts @@ -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', () => { diff --git a/packages/runtime/document.d.ts b/packages/runtime/document.d.ts new file mode 100644 index 0000000000..8398af4ddb --- /dev/null +++ b/packages/runtime/document.d.ts @@ -0,0 +1 @@ +export * from './esm/Document'; diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 8795ebf39b..5d8b61e71c 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -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", diff --git a/packages/runtime/src/index.server.ts b/packages/runtime/src/index.server.ts index e1390daf38..50b818a950 100644 --- a/packages/runtime/src/index.server.ts +++ b/packages/runtime/src/index.server.ts @@ -1,2 +1,2 @@ -export { renderToResponse, renderToHTML, renderToEntry } from './runServerApp.js'; +export { renderToResponse, renderToHTML } from './runServerApp.js'; export * from './index.js'; diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index f5306cc11c..44f7fafbf7 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -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, diff --git a/packages/runtime/src/renderHTMLToJS.tsx b/packages/runtime/src/renderHTMLToJS.tsx deleted file mode 100644 index 8e35e18260..0000000000 --- a/packages/runtime/src/renderHTMLToJS.tsx +++ /dev/null @@ -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, - }; -} \ No newline at end of file diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index d0bc8309bf..00a8f79439 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -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; - distType?: Array<'html' | 'javascript'>; - prependCode?: string; serverData?: any; streamOptions?: RenderToPipeableStreamOptions; } @@ -70,42 +67,6 @@ interface Response { headers?: Record; } -/** - * 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. */ diff --git a/packages/runtime/src/sourcemap.ts b/packages/runtime/src/sourcemap.ts deleted file mode 100644 index d6711769c1..0000000000 --- a/packages/runtime/src/sourcemap.ts +++ /dev/null @@ -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(); -} diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index fdf1ce211b..1e6c46c280 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -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 diff --git a/packages/runtime/templates/js-entry.js.ejs b/packages/runtime/templates/js-entry.js.ejs deleted file mode 100644 index 6accc3208a..0000000000 --- a/packages/runtime/templates/js-entry.js.ejs +++ /dev/null @@ -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 %> -<% }) -%> -<% } -%> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b6da01fde..b8d8258635 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==} From 77155bab1eec222a2f16249a46394aec7eb2d204 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Tue, 9 Apr 2024 16:26:35 +0800 Subject: [PATCH 3/8] Fix: optimize runtime code when dataLoader is not defined (#6849) * fix: optimize runtime code when dataLoader is not defined * fix: optimize options * fix: remove dataloader import in entry --- .changeset/wicked-points-fetch.md | 6 ++++++ packages/ice/src/createService.ts | 10 +++++++-- packages/ice/src/utils/runtimeEnv.ts | 21 +++++++++++++++---- .../ice/templates/core/entry.client.tsx.ejs | 7 ++++--- packages/runtime/src/appData.ts | 18 +++++++++------- packages/runtime/src/routes.tsx | 10 +++++---- packages/runtime/src/runClientApp.tsx | 6 ++++-- 7 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 .changeset/wicked-points-fetch.md diff --git a/.changeset/wicked-points-fetch.md b/.changeset/wicked-points-fetch.md new file mode 100644 index 0000000000..481c6e0fa4 --- /dev/null +++ b/.changeset/wicked-points-fetch.md @@ -0,0 +1,6 @@ +--- +'@ice/runtime': patch +'@ice/app': patch +--- + +feat: remove runtime code when loaders is not export diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index 7e08dcacc4..dbd1da8f12 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -247,6 +247,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, @@ -265,13 +267,13 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt entryCode, 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( { @@ -355,7 +357,11 @@ 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. + dataLoader: command !== 'build' || loaderExports, + }); return { run: async () => { diff --git a/packages/ice/src/utils/runtimeEnv.ts b/packages/ice/src/utils/runtimeEnv.ts index 74f1eb3373..a3dd17cb6e 100644 --- a/packages/ice/src/utils/runtimeEnv.ts +++ b/packages/ice/src/utils/runtimeEnv.ts @@ -10,6 +10,7 @@ export interface Envs { } interface EnvOptions { disableRouter: boolean; + dataLoader: boolean; } const expandedEnvs = {}; @@ -50,6 +51,8 @@ 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'; // set ssr and ssg env to false, for remove dead code in CSR. process.env.ICE_CORE_SSG = 'false'; @@ -57,17 +60,27 @@ export async function setEnv( } export const updateRuntimeEnv = (appConfig: AppConfig, options: EnvOptions) => { - const { disableRouter } = options; + const { disableRouter, dataLoader } = 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'; } }; 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_DATA_LOADER', + ]; } export function getExpandedEnvs(): Record { diff --git a/packages/ice/templates/core/entry.client.tsx.ejs b/packages/ice/templates/core/entry.client.tsx.ejs index 2fd1cb89bb..0e52e5a325 100644 --- a/packages/ice/templates/core/entry.client.tsx.ejs +++ b/packages/ice/templates/core/entry.client.tsx.ejs @@ -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 %> diff --git a/packages/runtime/src/appData.ts b/packages/runtime/src/appData.ts index 57fb28e2f5..de9a3cea37 100644 --- a/packages/runtime/src/appData.ts +++ b/packages/runtime/src/appData.ts @@ -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 { diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 716f2147d4..39ad9f7e4b 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -185,10 +185,12 @@ export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { const getData = (requestContext: RequestContext) => { let routeData: any; - if (globalLoader) { - routeData = globalLoader.getData(routeId, { renderMode, requestContext }); - } else { - routeData = callDataLoader(loader, requestContext); + if (process.env.ICE_CORE_REMOVE_DATA_LOADER !== 'true') { + if (globalLoader) { + routeData = globalLoader.getData(routeId, { renderMode, requestContext }); + } else { + routeData = callDataLoader(loader, requestContext); + } } return routeData; }; diff --git a/packages/runtime/src/runClientApp.tsx b/packages/runtime/src/runClientApp.tsx index 4a25fc3b59..d354be4210 100644 --- a/packages/runtime/src/runClientApp.tsx +++ b/packages/runtime/src/runClientApp.tsx @@ -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); From 012dbfda05e819adfb1afb74b29eac9e36145e46 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 10 Apr 2024 10:23:09 +0800 Subject: [PATCH 4/8] chore: remove useless code (#6860) --- packages/ice/src/service/webpackCompiler.ts | 204 -------------------- 1 file changed, 204 deletions(-) delete mode 100644 packages/ice/src/service/webpackCompiler.ts diff --git a/packages/ice/src/service/webpackCompiler.ts b/packages/ice/src/service/webpackCompiler.ts deleted file mode 100644 index 0ecacbc930..0000000000 --- a/packages/ice/src/service/webpackCompiler.ts +++ /dev/null @@ -1,204 +0,0 @@ -import webpackBundler from '@ice/bundles/compiled/webpack/index.js'; -import type ora from '@ice/bundles/compiled/ora/index.js'; -import lodash from '@ice/bundles/compiled/lodash/index.js'; -import type { TaskConfig, Context } from 'build-scripts'; -import type { Config } from '@ice/shared-config/types'; -import type webpack from 'webpack'; -import type { Urls, ServerCompiler, GetAppConfig, GetRoutesConfig, ExtendsPluginAPI, GetDataloaderConfig } from '../types/plugin.js'; -import formatWebpackMessages from '../utils/formatWebpackMessages.js'; -import type ServerCompilerPlugin from '../webpack/ServerCompilerPlugin'; -import { IMPORT_META_RENDERER, IMPORT_META_TARGET, WEB } from '../constant.js'; -import getServerCompilerPlugin from '../utils/getServerCompilerPlugin.js'; -import DataLoaderPlugin from '../webpack/DataLoaderPlugin.js'; -import ReCompilePlugin from '../webpack/ReCompilePlugin.js'; -import ServerRunnerPlugin from '../webpack/ServerRunnerPlugin.js'; -import { logger } from '../utils/logger.js'; -import type ServerRunner from './ServerRunner.js'; -import { getRouteExportConfig } from './config.js'; - -const { debounce } = lodash; - -async function webpackCompiler(options: { - context: Context; - webpackConfigs: webpack.Configuration[]; - taskConfigs: TaskConfig[]; - urls?: Urls; - spinner: ora.Ora; - devPath?: string; - hooksAPI: { - serverCompiler: ServerCompiler; - getAppConfig: GetAppConfig; - getRoutesConfig: GetRoutesConfig; - getDataloaderConfig: GetDataloaderConfig; - serverRunner?: ServerRunner; - }; -}) { - const { - taskConfigs, - urls, - hooksAPI, - webpackConfigs, - spinner, - devPath, - context, - } = options; - const { rootDir, applyHook, commandArgs, command, userConfig, getAllPlugin } = context; - // `commandArgs` doesn't guarantee target exists. - const { target = WEB } = commandArgs; - const { serverCompiler, serverRunner } = hooksAPI; - const { serverCompileTask, dataCache, watch, generator } = context.extendsPluginAPI; - - await applyHook(`before.${command}.run`, { - urls, - commandArgs, - taskConfigs, - webpackConfigs, - ...hooksAPI, - }); - - for (const taskConfig of taskConfigs) { - const index = taskConfigs.indexOf(taskConfig); - const webpackConfig = webpackConfigs[index]; - const { useDevServer, useDataLoader, server } = taskConfig.config; - const { reCompile: reCompileRouteConfig, ensureRoutesConfig } = getRouteExportConfig(rootDir); - let serverCompilerPlugin: ServerCompilerPlugin; - - // Add webpack [ServerCompilerPlugin] - if (useDevServer) { - const outputDir = webpackConfig.output.path; - - if (command === 'start' && serverRunner) { - // Add server runner plugin - webpackConfig.plugins.push(new ServerRunnerPlugin( - serverRunner, - ensureRoutesConfig, - )); - } else { - serverCompilerPlugin = getServerCompilerPlugin(serverCompiler, { - rootDir, - serverEntry: server?.entry, - outputDir, - serverCompileTask, - userConfig, - ensureRoutesConfig, - runtimeDefineVars: { - [IMPORT_META_TARGET]: JSON.stringify(target), - [IMPORT_META_RENDERER]: JSON.stringify('server'), - }, - }); - webpackConfig.plugins.push(serverCompilerPlugin); - } - - // Add re-compile plugin - if (command === 'start') { - webpackConfig.plugins.push( - new ReCompilePlugin(reCompileRouteConfig, (files) => { - // Only when routes file changed. - const routeManifest = JSON.parse(dataCache.get('routes'))?.routeManifest || {}; - const routeFiles = Object.keys(routeManifest).map((key) => { - const { file } = routeManifest[key]; - return `src/pages/${file}`; - }); - return files.some((filePath) => routeFiles.some(routeFile => filePath.includes(routeFile))); - }), - ); - const debounceCompile = debounce(() => { - serverCompilerPlugin?.buildResult?.context.rebuild(); - console.log('Document updated, try to reload page for latest html content.'); - }, 200); - watch.addEvent([ - /src\/document(\/index)?(.js|.jsx|.tsx)/, - (event: string) => { - if (event === 'change') { - debounceCompile(); - } - }, - ]); - } - } - - // Add webpack plugin of data-loader. - if (useDataLoader) { - const frameworkExports = generator.getExportList('framework', target); - - webpackConfig.plugins.push(new DataLoaderPlugin({ - serverCompiler, - target, - rootDir, - getAllPlugin, - frameworkExports, - })); - } - } - - // Add spinner for first webpack task. - // @TODO: Merge tasks for multi compiler. - const firstWebpackConfig = webpackConfigs[0]; - firstWebpackConfig.plugins.push((compiler: webpack.Compiler) => { - compiler.hooks.beforeCompile.tap('spinner', () => { - spinner.text = 'Compiling...\n'; - }); - compiler.hooks.afterEmit.tap('spinner', () => { - spinner.stop(); - }); - }); - let compiler: webpack.Compiler; - try { - // @ts-ignore - compiler = webpackBundler(webpackConfigs); - } catch (error) { - logger.error('Webpack compile error.'); - logger.error(error); - } - - let isFirstCompile = true; - - compiler.hooks.done.tap('done', async stats => { - const statsData = stats.toJson({ - all: false, - warnings: true, - errors: true, - timings: true, - assets: true, - }); - const messages = formatWebpackMessages(statsData); - const isSuccessful = !messages.errors.length; - if (messages.errors.length) { - // Only keep the first error. Others are often indicative - // of the same problem, but confuse the reader with noise. - if (messages.errors.length > 1) { - messages.errors.length = 1; - } - logger.error('Client compiled with errors.'); - console.error(messages.errors.join('\n')); - return; - } else if (messages.warnings.length) { - logger.warn('Client compiled with warnings.'); - logger.warn(messages.warnings.join('\n')); - } - if (command === 'start') { - // compiler.hooks.done is AsyncSeriesHook which does not support async function - await applyHook('after.start.compile', { - stats, - isSuccessful, - isFirstCompile, - urls, - devUrlInfo: { - devPath, - }, - messages, - taskConfigs, - ...hooksAPI, - }); - } - - if (isSuccessful) { - // if compiled successfully reset first compile flag after been posted to lifecycle hooks - isFirstCompile = false; - } - }); - - return compiler; -} - -export default webpackCompiler; From 46c1d24f2d4a59bccb1b01204a14b96952f538f4 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Fri, 12 Apr 2024 13:35:11 +0800 Subject: [PATCH 5/8] fix: remove deprecated api of addTargetExport (#6861) --- packages/ice/src/createService.ts | 3 --- packages/ice/src/types/plugin.ts | 5 ----- 2 files changed, 8 deletions(-) diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index dbd1da8f12..2600fcf611 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -76,9 +76,6 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt addExport: (declarationData: DeclarationData) => { generator.addDeclaration('framework', declarationData); }, - addTargetExport: () => { - logger.error('`addTargetExport` is deprecated, please use `addExport` instead.'); - }, addExportTypes: (declarationData: DeclarationData) => { generator.addDeclaration('frameworkTypes', declarationData); }, diff --git a/packages/ice/src/types/plugin.ts b/packages/ice/src/types/plugin.ts index d08fc3ce84..cd19b013f2 100644 --- a/packages/ice/src/types/plugin.ts +++ b/packages/ice/src/types/plugin.ts @@ -137,11 +137,6 @@ export interface ExtendsPluginAPI { registerTask: RegisterTask; generator: { addExport: AddExport; - /** - * @deprecated - * API will be removed in the next major version. - */ - addTargetExport: () => void; addExportTypes: AddExport; addRuntimeOptions: AddExport; removeRuntimeOptions: RemoveExport; From d5c378b6841b99adeb1c79001b6ab181857bb524 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Mon, 15 Apr 2024 15:15:05 +0800 Subject: [PATCH 6/8] Fix: reduce bundle size by remove runtime module (#6850) * fix: optimize runtime size * chore: changeset * chore: changeset * Delete .changeset/late-dryers-invent.md --- .changeset/cuddly-jars-agree.md | 6 ++++++ packages/ice/src/createService.ts | 2 +- packages/ice/src/routes.ts | 17 +++++++++++------ packages/ice/src/utils/runtimeEnv.ts | 9 ++++++++- packages/runtime/src/routes.tsx | 4 ++-- 5 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 .changeset/cuddly-jars-agree.md diff --git a/.changeset/cuddly-jars-agree.md b/.changeset/cuddly-jars-agree.md new file mode 100644 index 0000000000..02abde7398 --- /dev/null +++ b/.changeset/cuddly-jars-agree.md @@ -0,0 +1,6 @@ +--- +'@ice/runtime': patch +'@ice/app': patch +--- + +fix: reduce bundle size by remove runtime module diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index 2600fcf611..b440de2976 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -353,10 +353,10 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt ); const appConfig: AppConfig = (await getAppConfig()).default; - 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, }); diff --git a/packages/ice/src/routes.ts b/packages/ice/src/routes.ts index e7f26469f5..bfe542376c 100644 --- a/packages/ice/src/routes.ts +++ b/packages/ice/src/routes.ts @@ -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, }; } diff --git a/packages/ice/src/utils/runtimeEnv.ts b/packages/ice/src/utils/runtimeEnv.ts index a3dd17cb6e..08337167b6 100644 --- a/packages/ice/src/utils/runtimeEnv.ts +++ b/packages/ice/src/utils/runtimeEnv.ts @@ -10,6 +10,7 @@ export interface Envs { } interface EnvOptions { disableRouter: boolean; + routesConfig: boolean; dataLoader: boolean; } @@ -54,13 +55,15 @@ export async function setEnv( // 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, dataLoader } = options; + const { disableRouter, dataLoader, routesConfig } = options; if (!appConfig?.app?.errorBoundary) { process.env.ICE_CORE_ERROR_BOUNDARY = 'false'; } @@ -70,6 +73,9 @@ export const updateRuntimeEnv = (appConfig: AppConfig, options: EnvOptions) => { if (!dataLoader) { process.env.ICE_CORE_REMOVE_DATA_LOADER = 'true'; } + if (!routesConfig) { + process.env.ICE_CORE_REMOVE_ROUTES_CONFIG = 'true'; + } }; export function getCoreEnvKeys() { @@ -79,6 +85,7 @@ export function getCoreEnvKeys() { 'ICE_CORE_ERROR_BOUNDARY', 'ICE_CORE_INITIAL_DATA', 'ICE_CORE_DEV_PORT', + 'ICE_CORE_REMOVE_ROUTES_CONFIG', 'ICE_CORE_REMOVE_DATA_LOADER', ]; } diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index 39ad9f7e4b..f32c4a2a0d 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -165,7 +165,7 @@ export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { 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; @@ -242,7 +242,7 @@ export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { pageConfig: routeConfig, }; // Update routes config when render mode is CSR. - if (import.meta.renderer === 'client') { + if (import.meta.renderer === 'client' && process.env.ICE_CORE_REMOVE_ROUTES_CONFIG !== 'true') { await updateRoutesConfig(loaderData); } return loaderData; From ac3595270de4d5d2b633a2c7ea1c57f7b752585e Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Wed, 17 Apr 2024 17:14:53 +0800 Subject: [PATCH 7/8] fix: optimize loader function of route (#6864) --- packages/runtime/src/routes.tsx | 164 +++++++++++++++++--------------- 1 file changed, 89 insertions(+), 75 deletions(-) diff --git a/packages/runtime/src/routes.tsx b/packages/runtime/src/routes.tsx index f32c4a2a0d..63b20651ac 100644 --- a/packages/runtime/src/routes.tsx +++ b/packages/runtime/src/routes.tsx @@ -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,19 +154,21 @@ 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) { @@ -172,79 +183,82 @@ export function createRouteLoader(options: RouteLoaderOptions): LoaderFunction { }; } - 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; - } - - 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); - } - } - return routeData; - }; + // 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; - // 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({}) : {}, - }); - }; + // Compat dataLoaderConfig not return by defineDataLoader. + if (typeof dataLoaderConfig === 'function' || Array.isArray(dataLoaderConfig)) { + loader = dataLoaderConfig; } else { - throw new Error('DataLoader: defer is not supported in single router mode.'); + loader = dataLoaderConfig.loader; + loaderOptions = dataLoaderConfig.options; } - } - // 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' && process.env.ICE_CORE_REMOVE_ROUTES_CONFIG !== 'true') { - await updateRoutesConfig(loaderData); - } - return loaderData; - }; + } } From 9f5c785bddc97a628d2482741c5f7b4b184c1402 Mon Sep 17 00:00:00 2001 From: ClarkXia Date: Mon, 22 Apr 2024 10:22:06 +0800 Subject: [PATCH 8/8] chore: update versions (#6859) --- .changeset/cuddly-jars-agree.md | 6 ------ .changeset/wicked-points-fetch.md | 6 ------ packages/ice/CHANGELOG.md | 10 ++++++++++ packages/ice/package.json | 4 ++-- packages/plugin-i18n/package.json | 4 ++-- packages/runtime/CHANGELOG.md | 7 +++++++ packages/runtime/package.json | 2 +- pnpm-lock.yaml | 2 +- 8 files changed, 23 insertions(+), 18 deletions(-) delete mode 100644 .changeset/cuddly-jars-agree.md delete mode 100644 .changeset/wicked-points-fetch.md diff --git a/.changeset/cuddly-jars-agree.md b/.changeset/cuddly-jars-agree.md deleted file mode 100644 index 02abde7398..0000000000 --- a/.changeset/cuddly-jars-agree.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@ice/runtime': patch -'@ice/app': patch ---- - -fix: reduce bundle size by remove runtime module diff --git a/.changeset/wicked-points-fetch.md b/.changeset/wicked-points-fetch.md deleted file mode 100644 index 481c6e0fa4..0000000000 --- a/.changeset/wicked-points-fetch.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@ice/runtime': patch -'@ice/app': patch ---- - -feat: remove runtime code when loaders is not export diff --git a/packages/ice/CHANGELOG.md b/packages/ice/CHANGELOG.md index 5a277e39dd..ace526e3b5 100644 --- a/packages/ice/CHANGELOG.md +++ b/packages/ice/CHANGELOG.md @@ -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 diff --git a/packages/ice/package.json b/packages/ice/package.json index 02ccf1b4f6..8177386229 100644 --- a/packages/ice/package.json +++ b/packages/ice/package.json @@ -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", diff --git a/packages/plugin-i18n/package.json b/packages/plugin-i18n/package.json index 526379fe96..bcabba8076 100644 --- a/packages/plugin-i18n/package.json +++ b/packages/plugin-i18n/package.json @@ -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" diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 9f1ff19070..cd0a7b1f86 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -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` diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 2c1b954661..860770a21e 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40a306e6fc..3f9ddbb1cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1656,7 +1656,7 @@ importers: specifier: 1.1.6 version: link:../rspack-config '@ice/runtime': - specifier: ^1.4.3 + specifier: ^1.4.5 version: link:../runtime '@ice/shared-config': specifier: 1.2.6