Compare commits

...

7 Commits

Author SHA1 Message Date
ClarkXia 26d65de2c2
Merge 34659a428e into 2742ac4678 2025-09-26 14:24:47 +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 34659a428e
chore: update types 2024-07-25 11:43:12 +08:00
ClarkXia 5a15fac778 feat: plugin react-query 2024-07-09 15:11:58 +08:00
ClarkXia 2837d34b55 feat: init plugin-react-query 2024-07-09 13:47:10 +08:00
18 changed files with 294 additions and 19 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

@ -0,0 +1,5 @@
# @ice/plugin-react-query
## 1.0.0
- Initial release

View File

View File

@ -0,0 +1,36 @@
{
"name": "@ice/plugin-react-query",
"version": "1.0.0",
"description": "React Query plugin for ice.",
"license": "MIT",
"type": "module",
"main": "./esm/index.js",
"types": "./esm/index.d.ts",
"files": [
"esm",
"!esm/**/*.map",
"*.d.ts"
],
"dependencies": {
"@tanstack/react-query": "^5.50.1",
"@tanstack/react-query-devtools": "^5.50.1"
},
"devDependencies": {
"@ice/app": "^3.4.9",
"@ice/runtime": "^1.4.8",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@tanstack/query-devtools": "^5.50.1"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/ice/tree/master/packages/plugin-request"
},
"scripts": {
"watch": "tsc -w --sourceMap",
"build": "tsc"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,47 @@
import { createRequire } from 'module';
import type { Plugin } from '@ice/app/types';
const require = createRequire(import.meta.url);
const PLUGIN_NAME = '@ice/plugin-react-query';
const plugin: Plugin<void> = () => {
return {
name: PLUGIN_NAME,
setup: ({ generator, onGetConfig }) => {
generator.addExport({
specifier: [
// Core API export from @tanstack/react-query
'useQuery',
'useMutation',
'useQueryClient',
'useMutationState',
'useSuspenseQuery',
'useSuspenseInfiniteQuery',
'useSuspenseQueries',
'queryOptions',
'infiniteQueryOptions',
'QueryClientProvider',
'useQueryClient',
'QueryErrorResetBoundary',
'useQueryErrorResetBoundary',
'useIsRestoring',
'IsRestoringProvider',
],
source: '@tanstack/react-query',
type: false,
});
onGetConfig((config) => {
// Add alias for react-query and react-query-devtools to avoid mismatching versions.
config.alias = {
...config.alias,
'@tanstack/react-query': require.resolve('@tanstack/react-query'),
'@tanstack/react-query-devtools': require.resolve('@tanstack/react-query-devtools'),
};
});
},
runtime: `${PLUGIN_NAME}/runtime`,
staticRuntime: true,
};
};
export default plugin;

View File

@ -0,0 +1,36 @@
import * as React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { RuntimePlugin } from '@ice/runtime/types';
import type { ReactQueryConfig } from './types';
const EXPORT_CONFIG_NAME = 'reactQueryConfig';
const runtime: RuntimePlugin = ({ addProvider, appContext }) => {
const { appExport } = appContext;
const reactQueryConfig = appExport[EXPORT_CONFIG_NAME] as ReactQueryConfig;
const queryClient = new QueryClient({
...(reactQueryConfig?.queryClientConfig || {}),
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
...(reactQueryConfig?.queryClientConfig.defaultOptions?.queries || {}),
},
},
});
const ProviderWrapper = ({ children }) => {
return (
<QueryClientProvider client={queryClient}>
{children}
{ reactQueryConfig?.devTools &&
<ReactQueryDevtools
{...(reactQueryConfig.devTools || {})}
/> }
</QueryClientProvider>
);
};
addProvider(ProviderWrapper);
};
export default runtime;

View File

@ -0,0 +1,17 @@
import type { QueryClientConfig, QueryClient } from '@tanstack/react-query';
import type { DevtoolsButtonPosition, DevtoolsPosition, DevToolsErrorType } from '@tanstack/query-devtools';
interface DevtoolsOptions {
initialIsOpen?: boolean;
buttonPosition?: DevtoolsButtonPosition;
position?: DevtoolsPosition;
client?: QueryClient;
errorTypes?: Array<DevToolsErrorType>;
styleNonce?: string;
shadowDOMTarget?: ShadowRoot;
}
export interface ReactQueryConfig {
devTools?: DevtoolsOptions;
queryClientConfig?: QueryClientConfig;
}

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./",
"rootDir": "src",
"outDir": "esm",
"jsx": "react"
},
"include": ["src"]
}

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
@ -2252,6 +2252,31 @@ importers:
specifier: ^5.88.0
version: 5.88.2
packages/plugin-react-query:
dependencies:
'@tanstack/react-query':
specifier: ^5.50.1
version: 5.50.1(react@18.2.0)
'@tanstack/react-query-devtools':
specifier: ^5.50.1
version: 5.50.1(@tanstack/react-query@5.50.1)(react@18.2.0)
devDependencies:
'@ice/app':
specifier: ^3.4.9
version: link:../ice
'@ice/runtime':
specifier: ^1.4.8
version: link:../runtime
'@tanstack/query-devtools':
specifier: ^5.50.1
version: 5.50.1
'@types/react':
specifier: ^18.0.0
version: 18.0.34
'@types/react-dom':
specifier: ^18.0.0
version: 18.0.11
packages/plugin-request:
dependencies:
ahooks:
@ -8568,6 +8593,33 @@ packages:
dependencies:
defer-to-connect: 1.1.3
/@tanstack/query-core@5.50.1:
resolution: {integrity: sha512-lpfhKPrJlyV2DSVcQb/HuozH3Av3kws4ge22agx+lNGpFkS4vLZ7St0l3GLwlAD+bqB+qXGex3JdRKUNtMviEQ==}
dev: false
/@tanstack/query-devtools@5.50.1:
resolution: {integrity: sha512-MQ5JK3yRwBP1SRuwoJVPGZP4cMLXCQ0t+6blDbcAVGEoqrEuvbgTdwlN729AKBR0hidOWPFR9n5YpI2Y8bBZOQ==}
/@tanstack/react-query-devtools@5.50.1(@tanstack/react-query@5.50.1)(react@18.2.0):
resolution: {integrity: sha512-zgPmEFv9GhLAx6eaf9r0ACbcxit1ZSuv/uPpOXBTTSPLijlWcfpQTOdZx0jYQ14t2cUfWjrAW41cUmcCvT4X/g==}
peerDependencies:
'@tanstack/react-query': ^5.50.1
react: ^18 || ^19
dependencies:
'@tanstack/query-devtools': 5.50.1
'@tanstack/react-query': 5.50.1(react@18.2.0)
react: 18.2.0
dev: false
/@tanstack/react-query@5.50.1(react@18.2.0):
resolution: {integrity: sha512-s0DW3rVBDPReDDovUjVqItVa3R2nPfUANK9nqGvarO2DwTiY9U4EBTsqizMxItRCoGgK5apeM7D3mxlHrSKpdQ==}
peerDependencies:
react: ^18.0.0
dependencies:
'@tanstack/query-core': 5.50.1
react: 18.2.0
dev: false
/@testing-library/dom@8.20.0:
resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==}
engines: {node: '>=12'}