diff --git a/packages/ice/CHANGELOG.md b/packages/ice/CHANGELOG.md index 05b741009e..20f084b61a 100644 --- a/packages/ice/CHANGELOG.md +++ b/packages/ice/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 3.2.3 + +### Patch Changes + +- 94e7dff2: fix: pass `getRoutesFile` for onDemand server runner. +- 51411c4e: feat: hooks for server render +- 71f32f9c: fix: add default polyfill for signal +- Updated dependencies [d1df9ffa] +- Updated dependencies [51411c4e] +- Updated dependencies [71f32f9c] + - @ice/runtime@1.2.2 + ## 3.2.2 ### Patch Changes diff --git a/packages/ice/package.json b/packages/ice/package.json index 317631cbc1..03eef670b2 100644 --- a/packages/ice/package.json +++ b/packages/ice/package.json @@ -1,6 +1,6 @@ { "name": "@ice/app", - "version": "3.2.2", + "version": "3.2.3", "description": "provide scripts and configuration used by web framework ice", "type": "module", "main": "./esm/index.js", @@ -38,7 +38,7 @@ "dependencies": { "@ice/bundles": "0.1.10", "@ice/route-manifest": "1.2.0", - "@ice/runtime": "^1.2.1", + "@ice/runtime": "^1.2.2", "@ice/webpack-config": "1.0.15", "@swc/helpers": "0.5.1", "@types/express": "^4.17.14", diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index 5fa2210709..6409007ef2 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -162,6 +162,10 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt RUNTIME_EXPORTS.forEach(exports => { generatorAPI.addExport(exports); }); + // Add polyfills. + generatorAPI.addEntryImportAhead({ + source: '@ice/runtime/polyfills/signal', + }); const routeManifest = new RouteManifest(); const ctx = new Context({ rootDir, @@ -311,6 +315,7 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt task: platformTaskConfig, server, csr, + getRoutesFile: () => routeManifest.getRoutesFile(), }); addWatchEvent([ /src\/?[\w*-:.$]+$/, diff --git a/packages/ice/src/service/ServerRunner.ts b/packages/ice/src/service/ServerRunner.ts index 4a466cad52..3ab8a44dad 100644 --- a/packages/ice/src/service/ServerRunner.ts +++ b/packages/ice/src/service/ServerRunner.ts @@ -27,6 +27,7 @@ interface InitOptions { server: UserConfig['server']; csr: boolean; task: TaskConfig; + getRoutesFile: () => string[]; } type ResolveCallback = Parameters[1]; @@ -88,12 +89,14 @@ class ServerRunner extends Runner { server, rootDir, csr, + getRoutesFile, }: InitOptions) { const transformPlugins = getCompilerPlugins(rootDir, { ...task.config, fastRefresh: false, enableEnv: false, polyfill: false, + getRoutesFile, swcOptions: { nodeTransform: true, // Remove all exports except pageConfig when ssr and ssg both are false. diff --git a/packages/ice/templates/core/entry.server.ts.ejs b/packages/ice/templates/core/entry.server.ts.ejs index 746a85fe28..48b7a4eb0f 100644 --- a/packages/ice/templates/core/entry.server.ts.ejs +++ b/packages/ice/templates/core/entry.server.ts.ejs @@ -6,6 +6,7 @@ import { commons, statics } from './runtimeModules'; import * as app from '@/app'; import Document from '@/document'; import type { RenderMode, DistType } from '@ice/runtime'; +import type { RenderToPipeableStreamOptions } from 'react-dom/server'; // @ts-ignore import assetsManifest from 'virtual:assets-manifest.json'; import createRoutes from './routes'; @@ -42,6 +43,7 @@ interface RenderOptions { distType?: DistType; publicPath?: string; serverData?: any; + streamOptions?: RenderToPipeableStreamOptions; } export async function renderToHTML(requestContext, options: RenderOptions = {}) { @@ -71,28 +73,22 @@ export async function renderToEntry(requestContext, options: RenderOptions = {}) <% } -%> function mergeOptions(options) { - const { documentOnly, renderMode = 'SSR', basename, serverOnlyBasename, routePath, disableFallback, distType, prependCode, serverData, publicPath } = options; + const { renderMode = 'SSR', basename, publicPath } = options; if (publicPath) { assetsManifest.publicPath = publicPath; } return { + ...options, app, assetsManifest, createRoutes, runtimeModules, Document, - serverOnlyBasename, basename: basename || getRouterBasename(), - documentOnly, renderMode, - routePath, - disableFallback, routesConfig, - distType, - prependCode, - serverData, runtimeOptions: { <% if (runtimeOptions.exports) { -%> <%- runtimeOptions.exports %> diff --git a/packages/plugin-i18n/package.json b/packages/plugin-i18n/package.json index ab8f63e869..1d8ab5d8d3 100644 --- a/packages/plugin-i18n/package.json +++ b/packages/plugin-i18n/package.json @@ -55,8 +55,8 @@ "webpack-dev-server": "^4.13.2" }, "peerDependencies": { - "@ice/app": "^3.2.2", - "@ice/runtime": "^1.2.1" + "@ice/app": "^3.2.3", + "@ice/runtime": "^1.2.2" }, "publishConfig": { "access": "public" diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index f1fd1580b2..44503c63c6 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,13 @@ # @ice/runtime +## 1.2.2 + +### Patch Changes + +- d1df9ffa: fix: should return after reject error +- 51411c4e: feat: hooks for server render +- 71f32f9c: fix: add default polyfill for signal + ## 1.2.1 ### Patch Changes diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 35ebcc3215..6f1d9c51bc 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@ice/runtime", - "version": "1.2.1", + "version": "1.2.2", "description": "Runtime module for ice.js", "type": "module", "types": "./esm/index.d.ts", @@ -15,7 +15,8 @@ "./router": "./esm/router.js", "./single-router": "./esm/singleRouter.js", "./types": "./esm/types.js", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./polyfills/signal": "./esm/polyfills/signal.js" }, "files": [ "esm", @@ -42,7 +43,9 @@ "regenerator-runtime": "^0.13.9", "@remix-run/web-fetch": "^4.3.3" }, - "sideEffects": false, + "sideEffects": [ + "./esm/polyfills/signal.js" + ], "dependencies": { "@ice/jsx-runtime": "^0.2.1", "@remix-run/router": "1.6.1", diff --git a/packages/runtime/src/polyfills/signal.ts b/packages/runtime/src/polyfills/signal.ts new file mode 100644 index 0000000000..a04fa00650 --- /dev/null +++ b/packages/runtime/src/polyfills/signal.ts @@ -0,0 +1,23 @@ +// Add polyfill of Request.prototype.signal for some browser compatibility. +// eslint-disable-next-line +if (import.meta.renderer === 'client' && window.Request && !window.Request.prototype.hasOwnProperty('signal')) { + (function (self) { + const OriginalRequest = window.Request; + function Request(input: RequestInfo | URL, init?: RequestInit) { + if (input instanceof OriginalRequest) { + this.signal = input.signal; + } + this.signal = init.signal || this.signal || (function () { + if ('AbortController' in window) { + let ctrl = new AbortController(); + return ctrl.signal; + } + }()); + OriginalRequest.call(this, input, init); + } + Request.prototype = Object.create(OriginalRequest.prototype); + Request.prototype.constructor = Request; + // @ts-expect-error for overwrite the original Request. + self.Request = Request; + })(window); +} diff --git a/packages/runtime/src/runServerApp.tsx b/packages/runtime/src/runServerApp.tsx index 810e3eae67..6e625aa23e 100644 --- a/packages/runtime/src/runServerApp.tsx +++ b/packages/runtime/src/runServerApp.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import * as ReactDOMServer from 'react-dom/server'; import { parsePath } from 'history'; import type { Location } from 'history'; +import type { RenderToPipeableStreamOptions } from 'react-dom/server'; import type { AppContext, RouteItem, ServerContext, AppExport, AssetsManifest, @@ -51,6 +52,7 @@ interface RenderOptions { distType?: Array<'html' | 'javascript'>; prependCode?: string; serverData?: any; + streamOptions?: RenderToPipeableStreamOptions; } interface Piper { @@ -154,12 +156,21 @@ export async function renderToResponse(requestContext: ServerContext, renderOpti res.statusCode = 200; res.setHeader('Content-Type', 'text/html; charset=utf-8'); + const { streamOptions = {} } = renderOptions; + const { onShellReady, onShellError, onError, onAllReady } = streamOptions; + return new Promise((resolve, reject) => { // Send stream result to ServerResponse. pipe(res, { + onShellReady: () => { + onShellReady && onShellReady(); + }, onShellError: async (err) => { + onShellError && onShellError(err); + if (renderOptions.disableFallback) { reject(err); + return; } // downgrade to CSR. @@ -170,12 +181,14 @@ export async function renderToResponse(requestContext: ServerContext, renderOpti resolve(); }, onError: async (err) => { + onError && onError(err); // onError triggered after shell ready, should not downgrade to csr // and should not be throw to break the render process console.error('PipeToResponse error.'); console.error(err); }, onAllReady: () => { + onAllReady && onAllReady(); resolve(); }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6a08e6220..6e1155f38f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1057,7 +1057,7 @@ importers: specifiers: '@ice/bundles': 0.1.10 '@ice/route-manifest': 1.2.0 - '@ice/runtime': ^1.2.1 + '@ice/runtime': ^1.2.2 '@ice/webpack-config': 1.0.15 '@swc/helpers': 0.5.1 '@types/babel__generator': ^7.6.4 @@ -1419,7 +1419,7 @@ importers: consola: 2.15.3 css: 2.2.4 lodash.merge: 4.6.2 - rax-compat: link:../rax-compat + rax-compat: 0.2.3 style-unit: 3.0.5 stylesheet-loader: 0.9.1 devDependencies: @@ -5324,6 +5324,13 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@ice/appear/0.1.5: + resolution: {integrity: sha512-wChA3UYu9NHe9E9igDtRgNQ9vWmwNGpLyBrD4YAlqOvvRikwTO11by5yDUFtKjXlDoebQqKM6Jma6titdvGbPQ==} + peerDependencies: + react: ^18 + react-dom: ^18 + dev: false + /@ice/pkg-plugin-component/1.0.0: resolution: {integrity: sha512-Mff6Em1RwY2NOZgciQyy2NXL65u9ebHV2Jqb5746vUVEh0l7Xpokb+3djV1pKcgED4wU8pSuSQ6jj+hXd9Ht1Q==} dependencies: @@ -17702,6 +17709,18 @@ packages: rax-is-valid-element: 1.0.1 dev: false + /rax-compat/0.2.3: + resolution: {integrity: sha512-59l8lNIv53pDroiDqArnl1HKuviroOP6kn1Y/ohjadMFgVXD6nbgohZfUmrViNH91BEbsDqNx+pN0DrezaRqjw==} + peerDependencies: + react: ^18 + react-dom: ^18 + dependencies: + '@ice/appear': 0.1.5 + '@swc/helpers': 0.4.14 + create-react-class: 15.7.0 + style-unit: 3.0.5 + dev: false + /rax-create-factory/1.0.0_rax@1.2.3: resolution: {integrity: sha512-blBaVrurj/BOWelJhQWiuc0Kk8Ons1jsNsX78omaPBLkSOL7OkyJ3NC/0iKXHu425yWrGB6e5vho/qabROC7VQ==} engines: {npm: '>=3.0.0'}