diff --git a/.vscode/settings.json b/.vscode/settings.json index aee56f3ff939..4d52d856394e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,4 +59,5 @@ "[javascript]": { "editor.defaultFormatter": "biomejs.biome" }, + "todo-tree.tree.disableCompactFolders": true, } diff --git a/packages/runtime/plugin-runtime/package.json b/packages/runtime/plugin-runtime/package.json index c74515e6302e..254d97a5036e 100644 --- a/packages/runtime/plugin-runtime/package.json +++ b/packages/runtime/plugin-runtime/package.json @@ -232,8 +232,8 @@ "@types/node": "^14", "@types/react-side-effect": "^1.1.1", "jest": "^29", - "react": "^18", - "react-dom": "^18", + "react": "18.3.0-canary-8039e6d0b-20231026", + "react-dom": "18.3.0-canary-8039e6d0b-20231026", "ts-jest": "^29.1.0", "typescript": "^5", "webpack": "^5.93.0" diff --git a/packages/runtime/plugin-runtime/src/cli/template.ts b/packages/runtime/plugin-runtime/src/cli/template.ts index e21cec93955c..390d9bb57d1e 100644 --- a/packages/runtime/plugin-runtime/src/cli/template.ts +++ b/packages/runtime/plugin-runtime/src/cli/template.ts @@ -68,7 +68,8 @@ export const index = ({ customBootstrap?: string | false; mountId?: string; }) => - `import '@${metaName}/runtime/registry/${entryName}'; + // TODO: remove this + ` ${genRenderCode({ srcDirectory, internalSrcAlias, @@ -166,6 +167,7 @@ import App from '${ customEntry ? entry .replace('entry.tsx', 'App') + .replace('entry.jsx', 'App') .replace(srcDirectory, internalSrcAlias) : entry.replace(srcDirectory, internalSrcAlias).replace('.tsx', ''), ) diff --git a/packages/runtime/plugin-runtime/src/core/server/stream/afterTemplate.ts b/packages/runtime/plugin-runtime/src/core/server/stream/afterTemplate.ts index 4eb5bb9d6f6b..b67603c760d5 100644 --- a/packages/runtime/plugin-runtime/src/core/server/stream/afterTemplate.ts +++ b/packages/runtime/plugin-runtime/src/core/server/stream/afterTemplate.ts @@ -38,7 +38,7 @@ export function buildShellAfterTemplate( async function injectJs(template: string, entryName: string, nonce?: string) { const { routeManifest } = runtimeContext; const { routeAssets } = routeManifest; - const asyncEntry = routeAssets[`async-${entryName}`]; + const asyncEntry = routeAssets?.[`async-${entryName}`]; if (asyncEntry) { const { assets } = asyncEntry; const jsChunkStr = assets diff --git a/packages/runtime/plugin-runtime/src/core/server/stream/createReadableStream.ts b/packages/runtime/plugin-runtime/src/core/server/stream/createReadableStream.ts index 91f672a2fe08..1b053d4cf1c8 100644 --- a/packages/runtime/plugin-runtime/src/core/server/stream/createReadableStream.ts +++ b/packages/runtime/plugin-runtime/src/core/server/stream/createReadableStream.ts @@ -64,15 +64,15 @@ export const createReadableStreamFromElement: CreateReadableStreamFromElement = * So we use the `SHELL_STREAM_END_MARK` to mark the shell content' tail. */ let concatedChunk = chunkVec.join(''); - if (concatedChunk.includes(ESCAPED_SHELL_STREAM_END_MARK)) { - concatedChunk = concatedChunk.replace( - ESCAPED_SHELL_STREAM_END_MARK, - '', - ); - - shellChunkStatus = ShellChunkStatus.FINISH; - this.push(`${shellBefore}${concatedChunk}${shellAfter}`); - } + // if (concatedChunk.includes(ESCAPED_SHELL_STREAM_END_MARK)) { + concatedChunk = concatedChunk.replace( + ESCAPED_SHELL_STREAM_END_MARK, + '', + ); + + shellChunkStatus = ShellChunkStatus.FINISH; + this.push(`${shellBefore}${concatedChunk}${shellAfter}`); + // } } else { this.push(chunk); } diff --git a/packages/solutions/app-tools/src/plugins/analyze/index.ts b/packages/solutions/app-tools/src/plugins/analyze/index.ts index 240f2a1801a4..c834c7bb2857 100644 --- a/packages/solutions/app-tools/src/plugins/analyze/index.ts +++ b/packages/solutions/app-tools/src/plugins/analyze/index.ts @@ -168,7 +168,7 @@ export default ({ builder.onAfterBuild(async ({ stats }) => { const hookRunners = api.useHookRunners(); - await hookRunners.afterBuild({ stats }); + await hookRunners.afterBuild({ stats: stats as any }); await emitResolvedConfig(appContext.appDirectory, normalizedConfig); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0adc5f02da5..d7c68d66702d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2858,10 +2858,10 @@ importers: version: 5.15.3(@babel/core@7.24.7) '@loadable/component': specifier: 5.15.3 - version: 5.15.3(react@18.2.0) + version: 5.15.3(react@18.3.0-canary-8039e6d0b-20231026) '@loadable/server': specifier: 5.15.3 - version: 5.15.3(@loadable/component@5.15.3)(react@18.2.0) + version: 5.15.3(@loadable/component@5.15.3)(react@18.3.0-canary-8039e6d0b-20231026) '@modern-js-reduck/plugin-auto-actions': specifier: ^1.1.10 version: 1.1.10(@modern-js-reduck/store@1.1.10) @@ -2876,7 +2876,7 @@ importers: version: 1.1.10(@modern-js-reduck/store@1.1.10) '@modern-js-reduck/react': specifier: ^1.1.10 - version: 1.1.10(@types/react-dom@18.0.6)(@types/react@18.0.21)(react-dom@18.2.0)(react@18.2.0) + version: 1.1.10(@types/react-dom@18.0.6)(@types/react@18.0.21)(react-dom@18.3.0-canary-8039e6d0b-20231026)(react@18.3.0-canary-8039e6d0b-20231026) '@modern-js-reduck/store': specifier: ^1.1.10 version: 1.1.10 @@ -2924,16 +2924,16 @@ importers: version: 3.7.1 react-helmet: specifier: ^6.1.0 - version: 6.1.0(react@18.2.0) + version: 6.1.0(react@18.3.0-canary-8039e6d0b-20231026) react-is: specifier: ^18 version: 18.2.0 react-side-effect: specifier: ^2.1.1 - version: 2.1.2(react@18.2.0) + version: 2.1.2(react@18.3.0-canary-8039e6d0b-20231026) styled-components: specifier: ^5.3.1 - version: 5.3.5(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0) + version: 5.3.5(react-dom@18.3.0-canary-8039e6d0b-20231026)(react-is@18.2.0)(react@18.3.0-canary-8039e6d0b-20231026) devDependencies: '@modern-js/app-tools': specifier: workspace:* @@ -2955,7 +2955,7 @@ importers: version: link:../../../scripts/jest-config '@testing-library/react': specifier: ^13.4.0 - version: 13.4.0(react-dom@18.2.0)(react@18.2.0) + version: 13.4.0(react-dom@18.3.0-canary-8039e6d0b-20231026)(react@18.3.0-canary-8039e6d0b-20231026) '@types/cookie': specifier: 0.5.1 version: 0.5.1 @@ -2981,11 +2981,11 @@ importers: specifier: ^29 version: 29.5.0(@types/node@14.18.35)(ts-node@10.9.2) react: - specifier: ^18 - version: 18.2.0 + specifier: 18.3.0-canary-8039e6d0b-20231026 + version: 18.3.0-canary-8039e6d0b-20231026 react-dom: - specifier: ^18 - version: 18.2.0(react@18.2.0) + specifier: 18.3.0-canary-8039e6d0b-20231026 + version: 18.3.0-canary-8039e6d0b-20231026(react@18.3.0-canary-8039e6d0b-20231026) ts-jest: specifier: ^29.1.0 version: 29.1.0(@babel/core@7.24.7)(esbuild@0.17.19)(jest@29.5.0)(typescript@5.3.3) @@ -5637,6 +5637,73 @@ importers: '@modern-js/runtime': specifier: workspace:* version: link:../../../packages/runtime/plugin-runtime + date-fns: + specifier: ^2.29.3 + version: 2.29.3 + excerpts: + specifier: 0.0.3 + version: 0.0.3 + marked: + specifier: 4.2.12 + version: 4.2.12 + pg: + specifier: 8.10.0 + version: 8.10.0 + react: + specifier: 18.3.0-canary-8039e6d0b-20231026 + version: 18.3.0-canary-8039e6d0b-20231026 + react-dom: + specifier: 18.3.0-canary-8039e6d0b-20231026 + version: 18.3.0-canary-8039e6d0b-20231026(react@18.3.0-canary-8039e6d0b-20231026) + react-error-boundary: + specifier: 3.1.4 + version: 3.1.4(react@18.3.0-canary-8039e6d0b-20231026) + react-server-dom-webpack: + specifier: 18.3.0-canary-8039e6d0b-20231026 + version: 18.3.0-canary-8039e6d0b-20231026(react-dom@18.3.0-canary-8039e6d0b-20231026)(react@18.3.0-canary-8039e6d0b-20231026)(webpack@5.93.0) + sanitize-html: + specifier: 2.10.0 + version: 2.10.0 + server-only: + specifier: ^0.0.1 + version: 0.0.1 + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../packages/solutions/app-tools + '@modern-js/plugin-swc': + specifier: workspace:* + version: link:../../../packages/cli/plugin-swc + '@modern-js/server-core': + specifier: workspace:* + version: link:../../../packages/server/core + '@types/jest': + specifier: ^29 + version: 29.2.6 + '@types/node': + specifier: ^14 + version: 14.18.35 + '@types/pg': + specifier: 8.10.0 + version: 8.10.0 + '@types/react': + specifier: ^18 + version: 18.0.21 + '@types/react-dom': + specifier: ^18 + version: 18.0.6 + typescript: + specifier: ^5 + version: 5.4.5 + + tests/integration/basic-rsc-ssr: + dependencies: + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../packages/runtime/plugin-runtime + date-fns: + specifier: ^2.29.3 + version: 2.29.3 excerpts: specifier: 0.0.3 version: 0.0.3 @@ -13255,7 +13322,7 @@ packages: '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) dev: false - /@loadable/component@5.15.3(react@18.2.0): + /@loadable/component@5.15.3(react@18.3.0-canary-8039e6d0b-20231026): resolution: {integrity: sha512-VOgYgCABn6+/7aGIpg7m0Ruj34tGetaJzt4bQ345FwEovDQZ+dua+NWLmuJKv8rWZyxOUSfoJkmGnzyDXH2BAQ==} engines: {node: '>=8'} peerDependencies: @@ -13263,7 +13330,7 @@ packages: dependencies: '@babel/runtime': 7.24.7 hoist-non-react-statics: 3.3.2 - react: 18.2.0 + react: 18.3.0-canary-8039e6d0b-20231026 react-is: 16.13.1 dev: false @@ -13279,16 +13346,16 @@ packages: react-is: 16.13.1 dev: true - /@loadable/server@5.15.3(@loadable/component@5.15.3)(react@18.2.0): + /@loadable/server@5.15.3(@loadable/component@5.15.3)(react@18.3.0-canary-8039e6d0b-20231026): resolution: {integrity: sha512-Bm/BGe+RlChuHDKNNXpQOi4AJ0cKVuSLI+J8U0Q06zTIfT0S1RLoy85qs5RXm3cLIfefygL8+9bcYFgeWcoM8A==} engines: {node: '>=8'} peerDependencies: '@loadable/component': ^5.0.1 react: ^16.3.0 || ^17.0.0 || ^18.0.0 dependencies: - '@loadable/component': 5.15.3(react@18.2.0) + '@loadable/component': 5.15.3(react@18.3.0-canary-8039e6d0b-20231026) lodash: 4.17.21 - react: 18.2.0 + react: 18.3.0-canary-8039e6d0b-20231026 dev: false /@manypkg/find-root@1.1.0: @@ -13464,7 +13531,7 @@ packages: immer: 9.0.15 dev: false - /@modern-js-reduck/react@1.1.10(@types/react-dom@18.0.6)(@types/react@18.0.21)(react-dom@18.2.0)(react@18.2.0): + /@modern-js-reduck/react@1.1.10(@types/react-dom@18.0.6)(@types/react@18.0.21)(react-dom@18.3.0-canary-8039e6d0b-20231026)(react@18.3.0-canary-8039e6d0b-20231026): resolution: {integrity: sha512-URxdFeeI6zrbAPqha+FGh2zDMadnGJ3Fprgr0M1UXhZYxwsf31m6oukYqi/oMEh6HMFncT1mSQcWQYt0O6xzgg==} peerDependencies: '@types/react': ^18 @@ -13487,8 +13554,8 @@ packages: '@types/react-dom': 18.0.6 hoist-non-react-statics: 3.3.2 invariant: 2.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 18.3.0-canary-8039e6d0b-20231026 + react-dom: 18.3.0-canary-8039e6d0b-20231026(react@18.3.0-canary-8039e6d0b-20231026) dev: false /@modern-js-reduck/store@1.1.10: @@ -18430,6 +18497,20 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@testing-library/react@13.4.0(react-dom@18.3.0-canary-8039e6d0b-20231026)(react@18.3.0-canary-8039e6d0b-20231026): + resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==} + engines: {node: '>=12'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@babel/runtime': 7.24.7 + '@testing-library/dom': 8.14.0 + '@types/react-dom': 18.0.6 + react: 18.3.0-canary-8039e6d0b-20231026 + react-dom: 18.3.0-canary-8039e6d0b-20231026(react@18.3.0-canary-8039e6d0b-20231026) + dev: true + /@testing-library/user-event@14.4.3(@testing-library/dom@8.14.0): resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==} engines: {node: '>=12', npm: '>=6'} @@ -32841,7 +32922,6 @@ packages: loose-envify: 1.4.0 react: 18.3.0-canary-8039e6d0b-20231026 scheduler: 0.24.0-canary-8039e6d0b-20231026 - dev: false /react-dom@18.3.1(react@18.2.0): resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} @@ -32931,16 +33011,16 @@ packages: shallowequal: 1.1.0 dev: true - /react-helmet@6.1.0(react@18.2.0): + /react-helmet@6.1.0(react@18.3.0-canary-8039e6d0b-20231026): resolution: {integrity: sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==} peerDependencies: react: '>=16.3.0' dependencies: object-assign: 4.1.1 prop-types: 15.8.1 - react: 18.2.0 + react: 18.3.0-canary-8039e6d0b-20231026 react-fast-compare: 3.2.0 - react-side-effect: 2.1.2(react@18.2.0) + react-side-effect: 2.1.2(react@18.3.0-canary-8039e6d0b-20231026) dev: false /react-icons@4.11.0(react@18.2.0): @@ -33145,12 +33225,12 @@ packages: webpack: 5.93.0(esbuild@0.17.19) dev: false - /react-side-effect@2.1.2(react@18.2.0): + /react-side-effect@2.1.2(react@18.3.0-canary-8039e6d0b-20231026): resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==} peerDependencies: react: ^16.3.0 || ^17.0.0 || ^18.0.0 dependencies: - react: 18.2.0 + react: 18.3.0-canary-8039e6d0b-20231026 dev: false /react-style-singleton@2.2.1(@types/react@18.0.21)(react@18.2.0): @@ -33298,7 +33378,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: loose-envify: 1.4.0 - dev: false /react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} @@ -34194,7 +34273,6 @@ packages: resolution: {integrity: sha512-dWUzSzKcmDy+DQz/aNgYmaXMduHlXed7pTfL4UUkZwwHElNUVsJhZfItWIj2S+7t5o+8GrFA12se0CuJkrJVsA==} dependencies: loose-envify: 1.4.0 - dev: false /schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} @@ -35055,6 +35133,30 @@ packages: shallowequal: 1.1.0 supports-color: 5.5.0 + /styled-components@5.3.5(react-dom@18.3.0-canary-8039e6d0b-20231026)(react-is@18.2.0)(react@18.3.0-canary-8039e6d0b-20231026): + resolution: {integrity: sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==} + engines: {node: '>=10'} + requiresBuild: true + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + react-is: '>= 16.8.0' + dependencies: + '@babel/helper-module-imports': 7.22.15 + '@babel/traverse': 7.23.6(supports-color@5.5.0) + '@emotion/is-prop-valid': 1.2.1 + '@emotion/stylis': 0.8.5 + '@emotion/unitless': 0.7.5 + babel-plugin-styled-components: 1.13.3(styled-components@5.3.5) + css-to-react-native: 3.2.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.0-canary-8039e6d0b-20231026 + react-dom: 18.3.0-canary-8039e6d0b-20231026(react@18.3.0-canary-8039e6d0b-20231026) + react-is: 18.2.0 + shallowequal: 1.1.0 + supports-color: 5.5.0 + dev: false + /stylehacks@6.0.0(postcss@8.4.35): resolution: {integrity: sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==} engines: {node: ^14 || ^16 || >=18.0} diff --git a/tests/integration/basic-rsc-ssr/.browserslistrc b/tests/integration/basic-rsc-ssr/.browserslistrc new file mode 100644 index 000000000000..f5ceef6bb8ec --- /dev/null +++ b/tests/integration/basic-rsc-ssr/.browserslistrc @@ -0,0 +1,5 @@ +chrome >= 51 +edge >= 15 +firefox >= 54 +safari >= 10 +ios_saf >= 10 diff --git a/tests/integration/basic-rsc-ssr/modern.config.ts b/tests/integration/basic-rsc-ssr/modern.config.ts new file mode 100644 index 000000000000..f3a097042035 --- /dev/null +++ b/tests/integration/basic-rsc-ssr/modern.config.ts @@ -0,0 +1,53 @@ +import { applyBaseConfig } from '../../utils/applyBaseConfig'; +import ReactServerWebpackPlugin from 'react-server-dom-webpack/plugin'; +import path from 'path'; + +export default applyBaseConfig({ + runtime: { + state: false, + router: false, + }, + source: { + enableCustomEntry: true, + // 避免 Modern.js 项目代码中用的是其他版本 + alias: { + // react$: path.resolve(__dirname, 'node_modules/react'), + // 'react-dom/client': require.resolve('react-dom/client'), + // 'react-dom': require.resolve('react-dom'), + }, + }, + server: { + ssr: { + mode: 'stream', + }, + }, + tools: { + babel(config, { modifyPresetReactOptions }) { + modifyPresetReactOptions({ + runtime: 'automatic', + }); + }, + devServer: { + hot: false, + }, + // webpack(config, ctx) { + // config.resolve.conditionNames = ['require', 'node']; + // }, + bundlerChain(chain) { + chain + .plugin('react-server-dom-webpack-plugin') + .use(ReactServerWebpackPlugin, [ + { + isServer: false, + clientReferences: [ + { + directory: './src', + recursive: true, + include: /\.(js|jsx|ts|tsx)$/, + }, + ], + }, + ]); + }, + }, +}); diff --git a/tests/integration/basic-rsc-ssr/package.json b/tests/integration/basic-rsc-ssr/package.json new file mode 100644 index 000000000000..7d5eb57c1a54 --- /dev/null +++ b/tests/integration/basic-rsc-ssr/package.json @@ -0,0 +1,44 @@ +{ + "private": true, + "name": "basic-rsc-ssr", + "version": "2.9.0", + "scripts": { + "dev": "BUNDLER=webpack NODE_OPTIONS='--conditions=react-server' modern dev", + "build": "BUNDLER=webpack modern build", + "inspect": "BUNDLER=webpack NODE_OPTIONS='--conditions=react-server' modern inspect", + "serve": "modern serve", + "new": "modern new", + "lint": "modern lint" + }, + "engines": { + "node": ">=14.17.6" + }, + "eslintIgnore": [ + "node_modules/", + "dist/" + ], + "dependencies": { + "@modern-js/runtime": "workspace:*", + "react": "18.3.0-canary-8039e6d0b-20231026", + "react-dom": "18.3.0-canary-8039e6d0b-20231026", + "react-server-dom-webpack": "18.3.0-canary-8039e6d0b-20231026", + "marked": "4.2.12", + "sanitize-html": "2.10.0", + "react-error-boundary": "3.1.4", + "pg": "8.10.0", + "date-fns": "^2.29.3", + "server-only": "^0.0.1", + "excerpts": "0.0.3" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*", + "@modern-js/plugin-swc": "workspace:*", + "@modern-js/server-core": "workspace:*", + "@types/jest": "^29", + "@types/node": "^14", + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/pg": "8.10.0", + "typescript": "^5" + } +} diff --git a/tests/integration/basic-rsc-ssr/server/modern.server.ts b/tests/integration/basic-rsc-ssr/server/modern.server.ts new file mode 100644 index 000000000000..840504d83ece --- /dev/null +++ b/tests/integration/basic-rsc-ssr/server/modern.server.ts @@ -0,0 +1,12 @@ +import reactServerRegister from 'react-server-dom-webpack/node-register'; +import { + defineConfig, + type RenderMiddleware, +} from '@modern-js/app-tools/server'; +import rscServerPlugin from './serverPlugin'; + +reactServerRegister(); + +export default defineConfig({ + plugins: [rscServerPlugin()], +}); diff --git a/tests/integration/basic-rsc-ssr/server/server.d.ts b/tests/integration/basic-rsc-ssr/server/server.d.ts new file mode 100644 index 000000000000..712c8256e0cd --- /dev/null +++ b/tests/integration/basic-rsc-ssr/server/server.d.ts @@ -0,0 +1,57 @@ +declare module 'react-server-dom-webpack/node-register'; +declare module 'react-server-dom-webpack/server'; + +type Reference = {}; + +type TemporaryReferenceSet = Map; + +type ImportManifestEntry = { + id: string; + chunks: string[]; + name: string; +}; + +type ModuleLoading = null | { + prefix: string; + crossOrigin?: 'use-credentials' | ''; +}; + +type SSRModuleMap = null | { + [clientId: string]: { + [clientExportName: string]: ImportManifestEntry; + }; +}; + +type SSRManifest = { + moduleMap: SSRModuleMap; + moduleLoading: ModuleLoading; +}; + +type ServerManifest = { + [id: string]: ImportManifestEntry; +}; + +type ClientManifest = { + [id: string]: ImportManifestEntry; +}; + +declare module 'react-server-dom-webpack/server.edge' { + type Options = { + environmentName?: string; + identifierPrefix?: string; + signal?: AbortSignal; + temporaryReferences?: TemporaryReferenceSet; + onError?: ((error: unknown) => void) | undefined; + onPostpone?: ((reason: string) => void) | undefined; + }; + + export function renderToReadableStream( + model: ReactClientValue, + webpackMap: ClientManifest, + options?: Options, + ): ReadableStream; + export function decodeReply( + body: string | FormData, + webpackMap?: ServerManifest, + ): Promise; +} diff --git a/tests/integration/basic-rsc-ssr/server/serverPlugin.ts b/tests/integration/basic-rsc-ssr/server/serverPlugin.ts new file mode 100644 index 000000000000..1ba2e2966b6f --- /dev/null +++ b/tests/integration/basic-rsc-ssr/server/serverPlugin.ts @@ -0,0 +1,83 @@ +import type { Context, ServerPlugin } from '@modern-js/server-core'; +import path from 'path'; +import { readFileSync } from 'fs'; + +import { renderToReadableStream } from 'react-server-dom-webpack/server.edge'; +import React from 'react'; + +interface IProps { + selectedId: string; + isEditing: boolean; + searchText: string; +} + +const renderRsc = async (distDir: string, props: IProps) => { + const ReactApp = (await import('../src/App')).default; + const manifest = readFileSync( + path.resolve(distDir, './react-client-manifest.json'), + 'utf8', + ); + + const moduleMap = JSON.parse(manifest); + const readable = renderToReadableStream( + React.createElement(ReactApp, props), + moduleMap, + ); + return readable; +}; + +const handleResponse = async ( + c: Context, + distDir: string, + redirectId?: string, +) => { + const location = JSON.parse(c.req.query('location') as string); + if (redirectId) { + location.selectedId = redirectId; + } + const readable = await renderRsc(distDir, { + selectedId: location.selectedId, + isEditing: location.isEditing, + searchText: location.searchText, + }); + return c.body(readable, 200); +}; + +export default (): ServerPlugin => ({ + name: 'rsc-server-plugin', + setup(api) { + return { + prepare() { + const { middlewares, distDirectory } = api.useAppContext(); + console.log('push middleware'); + middlewares.unshift({ + name: 'rsc', + path: '/react', + handler: async (c, next) => { + // TODO: 临时代码 + return await handleResponse(c, distDirectory); + }, + }); + + // middlewares.unshift({ + // name: 'notes', + // path: '/notes/:id', + // handler: async (c, next) => { + // const { req } = c; + // const now = new Date(); + // const updatedId = Number(req.param('id')); + // await pool.query( + // 'update notes set title = $1, body = $2, updated_at = $3 where id = $4', + // [req.body.title, req.body.body, now, updatedId], + // ); + // await writeFile( + // path.resolve(NOTES_PATH, `${updatedId}.md`), + // req.body.body, + // 'utf8', + // ); + // }, + // }); + }, + }; + }, +}); diff --git a/tests/integration/basic-rsc-ssr/src/App.js b/tests/integration/basic-rsc-ssr/src/App.js new file mode 100644 index 000000000000..9e414fc7b3f2 --- /dev/null +++ b/tests/integration/basic-rsc-ssr/src/App.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { Suspense } from 'react'; + +import Show from './components/server/Show'; +import Footer from './components/server/Footer'; +import Header from './components/server/Header'; + +export default function App({ selectedId, isEditing, searchText }) { + return ( +
+
+ Loading...
}> + + +