mirror of https://github.com/alibaba/ice.git
feat: use scripts (#6238)
* feat: build for production * feat: use scripts * revert: production config * feat: use render mode * test: use scripts * fix: export * fix: exports * fix: lint * revert: lock * feat: export useAppContext * fix: type * fix: type * refactor: hooks name * fix: type * refactor: remove useRenderMode * refactor: remove useRenderMode * refactor: function name * fix: export app config * refactor: app context * chore: add changelog * chore: remove demo code
This commit is contained in:
parent
f839c67d7a
commit
474e19356f
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
'@ice/runtime': patch
|
||||
'@ice/app': patch
|
||||
---
|
||||
|
||||
feat: support usePageAssets
|
||||
feat: export useAppContext
|
||||
|
|
@ -61,6 +61,7 @@ export const RUNTIME_EXPORTS = [
|
|||
'ClientOnly',
|
||||
'withSuspense',
|
||||
'useSuspenseData',
|
||||
'useAppContext',
|
||||
'Await',
|
||||
'defineDataLoader',
|
||||
'defineServerDataLoader',
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const plugin: Plugin = () => ({
|
|||
'Scripts',
|
||||
'Data',
|
||||
'Main',
|
||||
'usePageAssets',
|
||||
],
|
||||
types: [
|
||||
'MetaType',
|
||||
|
|
|
|||
|
|
@ -137,6 +137,23 @@ export const Scripts: ScriptsType = (props: ScriptsProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
export function usePageAssets() {
|
||||
const { loaderData, matches, assetsManifest } = useAppContext();
|
||||
const routeLinks = getLinks(matches, loaderData);
|
||||
const routeScripts = getScripts(matches, loaderData);
|
||||
const pageAssets = getPageAssets(matches, assetsManifest);
|
||||
const entryAssets = getEntryAssets(assetsManifest);
|
||||
|
||||
// Page assets need to be load before entry assets, so when call dynamic import won't cause duplicate js chunk loaded.
|
||||
const assets = [].concat(routeLinks).concat(routeScripts).concat(pageAssets).concat(entryAssets);
|
||||
|
||||
if (assetsManifest.dataLoader) {
|
||||
assets.unshift(`${assetsManifest.publicPath}${assetsManifest.dataLoader}`);
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
interface DataProps {
|
||||
ScriptElement?: React.ComponentType<React.ScriptHTMLAttributes<HTMLScriptElement>> | string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
import type {
|
||||
RuntimePlugin,
|
||||
AppContext,
|
||||
PublicAppContext,
|
||||
AppConfig,
|
||||
RouteConfig,
|
||||
RouteItem,
|
||||
|
|
@ -22,7 +23,7 @@ import type {
|
|||
import Runtime from './runtime.js';
|
||||
import runClientApp from './runClientApp.js';
|
||||
import type { RunClientAppOptions } from './runClientApp.js';
|
||||
import { useAppContext, useAppData, AppContextProvider } from './AppContext.js';
|
||||
import { useAppContext as useInternalAppContext, useAppData, AppContextProvider } from './AppContext.js';
|
||||
import { getAppData } from './appData.js';
|
||||
import { useData, useConfig } from './RouteContext.js';
|
||||
import {
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
Scripts,
|
||||
Main,
|
||||
Data,
|
||||
usePageAssets,
|
||||
} from './Document.js';
|
||||
import type {
|
||||
MetaType,
|
||||
|
|
@ -53,6 +55,26 @@ import usePageLifecycle from './usePageLifecycle.js';
|
|||
import { withSuspense, useSuspenseData } from './Suspense.js';
|
||||
import { createRouteLoader, WrapRouteComponent, RouteErrorComponent, Await } from './routes.js';
|
||||
|
||||
function useAppContext(): PublicAppContext {
|
||||
const context = useInternalAppContext();
|
||||
|
||||
const {
|
||||
appConfig,
|
||||
routePath,
|
||||
downgrade,
|
||||
documentOnly,
|
||||
renderMode,
|
||||
} = context;
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
routePath,
|
||||
downgrade,
|
||||
documentOnly,
|
||||
renderMode,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
getAppConfig,
|
||||
defineAppConfig,
|
||||
|
|
@ -73,6 +95,7 @@ export {
|
|||
Scripts,
|
||||
Data,
|
||||
Main,
|
||||
usePageAssets,
|
||||
// API for data-loader.
|
||||
dataLoader,
|
||||
callDataLoader,
|
||||
|
|
|
|||
|
|
@ -460,6 +460,7 @@ function renderDocument(options: RenderDocumentOptions): Response {
|
|||
matches,
|
||||
routes,
|
||||
documentOnly: true,
|
||||
renderMode: 'CSR',
|
||||
routePath,
|
||||
basename,
|
||||
downgrade,
|
||||
|
|
|
|||
|
|
@ -111,8 +111,13 @@ export interface AppContext {
|
|||
revalidate?: boolean;
|
||||
}
|
||||
|
||||
export type PublicAppContext = Pick<
|
||||
AppContext,
|
||||
'appConfig' | 'routePath' | 'downgrade' | 'documentOnly' | 'renderMode'
|
||||
>;
|
||||
|
||||
export type WindowContext = Pick<
|
||||
AppContext,
|
||||
AppContext,
|
||||
'appData' | 'loaderData' | 'routePath' | 'downgrade' | 'matchedIds' | 'documentOnly' | 'renderMode' | 'serverData' | 'revalidate'
|
||||
>;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
import React from 'react';
|
||||
import { expect, it, describe } from 'vitest';
|
||||
import { renderToHTML, renderToResponse } from '../src/runServerApp';
|
||||
import { Meta, Title, Links, Main, Scripts } from '../src/Document';
|
||||
import { Meta, Title, Links, Main, Scripts, usePageAssets } from '../src/Document';
|
||||
import { useAppContext } from '../src/';
|
||||
import {
|
||||
createRouteLoader,
|
||||
} from '../src/routes.js';
|
||||
|
|
@ -254,4 +255,54 @@ describe('run server app', () => {
|
|||
expect(!!htmlContent).toBe(true);
|
||||
expect(htmlContent.includes('<div>home</div')).toBe(false);
|
||||
});
|
||||
|
||||
it('render with use scripts', async () => {
|
||||
let scripts;
|
||||
let renderMode;
|
||||
|
||||
const Document = () => {
|
||||
scripts = usePageAssets();
|
||||
const context = useAppContext();
|
||||
renderMode = context.renderMode;
|
||||
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="description" content="ICE 3.0 Demo" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Title />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
<Main />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
const html = await renderToHTML({
|
||||
// @ts-ignore
|
||||
req: {
|
||||
url: '/home',
|
||||
},
|
||||
}, {
|
||||
app: {},
|
||||
assetsManifest,
|
||||
runtimeModules: { commons: [] },
|
||||
// @ts-ignore don't need to pass params in test case.
|
||||
createRoutes: () => basicRoutes,
|
||||
Document,
|
||||
renderMode: 'SSR',
|
||||
});
|
||||
// @ts-ignore
|
||||
expect(html?.value?.includes('<div>home</div>')).toBe(true);
|
||||
// @ts-ignore
|
||||
expect(html?.value?.includes('js/home.js')).toBe(true);
|
||||
|
||||
expect(scripts).toStrictEqual(['/js/home.js']);
|
||||
expect(renderMode).toBe('SSR');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -241,6 +241,33 @@ const Home = () => {
|
|||
};
|
||||
```
|
||||
|
||||
### useAppContext
|
||||
|
||||
获取应用级 Context, 包含字段为:
|
||||
* appConfig object, 通过 defineAppConfig 定义的 appConfig
|
||||
* renderMode string, 渲染模式,'CSR'|'SSR'|'SSG'
|
||||
* documentOnly boolean, 是否指定为 CSR
|
||||
* downgrade boolean, 是否为 SSR 降级
|
||||
|
||||
```ts title="src/document.ts"
|
||||
import { useAppContext } from 'ice';
|
||||
|
||||
function Document() {
|
||||
const { renderMode } = useAppContext();
|
||||
console.log(renderMode);
|
||||
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<Main />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### `<ClientOnly />`
|
||||
|
||||
:::caution
|
||||
|
|
|
|||
|
|
@ -199,3 +199,28 @@ function Document() {
|
|||
);
|
||||
}
|
||||
```
|
||||
|
||||
### usePageAssets
|
||||
|
||||
获取当前页面的所有 Assets 资源,包含 CSS 和 JS。
|
||||
|
||||
```ts title="src/document.ts"
|
||||
import { usePageAssets } from 'ice';
|
||||
|
||||
function Document() {
|
||||
const pageAssets = usePageAssets();
|
||||
const pageScripts = pageAssets.filter(src => src.indexOf('.js') > -1);
|
||||
|
||||
console.log(pageScripts);
|
||||
|
||||
return (
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<Main />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
Loading…
Reference in New Issue