Compare commits

...

5 Commits

Author SHA1 Message Date
ClarkXia 274a38a7c7
Merge 6fb7336cb7 into 2742ac4678 2025-09-26 14:24:16 +08:00
水澜 2742ac4678
fix: duplicate css (#7137)
CI / build (16.x, ubuntu-latest) (push) Has been cancelled Details
CI / build (16.x, windows-latest) (push) Has been cancelled Details
CI / build (18.x, ubuntu-latest) (push) Has been cancelled Details
CI / build (18.x, windows-latest) (push) Has been cancelled Details
Coverage / coverage (16.x) (push) Has been cancelled Details
Release / Release (16) (push) Has been cancelled Details
* fix: duplicate css

* chore: add changelog

* fix: lint
2025-09-25 16:44:37 +08:00
AmAzing- fcc25dc3fd
docs: update contributors chart (#7139) 2025-09-11 12:16:55 +08:00
ClarkXia 8e27933423
feat: add SuspenseWrappers to Runtime (#7131) (#7134)
* feat: add SuspenseWrappers to Runtime (#7131)

* feat: add SuspenseWrappers to Runtime

* chore(runtime): format code

* fix(runtime): Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(runtime): remove redundant composeSuspenseWrappers function

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* chore: update versions (#7133)

* chore: update versions

* Remove 3.6.6 entry from CHANGELOG

Removed version 3.6.6 entry from CHANGELOG.

* Update package.json

* Update CHANGELOG.md

* Downgrade version from 1.1.7 to 1.1.6

* Remove changelog entry for version 1.2.7

Removed version 1.2.7 entry from changelog.

* Update package.json

* Update CHANGELOG.md

* Update package.json

* Update package.json

* Update package.json

* Update pnpm-lock.yaml

---------

Co-authored-by: Mixiu <112144929+riopop@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-27 16:05:42 +08:00
ClarkXia 6fb7336cb7 docs: update migrate docs 2025-06-23 16:27:12 +08:00
12 changed files with 221 additions and 46 deletions

View File

@ -0,0 +1,5 @@
---
'@ice/runtime': patch
---
fix: duplicate css

View File

@ -48,7 +48,9 @@ Please see our [CONTRIBUTING.md](/.github/CONTRIBUTING.md)
Contributors can contact us to join the Contributor Group.
<a href="https://github.com/alibaba/ice/graphs/contributors"><img src="https://alibaba.github.io/ice/ice.png" /></a>
<a href="https://openomy.com/alibaba/ice" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://openomy.com/svg?repo=alibaba/ice&chart=bubble&latestMonth=6" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>
## Community

View File

@ -57,7 +57,7 @@
},
"peerDependencies": {
"@ice/app": "^3.6.4",
"@ice/runtime": "^1.5.6"
"@ice/runtime": "^1.5.7"
},
"publishConfig": {
"access": "public"

View File

@ -51,7 +51,7 @@
},
"devDependencies": {
"@ice/app": "^3.6.4",
"@ice/runtime": "^1.5.6",
"@ice/runtime": "^1.5.7",
"webpack": "^5.88.0"
},
"repository": {

View File

@ -1,5 +1,11 @@
# @ice/runtime
## 1.5.7
### Patch Changes
- 4ff29969c: feat: add SuspenseWrappers to Runtime
## 1.5.6
### Patch Changes

View File

@ -1,6 +1,6 @@
{
"name": "@ice/runtime",
"version": "1.5.6",
"version": "1.5.7",
"description": "Runtime module for ice.js",
"type": "module",
"types": "./esm/index.d.ts",

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import type { WindowContext, RouteMatch, AssetsManifest } from './types.js';
import { useAppContext, useAppData } from './AppContext.js';
import { getMeta, getTitle, getLinks, getScripts } from './routesConfig.js';
import { getLinks, getMeta, getScripts, getTitle } from './routesConfig.js';
import type { AssetsManifest, RouteMatch, WindowContext } from './types.js';
import getCurrentRoutePath from './utils/getCurrentRoutePath.js';
interface DocumentContext {
@ -81,7 +81,15 @@ export const Links: LinksType = (props: LinksProps) => {
const routeLinks = getLinks(matches, loaderData);
const pageAssets = getPageAssets(matches, assetsManifest);
const entryAssets = getEntryAssets(assetsManifest);
const styles = entryAssets.concat(pageAssets).filter(path => path.indexOf('.css') > -1);
let styles = entryAssets.concat(pageAssets).filter(path => path.indexOf('.css') > -1);
// Unique styles for duplicate CSS files.
const cssSet = {};
styles = styles.filter((style) => {
if (cssSet[style]) return false;
cssSet[style] = true;
return true;
});
return (
<>

View File

@ -135,7 +135,8 @@ export function withSuspense(Component) {
return (props: SuspenseProps) => {
const { fallback, id, ...componentProps } = props;
const [suspenseState, updateSuspenseData] = React.useState({
const [suspenseState, updateSuspenseData] = React.useState<SuspenseState>({
id: id,
data: null,
done: false,
@ -156,24 +157,47 @@ export function withSuspense(Component) {
updateSuspenseData(newState);
}
return (
<React.Suspense fallback={fallback || null}>
// Get SuspenseWrappers from app context
const { SuspenseWrappers = [] } = useAppContext();
// Compose SuspenseWrappers
const composeSuspenseWrappers = React.useCallback(
(children: React.ReactNode) => {
if (!SuspenseWrappers.length) return children;
return SuspenseWrappers.reduce((WrappedComponent, wrapperConfig) => {
const { Wrapper } = wrapperConfig;
return <Wrapper id={id}>{WrappedComponent}</Wrapper>;
}, children);
},
[SuspenseWrappers, id],
);
const wrappedComponent = (
<>
<InlineScript
id={`suspense-parse-start-${id}`}
script={`(${DISPATCH_SUSPENSE_EVENT_STRING})('ice-suspense-parse-start','${id}');`}
/>
<SuspenseContext.Provider value={suspenseState}>
<Component {...componentProps} />
<InlineScript
id={`suspense-parse-data-${id}`}
script={`(${DISPATCH_SUSPENSE_EVENT_STRING})('ice-suspense-parse-data','${id}');`}
/>
<Data id={id} />
</SuspenseContext.Provider>
<Component {...componentProps} />
<InlineScript
id={`suspense-parse-data-${id}`}
script={`(${DISPATCH_SUSPENSE_EVENT_STRING})('ice-suspense-parse-data','${id}');`}
/>
<Data id={id} />
<InlineScript
id={`suspense-parse-end-${id}`}
script={`(${DISPATCH_SUSPENSE_EVENT_STRING})('ice-suspense-parse-end','${id}');`}
/>
</>
);
return (
<React.Suspense fallback={fallback || null}>
<SuspenseContext.Provider value={suspenseState}>
{composeSuspenseWrappers(wrappedComponent)}
</SuspenseContext.Provider>
</React.Suspense>
);
};

View File

@ -12,7 +12,9 @@ import type {
SetAppRouter,
AddProvider,
AddWrapper,
AddSuspenseWrapper,
RouteWrapperConfig,
SuspenseWrapperConfig,
SetRender,
AppRouterProps,
ComponentWithChildren,
@ -33,6 +35,8 @@ class Runtime {
private RouteWrappers: RouteWrapperConfig[];
private SuspenseWrappers: SuspenseWrapperConfig[];
private render: Renderer;
private responseHandlers: ResponseHandler[];
@ -46,6 +50,7 @@ class Runtime {
return root;
};
this.RouteWrappers = [];
this.SuspenseWrappers = [];
this.runtimeOptions = runtimeOptions;
this.responseHandlers = [];
this.getAppRouter = this.getAppRouter.bind(this);
@ -55,6 +60,7 @@ class Runtime {
return {
...this.appContext,
RouteWrappers: this.RouteWrappers,
SuspenseWrappers: this.SuspenseWrappers,
};
};
@ -72,6 +78,8 @@ class Runtime {
public getWrappers = () => this.RouteWrappers;
public getSuspenseWrappers = () => this.SuspenseWrappers;
public loadModule(module: RuntimePlugin | StaticRuntimePlugin | CommonJsRuntime) {
let runtimeAPI: RuntimeAPI = {
addProvider: this.addProvider,
@ -80,6 +88,7 @@ class Runtime {
getAppRouter: this.getAppRouter,
setRender: this.setRender,
addWrapper: this.addWrapper,
addSuspenseWrapper: this.addSuspenseWrapper,
appContext: this.appContext,
setAppRouter: this.setAppRouter,
useData: process.env.ICE_CORE_ROUTER === 'true' ? useData : useSingleRouterData,
@ -122,6 +131,12 @@ class Runtime {
});
};
private addSuspenseWrapper: AddSuspenseWrapper = (Wrapper) => {
this.SuspenseWrappers.push({
Wrapper,
});
};
public setAppRouter: SetAppRouter = (AppRouter) => {
this.AppRouter = AppRouter;
};

View File

@ -117,6 +117,7 @@ export interface AppContext {
loaderData?: LoadersData;
routeModules?: RouteModules;
RouteWrappers?: RouteWrapperConfig[];
SuspenseWrappers?: SuspenseWrapperConfig[];
routePath?: string;
matches?: RouteMatch[];
routes?: RouteItem[];
@ -187,16 +188,26 @@ export interface RouteWrapperConfig {
export type AppProvider = ComponentWithChildren<any>;
export type RouteWrapper = ComponentType<any>;
export type SuspenseWrapper = ComponentWithChildren<{
id: string;
}>;
export type ResponseHandler = (
req: IncomingMessage,
res: ServerResponse,
) => any | Promise<any>;
export interface SuspenseWrapperConfig {
Wrapper: SuspenseWrapper;
}
export type SetAppRouter = <T>(AppRouter: ComponentType<T>) => void;
export type GetAppRouter = () => AppProvider;
export type AddProvider = (Provider: AppProvider) => void;
export type SetRender = (render: Renderer) => void;
export type AddWrapper = (wrapper: RouteWrapper, forLayout?: boolean) => void;
export type AddSuspenseWrapper = (wrapper: SuspenseWrapper) => void;
export type AddResponseHandler = (handler: ResponseHandler) => void;
export type GetResponseHandlers = () => ResponseHandler[];
@ -227,6 +238,7 @@ export interface RuntimeAPI {
getResponseHandlers: GetResponseHandlers;
setRender: SetRender;
addWrapper: AddWrapper;
addSuspenseWrapper: AddSuspenseWrapper;
appContext: AppContext;
useData: UseData;
useConfig: UseConfig;

View File

@ -2160,7 +2160,7 @@ importers:
specifier: ^3.6.4
version: link:../ice
'@ice/runtime':
specifier: ^1.5.6
specifier: ^1.5.7
version: link:../runtime
webpack:
specifier: ^5.88.0

View File

@ -12,6 +12,8 @@ order: 0902
### 依赖修改
#### 框架依赖
```diff
{
"devDependencies": {
@ -22,40 +24,146 @@ order: 0902
}
```
对应插件能力:
- @ali/build-plugin-ice-def -> @ali/ice-plugin-def
- build-plugin-moment-locales -> @ice/plugin-moment-locales
- build-plugin-fusion -> @ice/plugin-fusion (多主题能力暂不支持)
- build-plugin-antd -> @ice/plugin-antd
- build-plugin-css-assets-local -> @ice/plugin-css-assets-local
- build-plugin-jsx-plus -> @ice/plugin-jsx-plus [文档](../advanced/jsx-plus.md)
- build-plugin-keep-alive 不再支持,有 ice.js 3.0 的 [keep alive 方案](../advanced/keep-alive.md)替代
插件使用方式变更为函数调用:
#### 插件依赖
##### @ali/build-plugin-ice-def
插件替换为 `@ali/ice-plugin-def`,使用版本 1.2.4+,使用方式:
```ts title="ice.config.mts"
import { defineConfig } from '@ice/app';
import jsxPlus from '@ice/plugin-jsx-plus';
import def from '@ali/ice-plugin-def';
export default defineConfig(() => ({
plugins: [
jsxPlus(),
def(),
],
}));
```
##### build-plugin-moment-locales
插件替换为 `@ice/plugin-moment-locales`,使用版本 1.0.2+,使用方式:
```ts title="ice.config.mts"
import { defineConfig } from '@ice/app';
import moment from '@ice/plugin-moment-locales';
export default defineConfig(() => ({
plugins: [
moment({
locales: ['zh-CN'],
}),
],
}));
```
##### build-plugin-fusion
替换为 `@ice/plugin-fusion`,使用版本 1.1.0+,使用方式:
```ts title="ice.config.mts"
import { defineConfig } from '@ice/app';
import fusion from '@ice/plugin-fusion';
export default defineConfig(() => ({
plugins: [fusion({
importStyle: true,
themePackage: '@alifd/theme-design-pro',
theme: {
'primary-color': '#fff',
},
})],
}));
```
fusion 插件文档:[链接](../plugins/plugin-fusion.md)
> ice3 下的 fusion 插件不再支持多主题能力,仅支持配置 importStyle、themePackage 和 theme 三个配置项
##### build-plugin-antd
插件替换为 `@ice/plugin-antd`,使用版本 1.0.2+,使用方式:
```ts title="ice.config.mts"
import { defineConfig } from '@ice/app';
import antd from '@ice/plugin-antd';
export default defineConfig(() => ({
plugins: [antd({
dark: true,
compact: true,
theme: {
'primary-color': '#fd8',
},
})],
}));
```
antd 插件文档:[链接](../advanced/antd.md)
##### build-plugin-css-assets-local
插件替换为 `@ice/plugin-css-assets-local`,使用版本 1.0.2+,使用方式:
```ts title="ice.config.mts"
import { defineConfig } from '@ice/app';
import cssAssetsLocal from '@ice/plugin-css-assets-local';
export default defineConfig(() => ({
plugins: [
cssAssetsLocal(),
],
}));
```
css-assets-local 插件文档:[链接](../advanced/css-assets-local.md)
##### build-plugin-jsx-plus
插件替换为 `@ice/plugin-jsx-plus`,使用版本 1.0.4+,使用方式:
```ts title="ice.config.mts"
import { defineConfig } from '@ice/app';
import jsxplus from '@ice/plugin-jsx-plus';
export default defineConfig(() => ({
plugins: [
jsxplus({
// options
}),
],
}));
```
jsx-plus 插件文档:[链接](../advanced/jsx-plus.md)
##### build-plugin-keep-alive
ice 3 不再支持插件形式的 keep-alive 方案。由框架内置提供的 `<KeepAliveOutlet />` 组件替代。
keep-alive 插件文档:[链接](../advanced/keep-alive.md)
> 完成依赖升级后推荐重新安装依赖,即执行 npm update
### 工程配置文件升级
为了获取更好的类型提示ice 新版本中推荐使用 ts 文件进行配置,即在项目目录下新增 `ice.config.mts` 文件,原 json 中的能力支持情况如下:
#### 命令行参数
| ice 2.x | ice 3.0 | 备注 |
| ---- | ---- | ---- |
| --port | --port | - |
| --host | --host | - |
| --config | --config | - |
| --disable-open | --no-open | - |
| --disable-mock | --no-mock | - |
| --https | --https | - |
| --analyzer | --analyzer | - |
| --disable-assets | ❌ | 不常用通过环境变量控制日志输出详细程度 |
| --disable-reload | ❌ | 配置禁止 fastRefresh |
#### 配置项
| ice 2.x | ice 3.0 | 备注 |
| ---- | ---- | ---- |
| --port | ✅ | - |
| --host | ✅ | - |
| --config | ✅ | - |
| --disable-open | ✅ | - |
| plugins | ✅ | - |
| alias | ✅ | - |
| publicPath | ✅ | - |
@ -67,9 +175,6 @@ export default defineConfig(() => ({
| proxy | ✅ | - |
| define | ✅ | - |
| ssr | ✅ | - |
| --disable-mock | ✅ | - |
| --https | ✅ | - |
| --analyzer | ✅ | - |
| dropLogLevel | ✅ | - |
| minify | ✅ | 简化配置true/false |
| compileDependencies | ✅ | 配合现有的 compileIncludes 能力 |
@ -78,8 +183,6 @@ export default defineConfig(() => ({
| postcssOptions / postcssrc | ✅ | - |
| polyfill | ✅ | 需要主动开启 |
| remoteRuntime | ❌ | - |
| --disable-assets | ❌ | 不常用通过环境变量控制日志输出详细程度 |
| --disable-reload | ❌ | 配置禁止 fastRefresh |
| terser | ❌ | 内置方案 |
| outputAssetsPath | ❌ | 后续输出最佳目录实践 |
| devServer | ❌ | 不支持全量配置 devServer按需开启 server 相关能力 |
@ -93,7 +196,7 @@ export default defineConfig(() => ({
| swc | ❌ | - |
| store / auth / request / pwa / router | ❌ | 通过定制的插件支持 |
| disableRuntime | ❌ | - |
| babelPlugins / babelPresets / webpackPlugins / webpackLoaders | ❌ | 不推荐直接配置 |
| babelPlugins / babelPresets / webpackPlugins / webpackLoaders | ❌ | 不推荐直接配置,如果有定制需求通过 webpack 配置进行迁移 |
ice.js 3 新版本中不再支持 vite 模式,并且 webpack 相关的快捷配置也不再支持。我们将会将内置的逻辑做到最优。如果存在 webpack 定制需求,可以参考如下自定义方式定制:
@ -113,11 +216,11 @@ export default defineConfig(() => ({
// 修改内置的 webpack 规则,借助官方工具可以更便捷的修改
// 修改 css 样式规则下的 postcss-loader 配置项
return modifyLoader(webpackConfig, {
rule: '.css',
loader: 'postcss-loader',
options: (originOptions) => ({}),
});
}
rule: '.css',
loader: 'postcss-loader',
options: (originOptions) => ({}),
});
},
}));
```