diff --git a/.eslintignore b/.eslintignore
index 93f43288ef..b95cb05b89 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -14,6 +14,7 @@ coverage/
/packages/*/lib/
/packages/*/esm/
/packages/*/es2017/
+/packages/*/es2021/
**/tests/libs/*.js
# 忽略第三方包
diff --git a/examples/app-config/ice.config.mts b/examples/app-config/ice.config.mts
index 562d28cc1d..b89aefa7cb 100644
--- a/examples/app-config/ice.config.mts
+++ b/examples/app-config/ice.config.mts
@@ -1,3 +1,6 @@
import { defineConfig } from '@ice/app';
+import externals from '@ice/plugin-externals';
-export default defineConfig(() => ({}));
+export default defineConfig(() => ({
+ plugins: [externals({ preset: 'react' })]
+}));
diff --git a/examples/app-config/package.json b/examples/app-config/package.json
index 25618a5f2d..8be59abe78 100644
--- a/examples/app-config/package.json
+++ b/examples/app-config/package.json
@@ -12,18 +12,13 @@
"license": "MIT",
"dependencies": {
"@ice/app": "workspace:*",
- "@ice/plugin-auth": "workspace:*",
- "@ice/plugin-rax-compat": "workspace:*",
+ "@ice/plugin-externals": "workspace:*",
"@ice/runtime": "workspace:*",
- "@uni/env": "^1.1.0",
- "ahooks": "^3.3.8",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
- "@types/react-dom": "^18.0.2",
- "speed-measure-webpack-plugin": "^1.5.0",
- "webpack": "^5.88.0"
+ "@types/react-dom": "^18.0.2"
}
}
diff --git a/examples/basic-project/tsconfig.json b/examples/basic-project/tsconfig.json
index 6584fa600c..28b2e34aeb 100644
--- a/examples/basic-project/tsconfig.json
+++ b/examples/basic-project/tsconfig.json
@@ -29,4 +29,4 @@
},
"include": ["src", ".ice", "ice.config.*"],
"exclude": ["build", "public"]
-}
\ No newline at end of file
+}
diff --git a/examples/with-entry-type/ice.config.mts b/examples/with-entry-type/ice.config.mts
deleted file mode 100644
index 080e40e660..0000000000
--- a/examples/with-entry-type/ice.config.mts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { defineConfig } from '@ice/app';
-
-export default defineConfig({
- plugins: [],
- server: {
- onDemand: true,
- format: 'esm',
- },
- output: {
- distType: 'javascript'
- },
- sourceMap: true,
- routes: {
- defineRoutes: (route) => {
- route('/custom', 'Custom/index.tsx');
- },
- },
-});
diff --git a/examples/with-entry-type/src/document.tsx b/examples/with-entry-type/src/document.tsx
deleted file mode 100644
index fd149f08a5..0000000000
--- a/examples/with-entry-type/src/document.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import path from 'path';
-import { fileURLToPath } from 'url';
-import { Meta, Title, Links, Main, Scripts } from 'ice';
-import fse from 'fs-extra';
-
-let dirname;
-if (typeof __dirname === 'string') {
- dirname = __dirname;
-} else {
- dirname = path.dirname(fileURLToPath(import.meta.url));
-}
-
-function Document() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- {
- if (props.src && !props.src.startsWith('http')) {
- const filePath = path.join(dirname, `..${props.src}`);
- const sourceMapFilePath = path.join(dirname, `..${props.src}.map`);
- const res = fse.readFileSync(filePath, 'utf-8');
- return ;
- } else {
- return ;
- }
- }}
- />
-
-
- );
-}
-
-export default Document;
diff --git a/examples/with-entry-type/src/pages/Custom/index.tsx b/examples/with-entry-type/src/pages/Custom/index.tsx
deleted file mode 100644
index 164e24ed00..0000000000
--- a/examples/with-entry-type/src/pages/Custom/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Link, useData } from 'ice';
-
-export default function Custom() {
- const data = useData();
-
- return (
- <>
- Custom Page
- home
- {data}
- >
- );
-}
-
-export function pageConfig() {
- return {
- title: 'Custom',
- };
-}
diff --git a/examples/with-entry-type/src/pages/about.tsx b/examples/with-entry-type/src/pages/about.tsx
deleted file mode 100644
index b44b31a76d..0000000000
--- a/examples/with-entry-type/src/pages/about.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Link } from 'ice';
-
-export default function About() {
- return (
- <>
- About Page
- home
- new
- >
- );
-}
-
-export function pageConfig() {
- return {
- title: 'About',
- meta: [
- {
- name: 'theme-color',
- content: '#eee',
- },
- ],
- links: [{
- href: 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css',
- rel: 'stylesheet',
- }],
- scripts: [{
- src: 'https://cdn.jsdelivr.net/npm/lodash@2.4.1/dist/lodash.min.js',
- }],
- };
-}
diff --git a/examples/with-entry-type/src/pages/blog.tsx b/examples/with-entry-type/src/pages/blog.tsx
deleted file mode 100644
index 4113cb37d1..0000000000
--- a/examples/with-entry-type/src/pages/blog.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Link, useData, useConfig } from 'ice';
-
-export default function Blog() {
- const data = useData();
- const config = useConfig();
-
- console.log('render Blog', 'data', data, 'config', config);
-
- return (
- <>
- Blog Page
- home
- >
- );
-}
-
-export function pageConfig() {
- return {
- title: 'Blog',
- };
-}
\ No newline at end of file
diff --git a/examples/with-entry-type/src/pages/home.tsx b/examples/with-entry-type/src/pages/home.tsx
deleted file mode 100644
index f5aaf1b72b..0000000000
--- a/examples/with-entry-type/src/pages/home.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { definePageConfig } from 'ice';
-
-export default function Home() {
- return (
- <>
- Home Page
- >
- );
-}
-
-export const pageConfig = definePageConfig(() => {
- return {
- queryParamsPassKeys: [
- 'questionId',
- 'source',
- 'disableNav',
- ],
- title: 'Home',
- };
-});
diff --git a/examples/with-entry-type/src/pages/index.module.css b/examples/with-entry-type/src/pages/index.module.css
deleted file mode 100644
index 679273d44d..0000000000
--- a/examples/with-entry-type/src/pages/index.module.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.title {
- color: red;
- margin-left: 10rpx;
-}
-
-.data {
- margin-top: 10px;
-}
-
-.homeContainer {
- align-items: center;
- margin-top: 200rpx;
-}
-
-.homeTitle {
- font-size: 45rpx;
- font-weight: bold;
- margin: 20rpx 0;
-}
-
-.homeInfo {
- font-size: 36rpx;
- margin: 8rpx 0;
- color: #555;
-}
diff --git a/examples/with-entry-type/src/pages/layout.tsx b/examples/with-entry-type/src/pages/layout.tsx
deleted file mode 100644
index 24a21a39b6..0000000000
--- a/examples/with-entry-type/src/pages/layout.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Outlet } from 'ice';
-
-export default () => {
- return (
-
-
ICE 3.0 Layout
-
-
- );
-};
-
-export function pageConfig() {
- return {
- title: 'Layout',
- meta: [
- {
- name: 'layout-color',
- content: '#f00',
- },
- ],
- };
-}
diff --git a/examples/with-entry-type/.browserslistrc b/examples/with-fallback-entry/.browserslistrc
similarity index 100%
rename from examples/with-entry-type/.browserslistrc
rename to examples/with-fallback-entry/.browserslistrc
diff --git a/examples/with-fallback-entry/ice.config.mts b/examples/with-fallback-entry/ice.config.mts
new file mode 100644
index 0000000000..183825d80b
--- /dev/null
+++ b/examples/with-fallback-entry/ice.config.mts
@@ -0,0 +1,10 @@
+import { defineConfig } from '@ice/app';
+import plugin from './plugin';
+
+export default defineConfig(() => ({
+ plugins: [plugin()],
+ ssr: true,
+ server: {
+ format: 'cjs',
+ }
+}));
diff --git a/examples/with-entry-type/package.json b/examples/with-fallback-entry/package.json
similarity index 76%
rename from examples/with-entry-type/package.json
rename to examples/with-fallback-entry/package.json
index 76c13d53af..a600d9eb7c 100644
--- a/examples/with-entry-type/package.json
+++ b/examples/with-fallback-entry/package.json
@@ -1,7 +1,7 @@
{
- "name": "@examples/with-entry-type",
- "private": true,
+ "name": "@examples/with-fallback-entry",
"version": "1.0.0",
+ "private": true,
"scripts": {
"start": "ice start",
"build": "ice build"
@@ -12,11 +12,10 @@
"dependencies": {
"@ice/app": "workspace:*",
"@ice/runtime": "workspace:*",
- "react": "^18.0.0",
- "react-dom": "^18.0.0"
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
- "fs-extra": "^10.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.2",
"webpack": "^5.88.0"
diff --git a/examples/with-fallback-entry/plugin.ts b/examples/with-fallback-entry/plugin.ts
new file mode 100644
index 0000000000..8286d268b4
--- /dev/null
+++ b/examples/with-fallback-entry/plugin.ts
@@ -0,0 +1,12 @@
+export default function createPlugin() {
+ return {
+ name: 'custom-plugin',
+ setup({ onGetConfig }) {
+ onGetConfig((config) => {
+ config.server = {
+ fallbackEntry: true,
+ };
+ });
+ },
+ };
+}
diff --git a/examples/with-entry-type/public/favicon.ico b/examples/with-fallback-entry/public/favicon.ico
similarity index 100%
rename from examples/with-entry-type/public/favicon.ico
rename to examples/with-fallback-entry/public/favicon.ico
diff --git a/examples/with-entry-type/src/app.tsx b/examples/with-fallback-entry/src/app.tsx
similarity index 100%
rename from examples/with-entry-type/src/app.tsx
rename to examples/with-fallback-entry/src/app.tsx
diff --git a/examples/with-fallback-entry/src/document.tsx b/examples/with-fallback-entry/src/document.tsx
new file mode 100644
index 0000000000..1e7b99c49d
--- /dev/null
+++ b/examples/with-fallback-entry/src/document.tsx
@@ -0,0 +1,22 @@
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Document;
diff --git a/examples/with-fallback-entry/src/global.css b/examples/with-fallback-entry/src/global.css
new file mode 100644
index 0000000000..604282adc9
--- /dev/null
+++ b/examples/with-fallback-entry/src/global.css
@@ -0,0 +1,3 @@
+body {
+ font-size: 14px;
+}
diff --git a/examples/with-fallback-entry/src/pages/index.tsx b/examples/with-fallback-entry/src/pages/index.tsx
new file mode 100644
index 0000000000..bb72815361
--- /dev/null
+++ b/examples/with-fallback-entry/src/pages/index.tsx
@@ -0,0 +1,3 @@
+export default function Home() {
+ return home
;
+}
diff --git a/examples/with-entry-type/src/typings.d.ts b/examples/with-fallback-entry/src/typings.d.ts
similarity index 100%
rename from examples/with-entry-type/src/typings.d.ts
rename to examples/with-fallback-entry/src/typings.d.ts
diff --git a/examples/with-entry-type/tsconfig.json b/examples/with-fallback-entry/tsconfig.json
similarity index 100%
rename from examples/with-entry-type/tsconfig.json
rename to examples/with-fallback-entry/tsconfig.json
diff --git a/packages/ice/CHANGELOG.md b/packages/ice/CHANGELOG.md
index 10c5cd07ea..944faa3ec3 100644
--- a/packages/ice/CHANGELOG.md
+++ b/packages/ice/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## 3.4.10
+
+### Patch Changes
+
+- 15c8200f: feat: support build additional server entry for fallback
+- Updated dependencies [15c8200f]
+- Updated dependencies [d073ee5a]
+ - @ice/shared-config@1.2.8
+ - @ice/runtime@1.4.10
+ - @ice/rspack-config@1.1.8
+ - @ice/webpack-config@1.1.15
+
## 3.4.9
### Patch Changes
diff --git a/packages/ice/package.json b/packages/ice/package.json
index 851ee4292d..eb21ec8827 100644
--- a/packages/ice/package.json
+++ b/packages/ice/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/app",
- "version": "3.4.9",
+ "version": "3.4.10",
"description": "provide scripts and configuration used by web framework ice",
"type": "module",
"main": "./esm/index.js",
@@ -49,10 +49,10 @@
"dependencies": {
"@ice/bundles": "0.2.6",
"@ice/route-manifest": "1.2.2",
- "@ice/runtime": "^1.4.8",
- "@ice/shared-config": "1.2.7",
- "@ice/webpack-config": "1.1.14",
- "@ice/rspack-config": "1.1.7",
+ "@ice/runtime": "^1.4.10",
+ "@ice/shared-config": "1.2.8",
+ "@ice/webpack-config": "1.1.15",
+ "@ice/rspack-config": "1.1.8",
"@swc/helpers": "0.5.1",
"@types/express": "^4.17.14",
"address": "^1.1.2",
diff --git a/packages/ice/src/bundler/config/plugins.ts b/packages/ice/src/bundler/config/plugins.ts
index a17f5410a3..3d69414195 100644
--- a/packages/ice/src/bundler/config/plugins.ts
+++ b/packages/ice/src/bundler/config/plugins.ts
@@ -1,5 +1,7 @@
+import path from 'path';
+import type { CommandName } from 'build-scripts';
import ServerRunnerPlugin from '../../webpack/ServerRunnerPlugin.js';
-import { IMPORT_META_RENDERER, IMPORT_META_TARGET, WEB } from '../../constant.js';
+import { IMPORT_META_RENDERER, IMPORT_META_TARGET, WEB, FALLBACK_ENTRY, RUNTIME_TMP_DIR } from '../../constant.js';
import getServerCompilerPlugin from '../../utils/getServerCompilerPlugin.js';
import ReCompilePlugin from '../../webpack/ReCompilePlugin.js';
import getEntryPoints from '../../utils/getEntryPoints.js';
@@ -20,6 +22,18 @@ export const getSpinnerPlugin = (spinner, name?: string) => {
};
};
+export const getFallbackEntry = (options: {
+ rootDir: string;
+ command: CommandName;
+ fallbackEntry: boolean;
+}): string => {
+ const { command, fallbackEntry, rootDir } = options;
+ if (command === 'build' && fallbackEntry) {
+ return path.join(rootDir, RUNTIME_TMP_DIR, FALLBACK_ENTRY);
+ }
+ return '';
+};
+
interface ServerPluginOptions {
serverRunner?: ServerRunner;
serverCompiler?: ServerCompiler;
@@ -30,6 +44,7 @@ interface ServerPluginOptions {
serverEntry?: string;
ensureRoutesConfig: () => Promise;
userConfig?: UserConfig;
+ fallbackEntry?: string;
getFlattenRoutes?: () => string[];
command?: string;
}
@@ -43,6 +58,7 @@ export const getServerPlugin = ({
outputDir,
serverCompileTask,
userConfig,
+ fallbackEntry,
getFlattenRoutes,
command,
}: ServerPluginOptions) => {
@@ -53,6 +69,7 @@ export const getServerPlugin = ({
return getServerCompilerPlugin(serverCompiler, {
rootDir,
serverEntry,
+ fallbackEntry,
outputDir,
serverCompileTask,
userConfig,
diff --git a/packages/ice/src/bundler/rspack/getConfig.ts b/packages/ice/src/bundler/rspack/getConfig.ts
index 1ca8a848b5..a724b49595 100644
--- a/packages/ice/src/bundler/rspack/getConfig.ts
+++ b/packages/ice/src/bundler/rspack/getConfig.ts
@@ -11,7 +11,7 @@ import {
CSS_MODULES_LOCAL_IDENT_NAME,
CSS_MODULES_LOCAL_IDENT_NAME_DEV,
} from '../../constant.js';
-import { getReCompilePlugin, getServerPlugin, getSpinnerPlugin } from '../config/plugins.js';
+import { getFallbackEntry, getReCompilePlugin, getServerPlugin, getSpinnerPlugin } from '../config/plugins.js';
import { getExpandedEnvs } from '../../utils/runtimeEnv.js';
import type { BundlerOptions, Context } from '../types.js';
import type { PluginData } from '../../types/plugin.js';
@@ -34,8 +34,8 @@ const getConfig: GetConfig = async (context, options, rspack) => {
} = options;
const {
rootDir,
- userConfig,
command,
+ userConfig,
extendsPluginAPI: {
serverCompileTask,
getRoutesFile,
@@ -50,6 +50,11 @@ const getConfig: GetConfig = async (context, options, rspack) => {
getSpinnerPlugin(spinner),
// Add Server runner plugin.
getServerPlugin({
+ fallbackEntry: getFallbackEntry({
+ rootDir,
+ command,
+ fallbackEntry: server?.fallbackEntry,
+ }),
serverRunner,
ensureRoutesConfig,
serverCompiler,
diff --git a/packages/ice/src/bundler/webpack/getWebpackConfig.ts b/packages/ice/src/bundler/webpack/getWebpackConfig.ts
index c086f9c4b6..4c57414eb3 100644
--- a/packages/ice/src/bundler/webpack/getWebpackConfig.ts
+++ b/packages/ice/src/bundler/webpack/getWebpackConfig.ts
@@ -7,7 +7,7 @@ import { getRouteExportConfig } from '../../service/config.js';
import { getFileHash } from '../../utils/hash.js';
import DataLoaderPlugin from '../../webpack/DataLoaderPlugin.js';
import { IMPORT_META_RENDERER, IMPORT_META_TARGET, RUNTIME_TMP_DIR, WEB } from '../../constant.js';
-import { getReCompilePlugin, getServerPlugin, getSpinnerPlugin } from '../config/plugins.js';
+import { getFallbackEntry, getReCompilePlugin, getServerPlugin, getSpinnerPlugin } from '../config/plugins.js';
import type RouteManifest from '../../utils/routeManifest.js';
import type ServerRunnerPlugin from '../../webpack/ServerRunnerPlugin.js';
import type ServerCompilerPlugin from '../../webpack/ServerCompilerPlugin.js';
@@ -93,6 +93,11 @@ const getWebpackConfig: GetWebpackConfig = async (context, options) => {
serverCompiler,
target,
rootDir,
+ fallbackEntry: getFallbackEntry({
+ rootDir,
+ command,
+ fallbackEntry: server?.fallbackEntry,
+ }),
serverEntry: server?.entry,
outputDir,
serverCompileTask,
diff --git a/packages/ice/src/constant.ts b/packages/ice/src/constant.ts
index 16a8f279b0..6f86b923c1 100644
--- a/packages/ice/src/constant.ts
+++ b/packages/ice/src/constant.ts
@@ -5,6 +5,7 @@ export const DEFAULT_HOST = '0.0.0.0';
export const RUNTIME_TMP_DIR = '.ice';
export const SERVER_ENTRY = path.join(RUNTIME_TMP_DIR, 'entry.server.ts');
+export const FALLBACK_ENTRY = 'entry.document.ts';
export const DATA_LOADER_ENTRY = path.join(RUNTIME_TMP_DIR, 'data-loader.ts');
export const SERVER_OUTPUT_DIR = 'server';
export const IMPORT_META_TARGET = 'import.meta.target';
diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts
index a20bbb27ee..4a97092a1d 100644
--- a/packages/ice/src/createService.ts
+++ b/packages/ice/src/createService.ts
@@ -4,7 +4,7 @@ import * as path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import { Context } from 'build-scripts';
-import type { CommandArgs, CommandName } from 'build-scripts';
+import type { CommandArgs, CommandName, TaskConfig } from 'build-scripts';
import type { Config } from '@ice/shared-config/types';
import type { AppConfig } from '@ice/runtime/types';
import webpack from '@ice/bundles/compiled/webpack/index.js';
@@ -23,7 +23,7 @@ import { setEnv, updateRuntimeEnv, getCoreEnvKeys } from './utils/runtimeEnv.js'
import getRuntimeModules from './utils/getRuntimeModules.js';
import { generateRoutesInfo, getRoutesDefinition } from './routes.js';
import * as config from './config.js';
-import { RUNTIME_TMP_DIR, WEB, RUNTIME_EXPORTS, SERVER_ENTRY } from './constant.js';
+import { RUNTIME_TMP_DIR, WEB, RUNTIME_EXPORTS, SERVER_ENTRY, FALLBACK_ENTRY } from './constant.js';
import createSpinner from './utils/createSpinner.js';
import ServerCompileTask from './utils/ServerCompileTask.js';
import { getAppExportConfig, getRouteExportConfig } from './service/config.js';
@@ -215,7 +215,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
ctx.registerConfig(configType, configData);
});
- let taskConfigs = await ctx.setup();
+ let taskConfigs: TaskConfig[] = await ctx.setup();
// get userConfig after setup because of userConfig maybe modified by plugins
const { userConfig } = ctx;
@@ -296,6 +296,11 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt
},
);
+ if (platformTaskConfig.config.server?.fallbackEntry) {
+ // Add fallback entry for server side rendering.
+ generator.addRenderFile('core/entry.server.ts.ejs', FALLBACK_ENTRY, { hydrate: false });
+ }
+
if (typeof userConfig.dataLoader === 'object' && userConfig.dataLoader.fetcher) {
const {
packageName,
diff --git a/packages/ice/src/utils/getServerCompilerPlugin.ts b/packages/ice/src/utils/getServerCompilerPlugin.ts
index a4f3db2fa1..0a5ef5aec5 100644
--- a/packages/ice/src/utils/getServerCompilerPlugin.ts
+++ b/packages/ice/src/utils/getServerCompilerPlugin.ts
@@ -10,6 +10,7 @@ interface Options {
userConfig: UserConfig;
outputDir: string;
serverEntry: string;
+ fallbackEntry?: string;
serverCompileTask: ExtendsPluginAPI['serverCompileTask'];
ensureRoutesConfig: () => Promise;
runtimeDefineVars: Record;
@@ -25,16 +26,24 @@ function getServerCompilerPlugin(serverCompiler: ServerCompiler, options: Option
serverCompileTask,
ensureRoutesConfig,
runtimeDefineVars,
+ fallbackEntry,
entryPoints,
} = options;
const { ssg, ssr, server: { format } } = userConfig;
const isEsm = userConfig?.server?.format === 'esm';
+ const defaultEntryPoints = { index: getServerEntry(rootDir, serverEntry) };
+ if (fallbackEntry) {
+ if (entryPoints) {
+ entryPoints['index.fallback'] = fallbackEntry;
+ }
+ defaultEntryPoints['index.fallback'] = fallbackEntry;
+ }
return new ServerCompilerPlugin(
serverCompiler,
[
{
- entryPoints: entryPoints || { index: getServerEntry(rootDir, serverEntry) },
+ entryPoints: entryPoints || defaultEntryPoints,
outdir: path.join(outputDir, SERVER_OUTPUT_DIR),
splitting: isEsm,
format,
diff --git a/packages/ice/src/webpack/ServerCompilerPlugin.ts b/packages/ice/src/webpack/ServerCompilerPlugin.ts
index a4b62f7ce9..09a0569057 100644
--- a/packages/ice/src/webpack/ServerCompilerPlugin.ts
+++ b/packages/ice/src/webpack/ServerCompilerPlugin.ts
@@ -36,7 +36,6 @@ export default class ServerCompilerPlugin {
public compileTask = async (compilation?: Compilation) => {
const [buildOptions] = this.serverCompilerOptions;
if (!this.isCompiling) {
- await this.ensureRoutesConfig();
if (compilation) {
// Option of compilationInfo need to be object, while it may changed during multi-time compilation.
this.compilerOptions.compilationInfo.assetsManifest =
diff --git a/packages/ice/src/webpack/ServerRunnerPlugin.ts b/packages/ice/src/webpack/ServerRunnerPlugin.ts
index 8297c32046..4a534e4c0e 100644
--- a/packages/ice/src/webpack/ServerRunnerPlugin.ts
+++ b/packages/ice/src/webpack/ServerRunnerPlugin.ts
@@ -19,7 +19,6 @@ export default class ServerRunnerPlugin {
public compileTask = async (compilation?: Compilation) => {
if (!this.isCompiling) {
- await this.ensureRoutesConfig();
if (compilation) {
// Option of compilationInfo need to be object, while it may changed during multi-time compilation.
this.serverRunner.addCompileData({
diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs
index c4ae2cfe13..2fd517eac9 100644
--- a/packages/ice/templates/core/entry.server.ts.ejs
+++ b/packages/ice/templates/core/entry.server.ts.ejs
@@ -1,6 +1,10 @@
import './env.server';
+<% if (hydrate) {-%>
+import { getAppConfig, renderToHTML as renderAppToHTML, renderToResponse as renderAppToResponse } from '@ice/runtime/server';
+<% } else { -%>
+import { getAppConfig, getDocumentResponse as renderAppToHTML, renderDocumentToResponse as renderAppToResponse } from '@ice/runtime/server';
+<% }-%>
<%- entryServer.imports %>
-import * as runtime from '@ice/runtime/server';
<% if (hydrate) {-%>
import { commons, statics } from './runtime-modules';
<% }-%>
@@ -19,7 +23,6 @@ import createRoutes from '<%- routesFile %>';
<% } else { -%>
import routesManifest from './route-manifest.json';
<% } -%>
-import routesConfig from './routes-config.bundle.mjs';
<% if (dataLoaderImport.imports) {-%><%-dataLoaderImport.imports%><% } -%>
<% if (hydrate) {-%><%- runtimeOptions.imports %><% } -%>
@@ -32,10 +35,11 @@ const createRoutes = () => routesManifest;
const runtimeModules = { commons, statics };
const getRouterBasename = () => {
- const appConfig = runtime.getAppConfig(app);
+ const appConfig = getAppConfig(app);
return appConfig?.router?.basename ?? <%- basename %> ?? '';
}
+<% if (hydrate) {-%>
const setRuntimeEnv = (renderMode) => {
if (renderMode === 'SSG') {
process.env.ICE_CORE_SSG = 'true';
@@ -43,6 +47,7 @@ const setRuntimeEnv = (renderMode) => {
process.env.ICE_CORE_SSR = 'true';
}
}
+<% } -%>
interface RenderOptions {
documentOnly?: boolean;
@@ -57,19 +62,21 @@ interface RenderOptions {
}
export async function renderToHTML(requestContext, options: RenderOptions = {}) {
+<% if (hydrate) {-%>
const { renderMode = 'SSR' } = options;
setRuntimeEnv(renderMode);
-
+<% }-%>
const mergedOptions = mergeOptions(options);
- return await runtime.renderToHTML(requestContext, mergedOptions);
+ return await renderAppToHTML(requestContext, mergedOptions);
}
export async function renderToResponse(requestContext, options: RenderOptions = {}) {
+<% if (hydrate) {-%>
const { renderMode = 'SSR' } = options;
setRuntimeEnv(renderMode);
-
+<% }-%>
const mergedOptions = mergeOptions(options);
- return runtime.renderToResponse(requestContext, mergedOptions);
+ return renderAppToResponse(requestContext, mergedOptions);
}
function mergeOptions(options) {
@@ -89,16 +96,13 @@ function mergeOptions(options) {
Document: Document.default,
basename: basename || getRouterBasename(),
renderMode,
- routesConfig,
- <% if (hydrate) {-%>
- runtimeOptions: {
- <% if (runtimeOptions.exports) { -%>
+ <% if (hydrate) {-%>runtimeOptions: {
+<% if (runtimeOptions.exports) { -%>
<%- runtimeOptions.exports %>
- <% } -%>
- <% if (locals.customRuntimeOptions) { _%>
- ...<%- JSON.stringify(customRuntimeOptions) %>,
- <% } _%>
+<% } -%>
+<% if (locals.customRuntimeOptions) { -%>
+ ...<%- JSON.stringify(customRuntimeOptions) %>,
+<% } -%>
},
- <% } -%>
- };
+ <% } -%>};
}
diff --git a/packages/plugin-externals/CHANGELOG.md b/packages/plugin-externals/CHANGELOG.md
new file mode 100644
index 0000000000..20e52887c8
--- /dev/null
+++ b/packages/plugin-externals/CHANGELOG.md
@@ -0,0 +1,5 @@
+# @ice/plugin-externals
+
+## 1.0.0
+
+- Initial release
diff --git a/packages/plugin-externals/README.md b/packages/plugin-externals/README.md
new file mode 100644
index 0000000000..7f2e170c85
--- /dev/null
+++ b/packages/plugin-externals/README.md
@@ -0,0 +1,45 @@
+# @ice/plugin-externals
+
+`@ice/plugin-externals` is a ice.js plugin. It provides a simple way to add externals support to your application.
+
+## Install
+
+```bash
+$ npm i @ice/plugin-externals --save-dev
+```
+
+## Usage
+
+Set preset `react` to external react in a easy way.
+
+```js
+import { defineConfig } from '@ice/app';
+import externals from '@ice/plugin-externals';
+
+export default defineConfig(() => ({
+ plugins: [externals({ preset: 'react' })]
+}));
+```
+
+Framework will auto add externals of `react` and `react-dom` to your application, and the cdn url will be inject to the document by default.
+
+Also, you can custom externals and cdn url by yourself:
+
+```js
+import { defineConfig } from '@ice/app';
+import externals from '@ice/plugin-externals';
+
+export default defineConfig(() => ({
+ plugins: [externals({
+ externals: {
+ antd: 'Antd',
+ },
+ cdnMap: {
+ antd: {
+ development: 'https://unpkg.com/antd/dist/antd.js',
+ production: 'https://unpkg.com/antd/dist/antd.min.js',
+ }
+ }
+ })]
+}));
+```
diff --git a/packages/plugin-externals/package.json b/packages/plugin-externals/package.json
new file mode 100644
index 0000000000..4f0ca006ac
--- /dev/null
+++ b/packages/plugin-externals/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@ice/plugin-externals",
+ "version": "1.0.0",
+ "description": "plugin to make externals much easier in ice.js",
+ "files": [
+ "esm",
+ "!esm/**/*.map",
+ "*.d.ts"
+ ],
+ "type": "module",
+ "main": "esm/index.js",
+ "module": "esm/index.js",
+ "types": "esm/index.d.ts",
+ "exports": {
+ ".": "./esm/index.js"
+ },
+ "sideEffects": false,
+ "scripts": {
+ "watch": "tsc -w --sourceMap",
+ "build": "tsc"
+ },
+ "devDependencies": {
+ "@ice/app": "^3.3.2",
+ "@ice/runtime": "^1.2.9",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "webpack": "^5.88.0"
+ },
+ "repository": {
+ "type": "http",
+ "url": "https://github.com/alibaba/ice/tree/master/packages/plugin-externals"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/plugin-externals/src/index.ts b/packages/plugin-externals/src/index.ts
new file mode 100644
index 0000000000..97c6a65b4d
--- /dev/null
+++ b/packages/plugin-externals/src/index.ts
@@ -0,0 +1,80 @@
+import type { Plugin } from '@ice/app/types';
+import InjectExternalScriptsWebpackPlugin from './webpack-plugin.js';
+
+const PLUGIN_NAME = '@ice/plugin-externals';
+
+type Preset = 'react';
+interface PluginOptions {
+ preset?: Preset | Preset[];
+ externals?: Record;
+ cdnMap?: Record;
+}
+
+const plugin: Plugin = (options: PluginOptions) => ({
+ name: PLUGIN_NAME,
+ setup: ({ onGetConfig, context }) => {
+ const { command } = context;
+ const reactExternals = {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ };
+ const reactCDN = {
+ react: {
+ development: 'https://g.alicdn.com/code/lib/react/18.3.1/umd/react.development.js',
+ production: 'https://g.alicdn.com/code/lib/react/18.3.1/umd/react.production.min.js',
+ },
+ 'react-dom': {
+ development: 'https://g.alicdn.com/code/lib/react-dom/18.3.1/umd/react-dom.development.js',
+ production: 'https://g.alicdn.com/code/lib/react-dom/18.3.1/umd/react-dom.production.min.js',
+ },
+ };
+ onGetConfig((config) => {
+ config.configureWebpack ??= [];
+ config.configureWebpack.push((webpackConfig) => {
+ let externals = options.externals || {};
+ let cdnMap = options.cdnMap || {};
+ if (options.preset && options.preset === 'react') {
+ switch (options.preset) {
+ case 'react':
+ externals = {
+ ...reactExternals,
+ ...externals,
+ };
+ cdnMap = {
+ ...reactCDN,
+ ...cdnMap,
+ };
+ break;
+ }
+ }
+
+ if (!webpackConfig.externals) {
+ webpackConfig.externals = externals;
+ } else if (typeof webpackConfig.externals === 'object') {
+ webpackConfig.externals = {
+ ...webpackConfig.externals,
+ ...externals,
+ };
+ }
+ const cdnList = [];
+ Object.keys(cdnMap).forEach((key) => {
+ const url = command === 'start' ? cdnMap[key].development : cdnMap[key].production;
+ const urls = Array.isArray(url) ? url : [url];
+ cdnList.push(...urls);
+ });
+ if (cdnList.length > 0) {
+ // @ts-ignore missmatch type becasue of webpack prebundled.
+ webpackConfig.plugins.push(new InjectExternalScriptsWebpackPlugin({
+ externals: cdnList,
+ }));
+ }
+ return webpackConfig;
+ });
+ });
+ },
+});
+
+export default plugin;
diff --git a/packages/plugin-externals/src/webpack-plugin.ts b/packages/plugin-externals/src/webpack-plugin.ts
new file mode 100644
index 0000000000..29a0595c61
--- /dev/null
+++ b/packages/plugin-externals/src/webpack-plugin.ts
@@ -0,0 +1,39 @@
+import webpack from 'webpack';
+import type { Compiler } from 'webpack';
+
+const ASSET_MANIFEST_JSON_NAME = 'assets-manifest.json';
+
+interface PluginOptions {
+ externals: string[];
+}
+
+export default class InjectExternalScriptsWebpackPlugin {
+ private options: PluginOptions;
+
+ constructor(options: PluginOptions) {
+ this.options = options;
+ }
+
+ apply(compiler: Compiler) {
+ compiler.hooks.make.tap('InjectExternalScriptsWebpackPlugin', (compilation) => {
+ compilation.hooks.processAssets.tap(
+ {
+ name: 'InjectExternalScriptsWebpackPlugin',
+ stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
+ },
+ () => {
+ const assetsManifest = compilation.assets[ASSET_MANIFEST_JSON_NAME];
+ if (assetsManifest) {
+ const json = JSON.parse(assetsManifest.source().toString());
+ delete compilation.assets[ASSET_MANIFEST_JSON_NAME];
+ json.entries.main.unshift(...this.options.externals);
+ compilation.emitAsset(
+ ASSET_MANIFEST_JSON_NAME,
+ new webpack.sources.RawSource(JSON.stringify(json)),
+ );
+ }
+ },
+ );
+ });
+ }
+}
diff --git a/packages/plugin-externals/tsconfig.json b/packages/plugin-externals/tsconfig.json
new file mode 100644
index 0000000000..d48d48a70c
--- /dev/null
+++ b/packages/plugin-externals/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "baseUrl": "./",
+ "rootDir": "src",
+ "outDir": "esm",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext"
+ },
+ "include": ["src"]
+}
diff --git a/packages/plugin-i18n/package.json b/packages/plugin-i18n/package.json
index b9a5a7c0f4..48343b3ff8 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.9",
- "@ice/runtime": "^1.4.8"
+ "@ice/app": "^3.4.10",
+ "@ice/runtime": "^1.4.10"
},
"publishConfig": {
"access": "public"
diff --git a/packages/plugin-rax-compat/CHANGELOG.md b/packages/plugin-rax-compat/CHANGELOG.md
index 493bbfd99c..880b297e2a 100644
--- a/packages/plugin-rax-compat/CHANGELOG.md
+++ b/packages/plugin-rax-compat/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 0.3.2
+
+### Patch Changes
+
+- Updated dependencies [9926faae]
+ - rax-compat@0.3.0
+
## 0.3.1
### Patch Changes
diff --git a/packages/plugin-rax-compat/package.json b/packages/plugin-rax-compat/package.json
index b624c28411..21211ba626 100644
--- a/packages/plugin-rax-compat/package.json
+++ b/packages/plugin-rax-compat/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/plugin-rax-compat",
- "version": "0.3.1",
+ "version": "0.3.2",
"description": "Provide rax compat support for ice.js",
"license": "MIT",
"type": "module",
@@ -25,12 +25,12 @@
"consola": "^2.15.3",
"css": "^2.2.1",
"lodash-es": "^4.17.21",
- "rax-compat": "^0.2.10",
+ "rax-compat": "^0.3.0",
"style-unit": "^3.0.5",
"stylesheet-loader": "^0.9.1"
},
"devDependencies": {
- "@ice/app": "^3.4.8",
+ "@ice/app": "^3.4.10",
"@types/lodash-es": "^4.17.7",
"webpack": "^5.88.0"
},
diff --git a/packages/rax-compat/.gitignore b/packages/rax-compat/.gitignore
index eb97fce6bd..7f2fa9cfec 100644
--- a/packages/rax-compat/.gitignore
+++ b/packages/rax-compat/.gitignore
@@ -1,3 +1,4 @@
es2017/
+es2021/
dist/
esm/
diff --git a/packages/rax-compat/CHANGELOG.md b/packages/rax-compat/CHANGELOG.md
index eeed96e36d..e490edff14 100644
--- a/packages/rax-compat/CHANGELOG.md
+++ b/packages/rax-compat/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.3.0
+
+### Minor Changes
+
+- 9926faae: feat: export es2021 output
+
## 0.2.12
### Patch Changes
diff --git a/packages/rax-compat/build.config.mts b/packages/rax-compat/build.config.mts
index 0bc2d21eaf..e0e61e1530 100644
--- a/packages/rax-compat/build.config.mts
+++ b/packages/rax-compat/build.config.mts
@@ -5,4 +5,7 @@ export default defineConfig({
formats: ['esm', 'es2017'],
},
sourceMaps: process.env.NODE_ENV === 'development',
+ plugins: [
+ './plugin.mjs',
+ ],
});
diff --git a/packages/rax-compat/package.json b/packages/rax-compat/package.json
index 9c1ae0d9f1..d45e7b1853 100644
--- a/packages/rax-compat/package.json
+++ b/packages/rax-compat/package.json
@@ -1,31 +1,81 @@
{
"name": "rax-compat",
- "version": "0.2.12",
+ "version": "0.3.0",
"description": "Rax compatible mode, running rax project on the react runtime.",
"files": [
"esm",
- "cjs",
"es2017",
- "dist",
- "build"
+ "es2021"
],
"type": "module",
"main": "esm/index.js",
"module": "esm/index.js",
"exports": {
- ".": "./esm/index.js",
- "./children": "./esm/children.js",
- "./clone-element": "./esm/clone-element.js",
- "./create-class": "./esm/create-class.js",
- "./create-factory": "./esm/create-factory.js",
- "./create-portal": "./esm/create-portal.js",
- "./find-dom-node": "./esm/find-dom-node.js",
- "./is-valid-element": "./esm/is-valid-element.js",
- "./unmount-component-at-node": "./esm/unmount-component-at-node.js",
- "./runtime": "./esm/runtime/index.js",
- "./runtime/jsx-dev-runtime": "./esm/runtime/jsx-dev-runtime.js",
- "./runtime/jsx-runtime": "./esm/runtime/jsx-runtime.js",
- "./es2017": "./es2017/index.js"
+ ".": {
+ "es2021": "./es2021/index.js",
+ "es2017": "./es2017/index.js",
+ "default": "./esm/index.js"
+ },
+ "./children": {
+ "es2021": "./es2021/children.js",
+ "es2017": "./es2017/children.js",
+ "default": "./esm/children.js"
+ },
+ "./clone-element": {
+ "es2021": "./es2021/clone-element.js",
+ "es2017": "./es2017/clone-element.js",
+ "default": "./esm/clone-element.js"
+ },
+ "./create-class": {
+ "es2021": "./es2021/create-class.js",
+ "es2017": "./es2017/create-class.js",
+ "default": "./esm/create-class.js"
+ },
+ "./create-factory": {
+ "es2021": "./es2021/create-factory.js",
+ "es2017": "./es2017/create-factory.js",
+ "default": "./esm/create-factory.js"
+ },
+ "./create-portal": {
+ "es2021": "./es2021/create-portal.js",
+ "es2017": "./es2017/create-portal.js",
+ "default": "./esm/create-portal.js"
+ },
+ "./find-dom-node": {
+ "es2021": "./es2021/find-dom-node.js",
+ "es2017": "./es2017/find-dom-node.js",
+ "default": "./esm/find-dom-node.js"
+ },
+ "./is-valid-element": {
+ "es2021": "./es2021/is-valid-element.js",
+ "es2017": "./es2017/is-valid-element.js",
+ "default": "./esm/is-valid-element.js"
+ },
+ "./unmount-component-at-node": {
+ "es2021": "./es2021/unmount-component-at-node.js",
+ "es2017": "./es2017/unmount-component-at-node.js",
+ "default": "./esm/unmount-component-at-node.js"
+ },
+ "./runtime": {
+ "es2021": "./es2021/runtime/index.js",
+ "es2017": "./es2017/runtime/index.js",
+ "default": "./esm/runtime/index.js"
+ },
+ "./runtime/jsx-dev-runtime": {
+ "es2021": "./es2021/runtime/jsx-dev-runtime.js",
+ "es2017": "./es2017/runtime/jsx-dev-runtime.js",
+ "default": "./esm/runtime/jsx-dev-runtime.js"
+ },
+ "./runtime/jsx-runtime": {
+ "es2021": "./es2021/runtime/jsx-runtime.js",
+ "es2017": "./es2017/runtime/jsx-runtime.js",
+ "default": "./esm/runtime/jsx-runtime.js"
+ },
+ "./es2017": {
+ "es2021": "./es2021/index.js",
+ "es2017": "./es2017/index.js",
+ "default": "./esm/index.js"
+ }
},
"sideEffects": [
"dist/*",
@@ -77,4 +127,4 @@
"author": "ice-admin@alibaba-inc.com",
"license": "MIT",
"homepage": "https://github.com/alibaba/ice#readme"
-}
\ No newline at end of file
+}
diff --git a/packages/rax-compat/plugin.mjs b/packages/rax-compat/plugin.mjs
new file mode 100644
index 0000000000..32fd9379ab
--- /dev/null
+++ b/packages/rax-compat/plugin.mjs
@@ -0,0 +1,18 @@
+/**
+ * @type {import('@ice/pkg').Plugin}
+ */
+const plugin = (api) => {
+ api.registerTask('transform-es2021', {
+ type: 'transform',
+ formats: ['es2021'],
+ outputDir: 'es2021',
+ modifySwcCompileOptions: (options => {
+ options.jsc.target = 'es2021';
+ return options;
+ }),
+ entry: 'src/index',
+ sourcemap: false,
+ });
+};
+
+export default plugin;
diff --git a/packages/rspack-config/CHANGELOG.md b/packages/rspack-config/CHANGELOG.md
index 1a080b497d..507fba5c98 100644
--- a/packages/rspack-config/CHANGELOG.md
+++ b/packages/rspack-config/CHANGELOG.md
@@ -1,5 +1,12 @@
# @ice/rspack-config
+## 1.1.8
+
+### Patch Changes
+
+- Updated dependencies [15c8200f]
+ - @ice/shared-config@1.2.8
+
## 1.1.7
### Patch Changes
diff --git a/packages/rspack-config/package.json b/packages/rspack-config/package.json
index aa27d49a95..7de62a4ae7 100644
--- a/packages/rspack-config/package.json
+++ b/packages/rspack-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/rspack-config",
- "version": "1.1.7",
+ "version": "1.1.8",
"repository": "alibaba/ice",
"bugs": "https://github.com/alibaba/ice/issues",
"homepage": "https://v3.ice.work",
@@ -16,7 +16,7 @@
],
"dependencies": {
"@ice/bundles": "0.2.6",
- "@ice/shared-config": "1.2.7"
+ "@ice/shared-config": "1.2.8"
},
"devDependencies": {
"@rspack/core": "0.5.7"
diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md
index 96e812545a..a08ab23c26 100644
--- a/packages/runtime/CHANGELOG.md
+++ b/packages/runtime/CHANGELOG.md
@@ -1,5 +1,12 @@
# @ice/runtime
+## 1.4.10
+
+### Patch Changes
+
+- 15c8200f: feat: support build additional server entry for fallback
+- d073ee5a: fix: support cdn url in assets manifest
+
## 1.4.9
Fix: add export of useAsyncValue in single route mode
diff --git a/packages/runtime/package.json b/packages/runtime/package.json
index 1bd960900d..75486586ca 100644
--- a/packages/runtime/package.json
+++ b/packages/runtime/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/runtime",
- "version": "1.4.9",
+ "version": "1.4.10",
"description": "Runtime module for ice.js",
"type": "module",
"types": "./esm/index.d.ts",
diff --git a/packages/runtime/server.d.ts b/packages/runtime/server.d.ts
new file mode 100644
index 0000000000..03f36ef707
--- /dev/null
+++ b/packages/runtime/server.d.ts
@@ -0,0 +1 @@
+export * from './esm/index.server';
diff --git a/packages/runtime/src/AppContext.tsx b/packages/runtime/src/AppContext.tsx
index ff05e7fdbf..4aff665a9c 100644
--- a/packages/runtime/src/AppContext.tsx
+++ b/packages/runtime/src/AppContext.tsx
@@ -10,7 +10,7 @@ function useAppContext() {
return value;
}
-function useAppData() {
+function useAppData(): T {
const value = React.useContext(Context);
return value.appData;
}
diff --git a/packages/runtime/src/Document.tsx b/packages/runtime/src/Document.tsx
index 740b36d2a6..53827764d6 100644
--- a/packages/runtime/src/Document.tsx
+++ b/packages/runtime/src/Document.tsx
@@ -250,5 +250,12 @@ export function getEntryAssets(assetsManifest: AssetsManifest): string[] {
result = result.concat(assets);
});
- return result.map(filePath => `${publicPath}${filePath}`);
+ return result.map((filePath: string) => {
+ const prefixes = ['http:', 'https:', '//'];
+ if (prefixes.some(prefix => filePath.startsWith(prefix))) {
+ return filePath;
+ } else {
+ return `${publicPath}${filePath}`;
+ }
+ });
}
diff --git a/packages/runtime/src/index.server.ts b/packages/runtime/src/index.server.ts
index 50b818a950..ce055ee921 100644
--- a/packages/runtime/src/index.server.ts
+++ b/packages/runtime/src/index.server.ts
@@ -1,2 +1,3 @@
export { renderToResponse, renderToHTML } from './runServerApp.js';
+export { renderToResponse as renderDocumentToResponse, getDocumentResponse } from './renderDocument.js';
export * from './index.js';
diff --git a/packages/runtime/src/renderDocument.tsx b/packages/runtime/src/renderDocument.tsx
new file mode 100644
index 0000000000..87eb3f8bb8
--- /dev/null
+++ b/packages/runtime/src/renderDocument.tsx
@@ -0,0 +1,140 @@
+import * as React from 'react';
+import * as ReactDOMServer from 'react-dom/server';
+import getAppConfig from './appConfig.js';
+import { AppContextProvider } from './AppContext.js';
+import { DocumentContextProvider } from './Document.js';
+import addLeadingSlash from './utils/addLeadingSlash.js';
+import getRequestContext from './requestContext.js';
+import matchRoutes from './matchRoutes.js';
+import getDocumentData from './server/getDocumentData.js';
+import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
+import { sendResponse, getLocation } from './server/response.js';
+
+import type {
+ AppContext,
+ RouteItem,
+ RouteMatch,
+ RenderOptions,
+ Response,
+ ServerContext,
+} from './types.js';
+
+interface RenderDocumentOptions {
+ matches: RouteMatch[];
+ renderOptions: RenderOptions;
+ routes: RouteItem[];
+ documentData: any;
+ routePath?: string;
+ downgrade?: boolean;
+}
+
+export function renderDocument(options: RenderDocumentOptions): Response {
+ const {
+ matches,
+ renderOptions,
+ routePath = '',
+ downgrade,
+ routes,
+ documentData,
+ }: RenderDocumentOptions = options;
+
+ const {
+ assetsManifest,
+ app,
+ Document,
+ basename,
+ routesConfig = {},
+ serverData,
+ } = renderOptions;
+
+ const appData = null;
+ const appConfig = getAppConfig(app);
+
+ const loaderData = {};
+ matches.forEach(async (match) => {
+ const { id } = match.route;
+ const pageConfig = routesConfig[id];
+
+ loaderData[id] = {
+ pageConfig: pageConfig ? pageConfig({}) : {},
+ };
+ });
+
+ const appContext: AppContext = {
+ assetsManifest,
+ appConfig,
+ appData,
+ loaderData,
+ matches,
+ routes,
+ documentOnly: true,
+ renderMode: 'CSR',
+ routePath,
+ basename,
+ downgrade,
+ serverData,
+ documentData,
+ };
+
+ const documentContext = {
+ main: null,
+ };
+
+ const htmlStr = ReactDOMServer.renderToString(
+
+
+ {
+ Document &&
+ }
+
+ ,
+ );
+
+ return {
+ value: `${htmlStr}`,
+ headers: {
+ 'Content-Type': 'text/html; charset=utf-8',
+ },
+ statusCode: 200,
+ };
+}
+
+export async function getDocumentResponse(
+ serverContext: ServerContext,
+ renderOptions: RenderOptions,
+): Promise {
+ const { req } = serverContext;
+ const {
+ app,
+ basename,
+ serverOnlyBasename,
+ createRoutes,
+ documentOnly,
+ renderMode,
+ } = renderOptions;
+ const finalBasename = addLeadingSlash(serverOnlyBasename || basename);
+ const location = getLocation(req.url);
+ const requestContext = getRequestContext(location, serverContext);
+ const appConfig = getAppConfig(app);
+ const routes = createRoutes({
+ requestContext,
+ renderMode,
+ });
+ const documentData = await getDocumentData({
+ loaderConfig: renderOptions.documentDataLoader,
+ requestContext,
+ documentOnly,
+ });
+ const matches = appConfig?.router?.type === 'hash' ? [] : matchRoutes(routes, location, finalBasename);
+ const routePath = getCurrentRoutePath(matches);
+ return renderDocument({ matches, routePath, routes, renderOptions, documentData });
+}
+
+export async function renderToResponse(
+ requestContext: ServerContext,
+ renderOptions: RenderOptions,
+) {
+ const { req, res } = requestContext;
+ const documentResoponse = await getDocumentResponse(requestContext, renderOptions);
+ sendResponse(req, res, documentResoponse);
+}
diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx
index 00a8f79439..a61b40c207 100644
--- a/packages/runtime/src/runServerApp.tsx
+++ b/packages/runtime/src/runServerApp.tsx
@@ -1,22 +1,14 @@
-import type { ServerResponse, IncomingMessage } from 'http';
import * as React from 'react';
-import * as ReactDOMServer from 'react-dom/server';
import type { Location } from 'history';
-import { parsePath } from 'history';
-import { isFunction } from '@ice/shared';
-import type { RenderToPipeableStreamOptions, OnAllReadyParams, NodeWritablePiper } from './server/streamRender.js';
+import type { OnAllReadyParams } from './server/streamRender.js';
import type {
- AppContext, RouteItem, ServerContext,
- AppExport,
- AssetsManifest,
+ AppContext,
+ ServerContext,
RouteMatch,
- PageConfig,
- RenderMode,
- DocumentComponent,
- RuntimeModules,
AppData,
ServerAppRouterProps,
- DocumentDataLoaderConfig,
+ RenderOptions,
+ Response,
} from './types.js';
import Runtime from './runtime.js';
import { AppContextProvider } from './AppContext.js';
@@ -24,48 +16,15 @@ import { getAppData } from './appData.js';
import getAppConfig from './appConfig.js';
import { DocumentContextProvider } from './Document.js';
import { loadRouteModules } from './routes.js';
-import type { RouteLoaderOptions } from './routes.js';
import { pipeToString, renderToNodeStream } from './server/streamRender.js';
import getRequestContext from './requestContext.js';
import matchRoutes from './matchRoutes.js';
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
import ServerRouter from './ServerRouter.js';
import addLeadingSlash from './utils/addLeadingSlash.js';
-
-export interface RenderOptions {
- app: AppExport;
- assetsManifest: AssetsManifest;
- createRoutes: (options: Pick) => RouteItem[];
- runtimeModules: RuntimeModules;
- documentDataLoader?: DocumentDataLoaderConfig;
- Document?: DocumentComponent;
- documentOnly?: boolean;
- preRender?: boolean;
- renderMode?: RenderMode;
- // basename is used both for server and client, once set, it will be sync to client.
- basename?: string;
- // serverOnlyBasename is used when just want to change basename for server.
- serverOnlyBasename?: string;
- routePath?: string;
- disableFallback?: boolean;
- routesConfig: {
- [key: string]: PageConfig;
- };
- runtimeOptions?: Record;
- serverData?: any;
- streamOptions?: RenderToPipeableStreamOptions;
-}
-
-interface Piper {
- pipe: NodeWritablePiper;
- fallback: Function;
-}
-interface Response {
- statusCode?: number;
- statusText?: string;
- value?: string | Piper;
- headers?: Record;
-}
+import { renderDocument } from './renderDocument.js';
+import { sendResponse, getLocation } from './server/response.js';
+import getDocumentData from './server/getDocumentData.js';
/**
* Render and return the result as html string.
@@ -161,23 +120,6 @@ export async function renderToResponse(requestContext: ServerContext, renderOpti
}
}
-async function sendResponse(
- req: IncomingMessage,
- res: ServerResponse,
- response: Response,
-) {
- res.statusCode = response.statusCode;
- res.statusMessage = response.statusText;
- Object.entries(response.headers || {}).forEach(([name, value]) => {
- res.setHeader(name, value);
- });
- if (response.value && req.method !== 'HEAD') {
- res.end(response.value);
- } else {
- res.end();
- }
-}
-
function needRevalidate(matchedRoutes: RouteMatch[]) {
return matchedRoutes.some(({ route }) => route.exports.includes('dataLoader') && route.exports.includes('staticDataLoader'));
}
@@ -227,18 +169,13 @@ async function doRender(serverContext: ServerContext, renderOptions: RenderOptio
await Promise.all(runtimeModules.statics.map(m => runtime.loadModule(m)).filter(Boolean));
}
- // Execute document dataLoader.
- let documentData: any;
- if (renderOptions.documentDataLoader) {
- const { loader } = renderOptions.documentDataLoader;
- if (isFunction(loader)) {
- documentData = await loader(requestContext, { documentOnly });
- // @TODO: document should have it's own context, not shared with app.
- appContext.documentData = documentData;
- } else {
- console.warn('Document dataLoader only accepts function.');
- }
- }
+ const documentData = await getDocumentData({
+ loaderConfig: renderOptions.documentDataLoader,
+ requestContext,
+ documentOnly,
+ });
+ // @TODO: document should have it's own context, not shared with app.
+ appContext.documentData = documentData;
// Not to execute [getAppData] when CSR.
if (!documentOnly) {
@@ -400,105 +337,3 @@ async function renderServerEntry(
},
};
}
-
-interface RenderDocumentOptions {
- matches: RouteMatch[];
- renderOptions: RenderOptions;
- routes: RouteItem[];
- documentData: any;
- routePath?: string;
- downgrade?: boolean;
-}
-
-/**
- * Render Document for CSR.
- */
-function renderDocument(options: RenderDocumentOptions): Response {
- const {
- matches,
- renderOptions,
- routePath = '',
- downgrade,
- routes,
- documentData,
- }: RenderDocumentOptions = options;
-
- const {
- assetsManifest,
- app,
- Document,
- basename,
- routesConfig = {},
- serverData,
- } = renderOptions;
-
- const appData = null;
- const appConfig = getAppConfig(app);
-
- const loaderData = {};
- matches.forEach(async (match) => {
- const { id } = match.route;
- const pageConfig = routesConfig[id];
-
- loaderData[id] = {
- pageConfig: pageConfig ? pageConfig({}) : {},
- };
- });
-
- const appContext: AppContext = {
- assetsManifest,
- appConfig,
- appData,
- loaderData,
- matches,
- routes,
- documentOnly: true,
- renderMode: 'CSR',
- routePath,
- basename,
- downgrade,
- serverData,
- documentData,
- };
-
- const documentContext = {
- main: null,
- };
-
- const htmlStr = ReactDOMServer.renderToString(
-
-
- {
- Document &&
- }
-
- ,
- );
-
- return {
- value: `${htmlStr}`,
- headers: {
- 'Content-Type': 'text/html; charset=utf-8',
- },
- statusCode: 200,
- };
-}
-
-/**
- * ref: https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/server.tsx
- */
-const REGEXP_WITH_HOSTNAME = /^https?:\/\/[^/]+/i;
-function getLocation(url: string) {
- // In case of invalid URL, provide a default base url.
- const locationPath = url.replace(REGEXP_WITH_HOSTNAME, '') || '/';
- const locationProps = parsePath(locationPath);
- const location: Location = {
- pathname: locationProps.pathname || '/',
- search: locationProps.search || '',
- hash: locationProps.hash || '',
- state: null,
- key: 'default',
- };
-
- return location;
-}
diff --git a/packages/runtime/src/server/getDocumentData.ts b/packages/runtime/src/server/getDocumentData.ts
new file mode 100644
index 0000000000..875eceaf39
--- /dev/null
+++ b/packages/runtime/src/server/getDocumentData.ts
@@ -0,0 +1,19 @@
+import type { DocumentDataLoaderConfig, RequestContext } from '../types.js';
+
+interface Options {
+ loaderConfig: DocumentDataLoaderConfig;
+ requestContext: RequestContext;
+ documentOnly: boolean;
+}
+
+export default async function getDocumentData(options: Options) {
+ const { loaderConfig, requestContext, documentOnly } = options;
+ if (loaderConfig) {
+ const { loader } = loaderConfig;
+ if (typeof loader === 'function') {
+ return await loader(requestContext, { documentOnly });
+ } else {
+ console.warn('Document dataLoader only accepts function.');
+ }
+ }
+}
diff --git a/packages/runtime/src/server/navigator.ts b/packages/runtime/src/server/navigator.ts
deleted file mode 100644
index 159afe4c53..0000000000
--- a/packages/runtime/src/server/navigator.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { createPath } from 'history';
-import type { To } from 'history';
-
-export function createStaticNavigator() {
- return {
- createHref(to: To) {
- return typeof to === 'string' ? to : createPath(to);
- },
- push(to: To) {
- throw new Error(
- 'You cannot use navigator.push() on the server because it is a stateless ' +
- 'environment. This error was probably triggered when you did a ' +
- `\`navigate(${JSON.stringify(to)})\` somewhere in your app.`,
- );
- },
- replace(to: To) {
- throw new Error(
- 'You cannot use navigator.replace() on the server because it is a stateless ' +
- 'environment. This error was probably triggered when you did a ' +
- `\`navigate(${JSON.stringify(to)}, { replace: true })\` somewhere ` +
- 'in your app.',
- );
- },
- go(delta: number) {
- throw new Error(
- 'You cannot use navigator.go() on the server because it is a stateless ' +
- 'environment. This error was probably triggered when you did a ' +
- `\`navigate(${delta})\` somewhere in your app.`,
- );
- },
- back() {
- throw new Error(
- 'You cannot use navigator.back() on the server because it is a stateless ' +
- 'environment.',
- );
- },
- forward() {
- throw new Error(
- 'You cannot use navigator.forward() on the server because it is a stateless ' +
- 'environment.',
- );
- },
- block() {
- throw new Error(
- 'You cannot use navigator.block() on the server because it is a stateless ' +
- 'environment.',
- );
- },
- };
-}
\ No newline at end of file
diff --git a/packages/runtime/src/server/response.ts b/packages/runtime/src/server/response.ts
new file mode 100644
index 0000000000..cb081fd58d
--- /dev/null
+++ b/packages/runtime/src/server/response.ts
@@ -0,0 +1,43 @@
+import type { ServerResponse, IncomingMessage } from 'http';
+import { parsePath } from 'history';
+import type { Location } from 'history';
+import type {
+ Response,
+} from '../types.js';
+
+export async function sendResponse(
+ req: IncomingMessage,
+ res: ServerResponse,
+ response: Response,
+) {
+ res.statusCode = response.statusCode;
+ res.statusMessage = response.statusText;
+ Object.entries(response.headers || {}).forEach(([name, value]) => {
+ res.setHeader(name, value);
+ });
+ if (response.value && req.method !== 'HEAD') {
+ res.end(response.value);
+ } else {
+ res.end();
+ }
+}
+
+/**
+ * ref: https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/server.tsx
+ */
+const REGEXP_WITH_HOSTNAME = /^https?:\/\/[^/]+/i;
+export function getLocation(url: string) {
+ // In case of invalid URL, provide a default base url.
+ const locationPath = url.replace(REGEXP_WITH_HOSTNAME, '') || '/';
+ const locationProps = parsePath(locationPath);
+ const location: Location = {
+ pathname: locationProps.pathname || '/',
+ search: locationProps.search || '',
+ hash: locationProps.hash || '',
+ state: null,
+ key: 'default',
+ };
+
+ return location;
+}
+
diff --git a/packages/runtime/src/server/streamRender.tsx b/packages/runtime/src/server/streamRender.tsx
index 34ce4813a6..bff805713f 100644
--- a/packages/runtime/src/server/streamRender.tsx
+++ b/packages/runtime/src/server/streamRender.tsx
@@ -2,8 +2,7 @@ import * as Stream from 'stream';
import type * as StreamType from 'stream';
import * as ReactDOMServer from 'react-dom/server';
import { getAllAssets } from '../Document.js';
-import type { RenderOptions } from '../runServerApp.js';
-import type { ServerAppRouterProps } from '../types.js';
+import type { ServerAppRouterProps, RenderOptions } from '../types.js';
const { Writable } = Stream;
@@ -117,4 +116,4 @@ export function pipeToString(input): Promise {
},
});
});
-}
\ No newline at end of file
+}
diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts
index 1e6c46c280..06c56b01bd 100644
--- a/packages/runtime/src/types.ts
+++ b/packages/runtime/src/types.ts
@@ -3,6 +3,8 @@ import type { InitialEntry, AgnosticRouteObject, Location, History, RouterInit,
import type { ComponentType, PropsWithChildren } from 'react';
import type { HydrationOptions, Root } from 'react-dom/client';
import type { Params, RouteObject } from 'react-router-dom';
+import type { RouteLoaderOptions } from './routes.js';
+import type { RenderToPipeableStreamOptions, NodeWritablePiper } from './server/streamRender.js';
type UseConfig = () => RouteConfig>;
type UseData = () => RouteData;
@@ -300,6 +302,42 @@ export interface RouteMatch {
export type RenderMode = 'SSR' | 'SSG' | 'CSR';
+interface Piper {
+ pipe: NodeWritablePiper;
+ fallback: Function;
+}
+
+export interface Response {
+ statusCode?: number;
+ statusText?: string;
+ value?: string | Piper;
+ headers?: Record;
+}
+
+export interface RenderOptions {
+ app: AppExport;
+ assetsManifest: AssetsManifest;
+ createRoutes: (options: Pick) => RouteItem[];
+ runtimeModules: RuntimeModules;
+ documentDataLoader?: DocumentDataLoaderConfig;
+ Document?: DocumentComponent;
+ documentOnly?: boolean;
+ preRender?: boolean;
+ renderMode?: RenderMode;
+ // basename is used both for server and client, once set, it will be sync to client.
+ basename?: string;
+ // serverOnlyBasename is used when just want to change basename for server.
+ serverOnlyBasename?: string;
+ routePath?: string;
+ disableFallback?: boolean;
+ routesConfig?: {
+ [key: string]: PageConfig;
+ };
+ runtimeOptions?: Record;
+ serverData?: any;
+ streamOptions?: RenderToPipeableStreamOptions;
+}
+
declare global {
interface ImportMeta {
// The build target for ice.js
diff --git a/packages/runtime/tests/navigator.test.ts b/packages/runtime/tests/navigator.test.ts
deleted file mode 100644
index d7625168c7..0000000000
--- a/packages/runtime/tests/navigator.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { expect, it, describe } from 'vitest';
-import { createStaticNavigator } from '../src/server/navigator';
-
-describe('mock server navigator', () => {
- const staticNavigator = createStaticNavigator();
- it('createHref', () => {
- expect(staticNavigator.createHref('/')).toBe('/');
- });
-
- it('push', () => {
- expect(() => staticNavigator.push('/')).toThrow();
- });
-
- it('replace', () => {
- expect(() => staticNavigator.replace('/')).toThrow();
- });
-
- it('go', () => {
- expect(() => staticNavigator.go(1)).toThrow();
- });
-
- it('back', () => {
- expect(() => staticNavigator.back()).toThrow();
- });
-
- it('forward', () => {
- expect(() => staticNavigator.forward()).toThrow();
- });
-
- it('block', () => {
- expect(() => staticNavigator.block()).toThrow();
- });
-});
\ No newline at end of file
diff --git a/packages/shared-config/CHANGELOG.md b/packages/shared-config/CHANGELOG.md
index 49f03bfb4b..ac50bdfcca 100644
--- a/packages/shared-config/CHANGELOG.md
+++ b/packages/shared-config/CHANGELOG.md
@@ -1,5 +1,11 @@
# @ice/shared-config
+## 1.2.8
+
+### Patch Changes
+
+- 15c8200f: feat: support build additional server entry for fallback
+
## 1.2.7
### Patch Changes
diff --git a/packages/shared-config/package.json b/packages/shared-config/package.json
index 0193fd6987..7695a17948 100644
--- a/packages/shared-config/package.json
+++ b/packages/shared-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/shared-config",
- "version": "1.2.7",
+ "version": "1.2.8",
"repository": "alibaba/ice",
"bugs": "https://github.com/alibaba/ice/issues",
"homepage": "https://v3.ice.work",
diff --git a/packages/shared-config/src/types.ts b/packages/shared-config/src/types.ts
index 91babe7ed8..e6a182b7ca 100644
--- a/packages/shared-config/src/types.ts
+++ b/packages/shared-config/src/types.ts
@@ -192,6 +192,12 @@ export interface Config {
memoryRouter?: boolean;
server?: {
+ /**
+ * Generate sperate bundle for fallback,
+ * it only outputs document content.
+ */
+ fallbackEntry?: boolean;
+
entry?: string;
buildOptions?: (options: BuildOptions) => BuildOptions;
diff --git a/packages/webpack-config/CHANGELOG.md b/packages/webpack-config/CHANGELOG.md
index 0e726af0bb..2c1ef90046 100644
--- a/packages/webpack-config/CHANGELOG.md
+++ b/packages/webpack-config/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 1.1.15
+
+### Patch Changes
+
+- Updated dependencies [15c8200f]
+ - @ice/shared-config@1.2.8
+
## 1.1.14
### Patch Changes
diff --git a/packages/webpack-config/package.json b/packages/webpack-config/package.json
index 7f62579f6e..497754c645 100644
--- a/packages/webpack-config/package.json
+++ b/packages/webpack-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/webpack-config",
- "version": "1.1.14",
+ "version": "1.1.15",
"repository": "alibaba/ice",
"bugs": "https://github.com/alibaba/ice/issues",
"homepage": "https://v3.ice.work",
@@ -15,7 +15,7 @@
"*.d.ts"
],
"dependencies": {
- "@ice/shared-config": "1.2.7",
+ "@ice/shared-config": "1.2.8",
"@ice/bundles": "0.2.6",
"fast-glob": "^3.2.11",
"process": "^0.11.10"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f866c36552..9b179afcbd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -130,21 +130,12 @@ importers:
'@ice/app':
specifier: workspace:*
version: link:../../packages/ice
- '@ice/plugin-auth':
- specifier: workspace:*
- version: link:../../packages/plugin-auth
- '@ice/plugin-rax-compat':
+ '@ice/plugin-externals':
specifier: workspace:*
- version: link:../../packages/plugin-rax-compat
+ version: link:../../packages/plugin-externals
'@ice/runtime':
specifier: workspace:*
version: link:../../packages/runtime
- '@uni/env':
- specifier: ^1.1.0
- version: 1.1.0
- ahooks:
- specifier: ^3.3.8
- version: 3.7.5(react@18.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
@@ -158,12 +149,6 @@ importers:
'@types/react-dom':
specifier: ^18.0.2
version: 18.0.11
- speed-measure-webpack-plugin:
- specifier: ^1.5.0
- version: 1.5.0(webpack@5.88.2)
- webpack:
- specifier: ^5.88.0
- version: 5.88.2
examples/basic-project:
dependencies:
@@ -817,7 +802,7 @@ importers:
specifier: ^18.0.6
version: 18.0.11
- examples/with-entry-type:
+ examples/with-fallback-entry:
dependencies:
'@ice/app':
specifier: workspace:*
@@ -826,10 +811,10 @@ importers:
specifier: workspace:*
version: link:../../packages/runtime
react:
- specifier: ^18.0.0
+ specifier: ^18.2.0
version: 18.2.0
react-dom:
- specifier: ^18.0.0
+ specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
devDependencies:
'@types/react':
@@ -838,9 +823,6 @@ importers:
'@types/react-dom':
specifier: ^18.0.2
version: 18.0.11
- fs-extra:
- specifier: ^10.0.0
- version: 10.1.0
webpack:
specifier: ^5.88.0
version: 5.88.2
@@ -1678,16 +1660,16 @@ importers:
specifier: 1.2.2
version: link:../route-manifest
'@ice/rspack-config':
- specifier: 1.1.7
+ specifier: 1.1.8
version: link:../rspack-config
'@ice/runtime':
- specifier: ^1.4.8
+ specifier: ^1.4.10
version: link:../runtime
'@ice/shared-config':
- specifier: 1.2.7
+ specifier: 1.2.8
version: link:../shared-config
'@ice/webpack-config':
- specifier: 1.1.14
+ specifier: 1.1.15
version: link:../webpack-config
'@swc/helpers':
specifier: 0.5.1
@@ -1974,6 +1956,24 @@ importers:
specifier: ^3.3.2
version: link:../ice
+ packages/plugin-externals:
+ devDependencies:
+ '@ice/app':
+ specifier: ^3.3.2
+ version: link:../ice
+ '@ice/runtime':
+ specifier: ^1.2.9
+ version: link:../runtime
+ '@types/react':
+ specifier: ^18.0.0
+ version: 18.0.34
+ '@types/react-dom':
+ specifier: ^18.0.0
+ version: 18.0.11
+ webpack:
+ specifier: ^5.88.0
+ version: 5.88.2
+
packages/plugin-fusion:
dependencies:
'@ice/style-import':
@@ -2224,7 +2224,7 @@ importers:
specifier: ^4.17.21
version: 4.17.21
rax-compat:
- specifier: ^0.2.10
+ specifier: ^0.3.0
version: link:../rax-compat
style-unit:
specifier: ^3.0.5
@@ -2234,7 +2234,7 @@ importers:
version: 0.9.1
devDependencies:
'@ice/app':
- specifier: ^3.4.8
+ specifier: ^3.4.10
version: link:../ice
'@types/lodash-es':
specifier: ^4.17.7
@@ -2382,7 +2382,7 @@ importers:
specifier: 0.2.6
version: link:../bundles
'@ice/shared-config':
- specifier: 1.2.7
+ specifier: 1.2.8
version: link:../shared-config
devDependencies:
'@rspack/core':
@@ -2487,7 +2487,7 @@ importers:
specifier: 0.2.6
version: link:../bundles
'@ice/shared-config':
- specifier: 1.2.7
+ specifier: 1.2.8
version: link:../shared-config
fast-glob:
specifier: ^3.2.11
@@ -19937,7 +19937,7 @@ packages:
dependencies:
'@babel/code-frame': 7.18.6
address: 1.2.2
- browserslist: 4.22.1
+ browserslist: 4.22.3
chalk: 4.1.2
cross-spawn: 7.0.3
detect-port-alt: 1.1.6
@@ -23580,7 +23580,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.5
acorn: 8.11.2
acorn-import-assertions: 1.9.0(acorn@8.11.2)
- browserslist: 4.21.5
+ browserslist: 4.22.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.2.1
@@ -23619,7 +23619,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.5
acorn: 8.11.2
acorn-import-assertions: 1.9.0(acorn@8.11.2)
- browserslist: 4.21.5
+ browserslist: 4.22.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.2.1
diff --git a/tests/integration/basic-project.test.ts b/tests/integration/basic-project.test.ts
index 779666f72d..a4b3720ab1 100644
--- a/tests/integration/basic-project.test.ts
+++ b/tests/integration/basic-project.test.ts
@@ -65,7 +65,7 @@ describe(`build ${example}`, () => {
test('render route config when downgrade to CSR.', async () => {
await page.push('/downgrade.html');
- expect(await page.$$text('title')).toStrictEqual(['hello']);
+ expect(await page.$$text('title')).toStrictEqual(['']);
expect((await page.$$text('h2')).length).toEqual(0);
});
diff --git a/tests/integration/with-fallback-entry.test.ts b/tests/integration/with-fallback-entry.test.ts
new file mode 100644
index 0000000000..35373fc2d2
--- /dev/null
+++ b/tests/integration/with-fallback-entry.test.ts
@@ -0,0 +1,26 @@
+import * as path from 'path';
+import * as fs from 'fs';
+import { fileURLToPath } from 'url';
+import { expect, test, describe } from 'vitest';
+import { buildFixture } from '../utils/build';
+// @ts-ignore
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+const example = 'with-fallback-entry';
+
+describe(`build ${example}`, () => {
+ let sizeServer = 0;
+ let sizeFallback = 0;
+
+ test('build fallback entry', async () => {
+ await buildFixture(example);
+ const serverPath = path.join(__dirname, `../../examples/${example}/build/server/index.cjs`);
+ sizeServer = fs.statSync(serverPath).size;
+ const fallbackPath = path.join(__dirname, `../../examples/${example}/build/server/index.fallback.cjs`);
+ sizeFallback = fs.statSync(fallbackPath).size;
+
+ expect(sizeFallback).toBeLessThan(sizeServer);
+ // The Stat size of fallback entry will reduce more than 50kb.
+ expect(sizeServer - sizeFallback).toBeGreaterThan(50 * 1024);
+ });
+});