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/index.ts b/packages/runtime/plugin-runtime/src/cli/index.ts
index 4b9a65d358e1..27cb7e5a3cfb 100644
--- a/packages/runtime/plugin-runtime/src/cli/index.ts
+++ b/packages/runtime/plugin-runtime/src/cli/index.ts
@@ -15,7 +15,7 @@ import { generateCode } from './code';
import { builderPluginAlias } from './alias';
export { isRuntimeEntry } from './entry';
-export { statePlugin, ssrPlugin, routerPlugin, documentPlugin };
+export { statePlugin, ssrPlugin, routerPlugin };
export const runtimePlugin = (params?: {
plugins?: CliPlugin[];
}): CliPlugin => ({
@@ -32,7 +32,7 @@ export const runtimePlugin = (params?: {
ssrPlugin(),
routerPlugin(),
statePlugin(),
- documentPlugin(),
+ // documentPlugin(),
],
setup: api => {
return {
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/packages/toolkit/utils/src/cli/is/project.ts b/packages/toolkit/utils/src/cli/is/project.ts
index 959c0470dc3b..e52dedeea5be 100644
--- a/packages/toolkit/utils/src/cli/is/project.ts
+++ b/packages/toolkit/utils/src/cli/is/project.ts
@@ -116,7 +116,7 @@ export const isReact18 = (cwd: string = process.cwd()) => {
return false;
}
- return semver.satisfies(semver.minVersion(deps.react)!, '>=18.0.0');
+ return semver.gte(semver.minVersion(deps.react)!, '18.0.0');
};
/**
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 37dd9d426351..d7c68d66702d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1009,7 +1009,7 @@ importers:
version: 0.2.4(@rspack/core@0.5.1)(esbuild@0.17.19)
'@rsdoctor/utils':
specifier: ^0.2.4
- version: 0.2.4(@rspack/core@0.5.1)(esbuild@0.17.19)
+ version: 0.2.4(esbuild@0.17.19)
birpc:
specifier: 0.2.13
version: 0.2.13
@@ -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)
@@ -5435,7 +5435,7 @@ importers:
version: 18.2.0
react-dom:
specifier: ^18
- version: 18.2.0(react@18.2.0)
+ version: 18.3.1(react@18.2.0)
devDependencies:
'@modern-js/app-tools':
specifier: workspace:*
@@ -5608,7 +5608,7 @@ importers:
version: 18.2.0
react-dom:
specifier: ^18
- version: 18.2.0(react@18.2.0)
+ version: 18.3.1(react@18.2.0)
devDependencies:
'@modern-js/app-tools':
specifier: workspace:*
@@ -5632,6 +5632,134 @@ importers:
specifier: ^5
version: 5.3.3
+ tests/integration/basic-next-app:
+ 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
+ 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
+ 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/bff-express:
dependencies:
'@modern-js/plugin-bff':
@@ -13194,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:
@@ -13202,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
@@ -13218,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:
@@ -13403,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
@@ -13426,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:
@@ -16552,6 +16680,36 @@ packages:
- webpack-cli
/@rsdoctor/utils@0.2.4(@rspack/core@0.5.1)(esbuild@0.17.19):
+ resolution: {integrity: sha512-iztfgAyRMtqNW7juiY77gOdVuVDWi4Jc9tA3BqHPUg+31PIZmUB1dujvr6tfm4VnfGZy5gu4UMJn/8IG32d4/g==}
+ dependencies:
+ '@babel/code-frame': 7.24.2
+ '@rsdoctor/types': 0.2.4(@rspack/core@0.5.1)(esbuild@0.17.19)
+ '@types/estree': 1.0.0
+ acorn: 8.12.1
+ acorn-import-assertions: 1.9.0(acorn@8.12.1)
+ acorn-walk: 8.3.2
+ bytes: 3.1.2
+ chalk: 4.1.2
+ connect: 3.7.0
+ deep-eql: 4.1.0
+ envinfo: 7.13.0
+ fs-extra: 11.2.0
+ get-port: 5.1.1
+ json-stream-stringify: 3.0.1
+ lines-and-columns: 2.0.4
+ lodash: 4.17.21
+ rslog: 1.2.1
+ strip-ansi: 6.0.1
+ transitivePeerDependencies:
+ - '@rspack/core'
+ - '@swc/core'
+ - esbuild
+ - supports-color
+ - uglify-js
+ - webpack-cli
+ dev: true
+
+ /@rsdoctor/utils@0.2.4(esbuild@0.17.19):
resolution: {integrity: sha512-iztfgAyRMtqNW7juiY77gOdVuVDWi4Jc9tA3BqHPUg+31PIZmUB1dujvr6tfm4VnfGZy5gu4UMJn/8IG32d4/g==}
dependencies:
'@babel/code-frame': 7.24.2
@@ -16579,6 +16737,7 @@ packages:
- supports-color
- uglify-js
- webpack-cli
+ dev: false
/@rspack/binding-darwin-arm64@0.5.1:
resolution: {integrity: sha512-Kc0b94ZN1ecUu2Gyj20kGLWzOrdJbeN1JUTMKZx6jlLa3m7uJ+FhRjnsqFmZ5kdK2zx722ejoKr7xkrl7hOkuw==}
@@ -18338,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'}
@@ -18875,6 +19048,14 @@ packages:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: true
+ /@types/pg@8.10.0:
+ resolution: {integrity: sha512-LniJdzpYb++pT2FnkfrCX1OA6PiauULKBE+Bp7XvlWcC0NZOXqpBMscdbxEITqpGMvDIk71TaNrx54Qd04r3rA==}
+ dependencies:
+ '@types/node': 18.11.17
+ pg-protocol: 1.6.1
+ pg-types: 4.0.2
+ dev: true
+
/@types/prettier@2.6.3:
resolution: {integrity: sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==}
@@ -19404,7 +19585,7 @@ packages:
'@vue/shared': 3.3.4
estree-walker: 2.0.2
magic-string: 0.30.5
- postcss: 8.4.40
+ postcss: 8.4.41
source-map-js: 1.2.0
dev: false
@@ -19708,6 +19889,15 @@ packages:
acorn: ^8
dependencies:
acorn: 8.11.3
+ dev: false
+
+ /acorn-import-assertions@1.9.0(acorn@8.12.1):
+ resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
+ peerDependencies:
+ acorn: ^8
+ dependencies:
+ acorn: 8.12.1
+ dev: true
/acorn-import-attributes@1.9.5(acorn@8.11.3):
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
@@ -19747,6 +19937,13 @@ packages:
acorn: 8.12.1
dev: true
+ /acorn-loose@8.4.0:
+ resolution: {integrity: sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==}
+ engines: {node: '>=0.4.0'}
+ dependencies:
+ acorn: 8.12.1
+ dev: false
+
/acorn-node@1.8.2:
resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
dependencies:
@@ -20926,6 +21123,11 @@ packages:
/buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ /buffer-writer@2.0.0:
+ resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
+ engines: {node: '>=4'}
+ dev: false
+
/buffer-xor@1.0.3:
resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==}
dev: false
@@ -21209,6 +21411,28 @@ packages:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
+ /cheerio@0.22.0:
+ resolution: {integrity: sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ css-select: 1.2.0
+ dom-serializer: 0.1.1
+ entities: 1.1.2
+ htmlparser2: 3.10.1
+ lodash.assignin: 4.2.0
+ lodash.bind: 4.2.1
+ lodash.defaults: 4.2.0
+ lodash.filter: 4.6.0
+ lodash.flatten: 4.4.0
+ lodash.foreach: 4.5.0
+ lodash.map: 4.6.0
+ lodash.merge: 4.6.2
+ lodash.pick: 4.4.0
+ lodash.reduce: 4.6.0
+ lodash.reject: 4.6.0
+ lodash.some: 4.6.0
+ dev: false
+
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@@ -22068,6 +22292,15 @@ packages:
webpack: 5.93.0(esbuild@0.17.19)
dev: false
+ /css-select@1.2.0:
+ resolution: {integrity: sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==}
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 2.1.3
+ domutils: 1.5.1
+ nth-check: 1.0.2
+ dev: false
+
/css-select@4.3.0:
resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
dependencies:
@@ -22113,6 +22346,10 @@ packages:
resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==}
dev: false
+ /css-what@2.1.3:
+ resolution: {integrity: sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==}
+ dev: false
+
/css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'}
@@ -22730,6 +22967,13 @@ packages:
'@babel/runtime': 7.24.7
csstype: 3.1.3
+ /dom-serializer@0.1.1:
+ resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==}
+ dependencies:
+ domelementtype: 1.3.1
+ entities: 1.1.2
+ dev: false
+
/dom-serializer@1.4.1:
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
dependencies:
@@ -22754,6 +22998,10 @@ packages:
engines: {node: '>=4'}
dev: false
+ /domelementtype@1.3.1:
+ resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}
+ dev: false
+
/domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
@@ -22764,6 +23012,12 @@ packages:
dependencies:
webidl-conversions: 7.0.0
+ /domhandler@2.4.2:
+ resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==}
+ dependencies:
+ domelementtype: 1.3.1
+ dev: false
+
/domhandler@4.3.1:
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
engines: {node: '>= 4'}
@@ -22776,6 +23030,20 @@ packages:
dependencies:
domelementtype: 2.3.0
+ /domutils@1.5.1:
+ resolution: {integrity: sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==}
+ dependencies:
+ dom-serializer: 0.1.1
+ domelementtype: 1.3.1
+ dev: false
+
+ /domutils@1.7.0:
+ resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
+ dependencies:
+ dom-serializer: 0.1.1
+ domelementtype: 1.3.1
+ dev: false
+
/domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
dependencies:
@@ -22970,6 +23238,10 @@ packages:
dependencies:
ansi-colors: 4.1.3
+ /entities@1.1.2:
+ resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
+ dev: false
+
/entities@2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
@@ -23910,6 +24182,12 @@ packages:
safe-buffer: 5.2.1
dev: false
+ /excerpts@0.0.3:
+ resolution: {integrity: sha512-osE67JikKwTcbZYgEMZrnDFJMto8HDwlYKsjKPyVIkRvOixG6NWqj0hfKkISUwz5R7HOusJEzSqyZmLw565AJQ==}
+ dependencies:
+ cheerio: 0.22.0
+ dev: false
+
/execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
@@ -25438,6 +25716,17 @@ packages:
resolution: {integrity: sha512-SZwGvLGNtgp8GbgFX7oXEp8OR1aBt5LliX6dG0kdD1kl3KhMonN0QcSa/A3TsTgFewaGCbIryQunjayWDXzxmw==}
dev: false
+ /htmlparser2@3.10.1:
+ resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
+ dependencies:
+ domelementtype: 1.3.1
+ domhandler: 2.4.2
+ domutils: 1.7.0
+ entities: 1.1.2
+ inherits: 2.0.4
+ readable-stream: 3.6.0
+ dev: false
+
/htmlparser2@6.1.0:
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
dependencies:
@@ -25453,7 +25742,6 @@ packages:
domhandler: 5.0.3
domutils: 3.1.0
entities: 4.5.0
- dev: true
/htmlparser2@9.1.0:
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
@@ -27526,6 +27814,14 @@ packages:
/lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+ /lodash.assignin@4.2.0:
+ resolution: {integrity: sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==}
+ dev: false
+
+ /lodash.bind@4.2.1:
+ resolution: {integrity: sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==}
+ dev: false
+
/lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
@@ -27536,22 +27832,33 @@ packages:
/lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+ /lodash.defaults@4.2.0:
+ resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
+ dev: false
+
/lodash.escape@4.0.1:
resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==}
dev: true
+ /lodash.filter@4.6.0:
+ resolution: {integrity: sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==}
+ dev: false
+
/lodash.flatmap@4.5.0:
resolution: {integrity: sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg==}
dev: false
/lodash.flatten@4.4.0:
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
- dev: true
/lodash.flow@3.5.0:
resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==}
dev: true
+ /lodash.foreach@4.5.0:
+ resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
+ dev: false
+
/lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
dev: false
@@ -27575,6 +27882,10 @@ packages:
/lodash.kebabcase@4.1.1:
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
+ /lodash.map@4.6.0:
+ resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==}
+ dev: false
+
/lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
@@ -27585,13 +27896,29 @@ packages:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
dev: true
+ /lodash.pick@4.4.0:
+ resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==}
+ dev: false
+
/lodash.pullall@4.2.0:
resolution: {integrity: sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==}
dev: true
+ /lodash.reduce@4.6.0:
+ resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==}
+ dev: false
+
+ /lodash.reject@4.6.0:
+ resolution: {integrity: sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==}
+ dev: false
+
/lodash.snakecase@4.1.1:
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
+ /lodash.some@4.6.0:
+ resolution: {integrity: sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==}
+ dev: false
+
/lodash.startcase@4.4.0:
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
@@ -27757,6 +28084,12 @@ packages:
react: 18.2.0
dev: true
+ /marked@4.2.12:
+ resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==}
+ engines: {node: '>= 12'}
+ hasBin: true
+ dev: false
+
/md5.js@1.3.5:
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
dependencies:
@@ -28865,6 +29198,12 @@ packages:
resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
dev: true
+ /nth-check@1.0.2:
+ resolution: {integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==}
+ dependencies:
+ boolbase: 1.0.0
+ dev: false
+
/nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
dependencies:
@@ -29022,6 +29361,10 @@ packages:
resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==}
dev: false
+ /obuf@1.1.2:
+ resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
+ dev: true
+
/on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
@@ -29217,6 +29560,10 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
+ /packet-reader@1.0.0:
+ resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
+ dev: false
+
/pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
dev: false
@@ -29297,6 +29644,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /parse-srcset@1.0.2:
+ resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
+ dev: false
+
/parse5@6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
dev: false
@@ -29440,6 +29791,78 @@ packages:
q: 1.5.1
dev: false
+ /pg-connection-string@2.6.4:
+ resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==}
+ dev: false
+
+ /pg-int8@1.0.1:
+ resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+ engines: {node: '>=4.0.0'}
+
+ /pg-numeric@1.0.2:
+ resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /pg-pool@3.6.2(pg@8.10.0):
+ resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==}
+ peerDependencies:
+ pg: '>=8.0'
+ dependencies:
+ pg: 8.10.0
+ dev: false
+
+ /pg-protocol@1.6.1:
+ resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==}
+
+ /pg-types@2.2.0:
+ resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+ engines: {node: '>=4'}
+ dependencies:
+ pg-int8: 1.0.1
+ postgres-array: 2.0.0
+ postgres-bytea: 1.0.0
+ postgres-date: 1.0.7
+ postgres-interval: 1.2.0
+ dev: false
+
+ /pg-types@4.0.2:
+ resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==}
+ engines: {node: '>=10'}
+ dependencies:
+ pg-int8: 1.0.1
+ pg-numeric: 1.0.2
+ postgres-array: 3.0.2
+ postgres-bytea: 3.0.0
+ postgres-date: 2.1.0
+ postgres-interval: 3.0.0
+ postgres-range: 1.1.4
+ dev: true
+
+ /pg@8.10.0:
+ resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==}
+ engines: {node: '>= 8.0.0'}
+ peerDependencies:
+ pg-native: '>=3.0.1'
+ peerDependenciesMeta:
+ pg-native:
+ optional: true
+ dependencies:
+ buffer-writer: 2.0.0
+ packet-reader: 1.0.0
+ pg-connection-string: 2.6.4
+ pg-pool: 3.6.2(pg@8.10.0)
+ pg-protocol: 1.6.1
+ pg-types: 2.2.0
+ pgpass: 1.0.5
+ dev: false
+
+ /pgpass@1.0.5:
+ resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
+ dependencies:
+ split2: 4.2.0
+ dev: false
+
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
@@ -30392,15 +30815,6 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
- /postcss@8.4.38:
- resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
- engines: {node: ^10 || ^12 || >=14}
- dependencies:
- nanoid: 3.3.7
- picocolors: 1.0.1
- source-map-js: 1.2.0
- dev: true
-
/postcss@8.4.40:
resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==}
engines: {node: ^10 || ^12 || >=14}
@@ -30418,6 +30832,54 @@ packages:
picocolors: 1.0.1
source-map-js: 1.2.0
+ /postgres-array@2.0.0:
+ resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /postgres-array@3.0.2:
+ resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /postgres-bytea@1.0.0:
+ resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /postgres-bytea@3.0.0:
+ resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
+ engines: {node: '>= 6'}
+ dependencies:
+ obuf: 1.1.2
+ dev: true
+
+ /postgres-date@1.0.7:
+ resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /postgres-date@2.1.0:
+ resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /postgres-interval@1.2.0:
+ resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ xtend: 4.0.2
+ dev: false
+
+ /postgres-interval@3.0.0:
+ resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /postgres-range@1.1.4:
+ resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
+ dev: true
+
/preferred-pm@3.0.3:
resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==}
engines: {node: '>=10'}
@@ -32452,6 +32914,15 @@ packages:
react: 18.2.0
scheduler: 0.23.0
+ /react-dom@18.3.0-canary-8039e6d0b-20231026(react@18.3.0-canary-8039e6d0b-20231026):
+ resolution: {integrity: sha512-H1JoRPsibBazpZeqQ9mWw58ChfxE1EZo5eOiRh+Ztz2rpfLBOMZCa8IjtnLrUOAjWcO+KkBisgibjeCQb6WpvQ==}
+ peerDependencies:
+ react: 18.3.0-canary-8039e6d0b-20231026
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.0-canary-8039e6d0b-20231026
+ scheduler: 0.24.0-canary-8039e6d0b-20231026
+
/react-dom@18.3.1(react@18.2.0):
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
@@ -32484,6 +32955,16 @@ packages:
react-is: 18.1.0
dev: false
+ /react-error-boundary@3.1.4(react@18.3.0-canary-8039e6d0b-20231026):
+ resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==}
+ engines: {node: '>=10', npm: '>=6'}
+ peerDependencies:
+ react: '>=16.13.1'
+ dependencies:
+ '@babel/runtime': 7.24.7
+ react: 18.3.0-canary-8039e6d0b-20231026
+ dev: false
+
/react-error-boundary@4.0.12(react@18.2.0):
resolution: {integrity: sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==}
peerDependencies:
@@ -32530,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):
@@ -32728,12 +33209,28 @@ packages:
react: 18.3.1
dev: true
- /react-side-effect@2.1.2(react@18.2.0):
+ /react-server-dom-webpack@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):
+ resolution: {integrity: sha512-JhmrWiHewwWIUfhVmc38G8QZDO8mFJSOBIbUwIfqKPH/7/u23VqUjejkuXKTGyVUpPKJlC4PIVjmnk7Wqoguzg==}
+ engines: {node: '>=0.10.0'}
+ peerDependencies:
+ react: 18.3.0-canary-8039e6d0b-20231026
+ react-dom: 18.3.0-canary-8039e6d0b-20231026
+ webpack: ^5.59.0
+ dependencies:
+ acorn-loose: 8.4.0
+ loose-envify: 1.4.0
+ neo-async: 2.6.2
+ react: 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(esbuild@0.17.19)
+ dev: false
+
+ /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):
@@ -32876,6 +33373,12 @@ packages:
dependencies:
loose-envify: 1.4.0
+ /react@18.3.0-canary-8039e6d0b-20231026:
+ resolution: {integrity: sha512-vlVdzo0PJiwshpfpMSxB4iirygntWJ8AD3nMetQBp6T8YHLv7WMRqMOfna9wn5GMXRJcUw9paQklNiOVGP68ag==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ loose-envify: 1.4.0
+
/react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -33542,6 +34045,17 @@ packages:
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ /sanitize-html@2.10.0:
+ resolution: {integrity: sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==}
+ dependencies:
+ deepmerge: 4.3.1
+ escape-string-regexp: 4.0.0
+ htmlparser2: 8.0.1
+ is-plain-object: 5.0.0
+ parse-srcset: 1.0.2
+ postcss: 8.4.41
+ dev: false
+
/sass-embedded-android-arm64@1.77.8:
resolution: {integrity: sha512-EmWHLbEx0Zo/f/lTFzMeH2Du+/I4RmSRlEnERSUKQWVp3aBSO04QDvdxfFezgQ+2Yt/ub9WMqBpma9P/8MPsLg==}
engines: {node: '>=14.0.0'}
@@ -33755,6 +34269,11 @@ packages:
dependencies:
loose-envify: 1.4.0
+ /scheduler@0.24.0-canary-8039e6d0b-20231026:
+ resolution: {integrity: sha512-dWUzSzKcmDy+DQz/aNgYmaXMduHlXed7pTfL4UUkZwwHElNUVsJhZfItWIj2S+7t5o+8GrFA12se0CuJkrJVsA==}
+ dependencies:
+ loose-envify: 1.4.0
+
/schema-utils@3.3.0:
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
engines: {node: '>= 10.13.0'}
@@ -33915,6 +34434,10 @@ packages:
transitivePeerDependencies:
- supports-color
+ /server-only@0.0.1:
+ resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
+ dev: false
+
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: false
@@ -34249,6 +34772,11 @@ packages:
readable-stream: 3.6.0
dev: true
+ /split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+ dev: false
+
/sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
@@ -34556,7 +35084,7 @@ packages:
/strip-literal@1.0.1:
resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==}
dependencies:
- acorn: 8.11.2
+ acorn: 8.12.1
dev: true
/strong-log-transformer@2.1.0:
@@ -34605,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}
@@ -36294,7 +36846,7 @@ packages:
dependencies:
'@types/node': 18.11.17
esbuild: 0.17.19
- postcss: 8.4.38
+ postcss: 8.4.41
rollup: 3.29.4
optionalDependencies:
fsevents: 2.3.3
diff --git a/tests/integration/basic-next-app/.browserslistrc b/tests/integration/basic-next-app/.browserslistrc
new file mode 100644
index 000000000000..f5ceef6bb8ec
--- /dev/null
+++ b/tests/integration/basic-next-app/.browserslistrc
@@ -0,0 +1,5 @@
+chrome >= 51
+edge >= 15
+firefox >= 54
+safari >= 10
+ios_saf >= 10
diff --git a/tests/integration/basic-next-app/credentials.js b/tests/integration/basic-next-app/credentials.js
new file mode 100644
index 000000000000..edc6d3d66e71
--- /dev/null
+++ b/tests/integration/basic-next-app/credentials.js
@@ -0,0 +1,7 @@
+module.exports = {
+ host: process.env.DB_HOST || 'localhost',
+ database: 'notesapi',
+ user: 'notesadmin',
+ password: 'password',
+ port: '5432',
+};
diff --git a/tests/integration/basic-next-app/modern.config.ts b/tests/integration/basic-next-app/modern.config.ts
new file mode 100644
index 000000000000..bc7f7c25ec0a
--- /dev/null
+++ b/tests/integration/basic-next-app/modern.config.ts
@@ -0,0 +1,47 @@
+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: {
+ // 避免 Modern.js 项目代码中用的是其他版本
+ alias: {
+ // react$: require.resolve('react'),
+ // 'react-dom/client': require.resolve('react-dom/client'),
+ // 'react-dom': require.resolve('react-dom'),
+ },
+ },
+ 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-next-app/package.json b/tests/integration/basic-next-app/package.json
new file mode 100644
index 000000000000..3afa065ff691
--- /dev/null
+++ b/tests/integration/basic-next-app/package.json
@@ -0,0 +1,43 @@
+{
+ "private": true,
+ "name": "tmp",
+ "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",
+ "marked": "4.2.12",
+ "sanitize-html": "2.10.0",
+ "react-error-boundary": "3.1.4",
+ "pg": "8.10.0",
+ "server-only": "^0.0.1",
+ "excerpts": "0.0.3",
+ "react-server-dom-webpack": "18.3.0-canary-8039e6d0b-20231026"
+ },
+ "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-next-app/server/modern.server.ts b/tests/integration/basic-next-app/server/modern.server.ts
new file mode 100644
index 000000000000..840504d83ece
--- /dev/null
+++ b/tests/integration/basic-next-app/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-next-app/server/server.d.ts b/tests/integration/basic-next-app/server/server.d.ts
new file mode 100644
index 000000000000..712c8256e0cd
--- /dev/null
+++ b/tests/integration/basic-next-app/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-next-app/server/serverPlugin.ts b/tests/integration/basic-next-app/server/serverPlugin.ts
new file mode 100644
index 000000000000..8cc85a36a71c
--- /dev/null
+++ b/tests/integration/basic-next-app/server/serverPlugin.ts
@@ -0,0 +1,93 @@
+import type { Context, ServerPlugin } from '@modern-js/server-core';
+import path from 'path';
+import { readFileSync } from 'fs';
+// import {
+// renderToPipeableStream,
+// renderToReadableStream,
+// } from 'react-server-dom-webpack/server';
+import { renderToReadableStream } from 'react-server-dom-webpack/server.edge';
+import React from 'react';
+import { Pool } from 'pg';
+// import ReactApp from '../src/App';
+
+console.log('react1111111111', React.version);
+console.log('renderToReadableStream', renderToReadableStream);
+
+const pool = new Pool(require('../credentials'));
+
+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-next-app/src/App.js b/tests/integration/basic-next-app/src/App.js
new file mode 100644
index 000000000000..e825616dc186
--- /dev/null
+++ b/tests/integration/basic-next-app/src/App.js
@@ -0,0 +1,50 @@
+/**
+ * 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 Note from './Note';
+import NoteList from './NoteList';
+import EditButton from './EditButton';
+import SearchField from './SearchField';
+import NoteSkeleton from './NoteSkeleton';
+import NoteListSkeleton from './NoteListSkeleton';
+
+export default function App({ selectedId, isEditing, searchText }) {
+ return (
+
+
+
+
+ React Notes
+
+
+
+ }>
+
+
+
+
+
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/EditButton.js b/tests/integration/basic-next-app/src/EditButton.js
new file mode 100644
index 000000000000..f0265fc45afa
--- /dev/null
+++ b/tests/integration/basic-next-app/src/EditButton.js
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ *
+ */
+
+'use client';
+
+import {useTransition} from 'react';
+import {useRouter} from './framework/router';
+
+export default function EditButton({noteId, children}) {
+ const [isPending, startTransition] = useTransition();
+ const {navigate} = useRouter();
+ const isDraft = noteId == null;
+ return (
+ {
+ startTransition(() => {
+ navigate({
+ selectedId: noteId,
+ isEditing: true,
+ });
+ });
+ }}
+ role="menuitem">
+ {children}
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/Note.js b/tests/integration/basic-next-app/src/Note.js
new file mode 100644
index 000000000000..990cf52c0f12
--- /dev/null
+++ b/tests/integration/basic-next-app/src/Note.js
@@ -0,0 +1,66 @@
+/**
+ * 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 {format} from 'date-fns';
+
+// Uncomment if you want to read from a file instead.
+// import {readFile} from 'fs/promises';
+// import {resolve} from 'path';
+
+import NotePreview from './NotePreview';
+import EditButton from './EditButton';
+import NoteEditor from './NoteEditor';
+
+export default async function Note({selectedId, isEditing}) {
+ if (selectedId === null) {
+ if (isEditing) {
+ return (
+
+ );
+ } else {
+ return (
+
+
+ Click a note on the left to view something! 🥺
+
+
+ );
+ }
+ }
+
+ const noteResponse = await fetch(`http://localhost:4000/notes/${selectedId}`);
+ const note = await noteResponse.json();
+
+ let {id, title, body, updated_at} = note;
+ const updatedAt = new Date(updated_at);
+
+ // We could also read from a file instead.
+ // body = await readFile(resolve(`./notes/${note.id}.md`), 'utf8');
+
+ // Now let's see how the Suspense boundary above lets us not block on this.
+ // await fetch('http://localhost:4000/sleep/3000');
+
+ if (isEditing) {
+ return ;
+ } else {
+ return (
+
+
+
{title}
+
+
+ Last updated on {format(updatedAt, "d MMM yyyy 'at' h:mm bb")}
+
+ Edit
+
+
+
+
+ );
+ }
+}
diff --git a/tests/integration/basic-next-app/src/NoteEditor.js b/tests/integration/basic-next-app/src/NoteEditor.js
new file mode 100644
index 000000000000..0b3b7bd8d914
--- /dev/null
+++ b/tests/integration/basic-next-app/src/NoteEditor.js
@@ -0,0 +1,120 @@
+/**
+ * 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.
+ *
+ */
+
+'use client';
+
+import {useState, useTransition} from 'react';
+import {useRouter, useMutation} from './framework/router';
+
+import NotePreview from './NotePreview';
+
+export default function NoteEditor({noteId, initialTitle, initialBody}) {
+ const [title, setTitle] = useState(initialTitle);
+ const [body, setBody] = useState(initialBody);
+ const {location} = useRouter();
+ const [isNavigating, startNavigating] = useTransition();
+ const [isSaving, saveNote] = useMutation({
+ endpoint: noteId !== null ? `/notes/${noteId}` : `/notes`,
+ method: noteId !== null ? 'PUT' : 'POST',
+ });
+ const [isDeleting, deleteNote] = useMutation({
+ endpoint: `/notes/${noteId}`,
+ method: 'DELETE',
+ });
+
+ async function handleSave() {
+ const payload = {title, body};
+ const requestedLocation = {
+ selectedId: noteId,
+ isEditing: false,
+ searchText: location.searchText,
+ };
+ await saveNote(payload, requestedLocation);
+ }
+
+ async function handleDelete() {
+ const payload = {};
+ const requestedLocation = {
+ selectedId: null,
+ isEditing: false,
+ searchText: location.searchText,
+ };
+ await deleteNote(payload, requestedLocation);
+ }
+
+ const isDraft = noteId === null;
+ return (
+
+
+
+
+
handleSave()}
+ role="menuitem">
+
+ Done
+
+ {!isDraft && (
+
handleDelete()}
+ role="menuitem">
+
+ Delete
+
+ )}
+
+
+ Preview
+
+
{title}
+
+
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/NoteList.js b/tests/integration/basic-next-app/src/NoteList.js
new file mode 100644
index 000000000000..41b97cbf43c4
--- /dev/null
+++ b/tests/integration/basic-next-app/src/NoteList.js
@@ -0,0 +1,43 @@
+/**
+ * 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 {db} from './db';
+import SidebarNote from './SidebarNote';
+
+export default async function NoteList({searchText}) {
+ // const notes = await (await fetch('http://localhost:4000/notes')).json();
+
+ // WARNING: This is for demo purposes only.
+ // We don't encourage this in real apps. There are far safer ways to access
+ // data in a real application!
+ const notes = (
+ await db.query(
+ `select * from notes where title ilike $1 order by id desc`,
+ ['%' + searchText + '%']
+ )
+ ).rows;
+
+ // Now let's see how the Suspense boundary above lets us not block on this.
+ // await fetch('http://localhost:4000/sleep/3000');
+
+ return notes.length > 0 ? (
+
+ {notes.map((note) => (
+
+
+
+ ))}
+
+ ) : (
+
+ {searchText
+ ? `Couldn't find any notes titled "${searchText}".`
+ : 'No notes created yet!'}{' '}
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/NoteListSkeleton.js b/tests/integration/basic-next-app/src/NoteListSkeleton.js
new file mode 100644
index 000000000000..b5845b1ea48e
--- /dev/null
+++ b/tests/integration/basic-next-app/src/NoteListSkeleton.js
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ *
+ */
+
+export default function NoteListSkeleton() {
+ return (
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/NotePreview.js b/tests/integration/basic-next-app/src/NotePreview.js
new file mode 100644
index 000000000000..585ef81dcf63
--- /dev/null
+++ b/tests/integration/basic-next-app/src/NotePreview.js
@@ -0,0 +1,17 @@
+/**
+ * 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 TextWithMarkdown from './TextWithMarkdown';
+
+export default function NotePreview({body}) {
+ return (
+
+
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/NoteSkeleton.js b/tests/integration/basic-next-app/src/NoteSkeleton.js
new file mode 100644
index 000000000000..44dd7cbb5f0a
--- /dev/null
+++ b/tests/integration/basic-next-app/src/NoteSkeleton.js
@@ -0,0 +1,75 @@
+/**
+ * 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.
+ *
+ */
+
+export default function NoteSkeleton({isEditing}) {
+ return isEditing ? : ;
+}
+
+function NoteEditorSkeleton() {
+ return (
+
+ );
+}
+
+function NotePreviewSkeleton() {
+ return (
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/SearchField.js b/tests/integration/basic-next-app/src/SearchField.js
new file mode 100644
index 000000000000..f35700d72962
--- /dev/null
+++ b/tests/integration/basic-next-app/src/SearchField.js
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ *
+ */
+
+'use client';
+
+import {useState, useTransition} from 'react';
+import {useRouter} from './framework/router';
+
+import Spinner from './Spinner';
+
+export default function SearchField() {
+ const [text, setText] = useState('');
+ const [isSearching, startSearching] = useTransition();
+ const {navigate} = useRouter();
+ return (
+ e.preventDefault()}>
+
+ Search for a note by title
+
+ {
+ const newText = e.target.value;
+ setText(newText);
+ startSearching(() => {
+ navigate({
+ searchText: newText,
+ });
+ });
+ }}
+ />
+
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/SidebarNote.js b/tests/integration/basic-next-app/src/SidebarNote.js
new file mode 100644
index 000000000000..4560c5a715a3
--- /dev/null
+++ b/tests/integration/basic-next-app/src/SidebarNote.js
@@ -0,0 +1,34 @@
+/**
+ * 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 {format, isToday} from 'date-fns';
+import excerpts from 'excerpts';
+import {marked} from 'marked';
+
+import SidebarNoteContent from './SidebarNoteContent';
+
+export default function SidebarNote({note}) {
+ const updatedAt = new Date(note.updated_at);
+ const lastUpdatedAt = isToday(updatedAt)
+ ? format(updatedAt, 'h:mm bb')
+ : format(updatedAt, 'M/d/yy');
+ const summary = excerpts(marked(note.body), {words: 20});
+ return (
+ {summary || (No content) }
+ }>
+
+ {note.title}
+ {lastUpdatedAt}
+
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/SidebarNoteContent.js b/tests/integration/basic-next-app/src/SidebarNoteContent.js
new file mode 100644
index 000000000000..5f761c33e47d
--- /dev/null
+++ b/tests/integration/basic-next-app/src/SidebarNoteContent.js
@@ -0,0 +1,88 @@
+/**
+ * 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.
+ *
+ */
+
+'use client';
+
+import {useState, useRef, useEffect, useTransition} from 'react';
+import {useRouter} from './framework/router';
+
+export default function SidebarNoteContent({
+ id,
+ title,
+ children,
+ expandedChildren,
+}) {
+ const {location, navigate} = useRouter();
+ const [isPending, startTransition] = useTransition();
+ const [isExpanded, setIsExpanded] = useState(false);
+ const isActive = id === location.selectedId;
+
+ // Animate after title is edited.
+ const itemRef = useRef(null);
+ const prevTitleRef = useRef(title);
+ useEffect(() => {
+ if (title !== prevTitleRef.current) {
+ prevTitleRef.current = title;
+ itemRef.current.classList.add('flash');
+ }
+ }, [title]);
+
+ return (
+ {
+ itemRef.current.classList.remove('flash');
+ }}
+ className={[
+ 'sidebar-note-list-item',
+ isExpanded ? 'note-expanded' : '',
+ ].join(' ')}>
+ {children}
+
{
+ startTransition(() => {
+ navigate({
+ selectedId: id,
+ isEditing: false,
+ });
+ });
+ }}>
+ Open note for preview
+
+
{
+ e.stopPropagation();
+ setIsExpanded(!isExpanded);
+ }}>
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+ {isExpanded && expandedChildren}
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/Spinner.js b/tests/integration/basic-next-app/src/Spinner.js
new file mode 100644
index 000000000000..b91af634cbe2
--- /dev/null
+++ b/tests/integration/basic-next-app/src/Spinner.js
@@ -0,0 +1,17 @@
+/**
+ * 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.
+ *
+ */
+
+export default function Spinner({active = true}) {
+ return (
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/TextWithMarkdown.js b/tests/integration/basic-next-app/src/TextWithMarkdown.js
new file mode 100644
index 000000000000..222eec5787e3
--- /dev/null
+++ b/tests/integration/basic-next-app/src/TextWithMarkdown.js
@@ -0,0 +1,38 @@
+/**
+ * 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 {marked} from 'marked';
+import sanitizeHtml from 'sanitize-html';
+
+const allowedTags = sanitizeHtml.defaults.allowedTags.concat([
+ 'img',
+ 'h1',
+ 'h2',
+ 'h3',
+]);
+const allowedAttributes = Object.assign(
+ {},
+ sanitizeHtml.defaults.allowedAttributes,
+ {
+ img: ['alt', 'src'],
+ }
+);
+
+export default function TextWithMarkdown({text}) {
+ return (
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/db.js b/tests/integration/basic-next-app/src/db.js
new file mode 100644
index 000000000000..ae0eeb44e1c0
--- /dev/null
+++ b/tests/integration/basic-next-app/src/db.js
@@ -0,0 +1,16 @@
+/**
+ * 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.
+ *
+ */
+
+// Error early if this is accidentally imported on the client.
+import 'server-only';
+
+import { Pool } from 'pg';
+import credentials from '../credentials';
+
+// Don't keep credentials in the source tree in a real app!
+export const db = new Pool(credentials);
diff --git a/tests/integration/basic-next-app/src/framework/bootstrap.js b/tests/integration/basic-next-app/src/framework/bootstrap.js
new file mode 100644
index 000000000000..d568be8accd3
--- /dev/null
+++ b/tests/integration/basic-next-app/src/framework/bootstrap.js
@@ -0,0 +1,35 @@
+/**
+ * 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.
+ *
+ */
+
+// ---------------------------------------------------------
+// Note: this code would usually be provided by a framework.
+// ---------------------------------------------------------
+
+import {createRoot} from 'react-dom/client';
+import {ErrorBoundary} from 'react-error-boundary';
+import {Router} from './router'
+
+const root = createRoot(document.getElementById('root'));
+root.render( );
+
+function Root() {
+ return (
+
+
+
+ );
+}
+
+function Error({error}) {
+ return (
+
+
Application Error
+
{error.stack}
+
+ );
+}
diff --git a/tests/integration/basic-next-app/src/framework/router.js b/tests/integration/basic-next-app/src/framework/router.js
new file mode 100644
index 000000000000..7f4086447d83
--- /dev/null
+++ b/tests/integration/basic-next-app/src/framework/router.js
@@ -0,0 +1,120 @@
+/**
+ * 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.
+ *
+ */
+
+'use client';
+
+// ---------------------------------------------------------
+// Note: this code would usually be provided by a framework.
+// ---------------------------------------------------------
+
+import React from 'react';
+import {
+ createFromFetch,
+ createFromReadableStream,
+} from 'react-server-dom-webpack/client';
+import '../style.css';
+
+const { createContext, startTransition, useContext, useState, use } =
+ React.default || React;
+
+console.log('createContext11111111', React.default, React);
+
+const RouterContext = createContext();
+const initialCache = new Map();
+
+// 向服务端发送 location,拿到响应,然后渲染
+export function Router() {
+ const [cache, setCache] = useState(initialCache);
+ const [location, setLocation] = useState({
+ selectedId: null,
+ isEditing: false,
+ searchText: '',
+ });
+
+ const locationKey = JSON.stringify(location);
+ let content = cache.get(locationKey);
+ if (!content) {
+ content = createFromFetch(
+ fetch('/react?location=' + encodeURIComponent(locationKey)),
+ );
+ cache.set(locationKey, content);
+ }
+
+ function refresh(response) {
+ startTransition(() => {
+ const nextCache = new Map();
+ if (response != null) {
+ const locationKey = response.headers.get('X-Location');
+ const nextLocation = JSON.parse(locationKey);
+ const nextContent = createFromReadableStream(response.body);
+ nextCache.set(locationKey, nextContent);
+ navigate(nextLocation);
+ }
+ setCache(nextCache);
+ });
+ }
+
+ function navigate(nextLocation) {
+ startTransition(() => {
+ setLocation(loc => ({
+ ...loc,
+ ...nextLocation,
+ }));
+ });
+ }
+
+ return (
+
+ {use(content)}
+
+ );
+}
+
+export function useRouter() {
+ return useContext(RouterContext);
+}
+
+export function useMutation({ endpoint, method }) {
+ const { refresh } = useRouter();
+ const [isSaving, setIsSaving] = useState(false);
+ const [didError, setDidError] = useState(false);
+ const [error, setError] = useState(null);
+ if (didError) {
+ // Let the nearest error boundary handle errors while saving.
+ throw error;
+ }
+
+ async function performMutation(payload, requestedLocation) {
+ setIsSaving(true);
+ try {
+ const response = await fetch(
+ `${endpoint}?location=${encodeURIComponent(
+ JSON.stringify(requestedLocation),
+ )}`,
+ {
+ method,
+ body: JSON.stringify(payload),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+ if (!response.ok) {
+ throw new Error(await response.text());
+ }
+ refresh(response);
+ } catch (e) {
+ setDidError(true);
+ setError(e);
+ } finally {
+ setIsSaving(false);
+ }
+ }
+
+ return [isSaving, performMutation];
+}
diff --git a/tests/integration/basic-next-app/src/index.jsx b/tests/integration/basic-next-app/src/index.jsx
new file mode 100644
index 000000000000..682ac0d00bc8
--- /dev/null
+++ b/tests/integration/basic-next-app/src/index.jsx
@@ -0,0 +1 @@
+import './framework/bootstrap';
diff --git a/tests/integration/basic-next-app/src/style.css b/tests/integration/basic-next-app/src/style.css
new file mode 100644
index 000000000000..3b987093c5e3
--- /dev/null
+++ b/tests/integration/basic-next-app/src/style.css
@@ -0,0 +1,692 @@
+/* -------------------------------- CSSRESET --------------------------------*/
+/* CSS Reset adapted from https://dev.to/hankchizljaw/a-modern-css-reset-6p3 */
+/* Box sizing rules */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+/* Remove default padding */
+ul[class],
+ol[class] {
+ padding: 0;
+}
+
+/* Remove default margin */
+body,
+h1,
+h2,
+h3,
+h4,
+p,
+ul[class],
+ol[class],
+li,
+figure,
+figcaption,
+blockquote,
+dl,
+dd {
+ margin: 0;
+}
+
+/* Set core body defaults */
+body {
+ min-height: 100vh;
+ scroll-behavior: smooth;
+ text-rendering: optimizeSpeed;
+ line-height: 1.5;
+}
+
+/* Remove list styles on ul, ol elements with a class attribute */
+ul[class],
+ol[class] {
+ list-style: none;
+}
+
+/* A elements that don't have a class get default styles */
+a:not([class]) {
+ text-decoration-skip-ink: auto;
+}
+
+/* Make images easier to work with */
+img {
+ max-width: 100%;
+ display: block;
+}
+
+/* Natural flow and rhythm in articles by default */
+article > * + * {
+ margin-block-start: 1em;
+}
+
+/* Inherit fonts for inputs and buttons */
+input,
+button,
+textarea,
+select {
+ font: inherit;
+}
+
+/* Remove all animations and transitions for people that prefer not to see them */
+@media (prefers-reduced-motion: reduce) {
+ * {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+/* -------------------------------- /CSSRESET --------------------------------*/
+
+:root {
+ /* Colors */
+ --main-border-color: #ddd;
+ --primary-border: #037dba;
+ --gray-20: #404346;
+ --gray-60: #8a8d91;
+ --gray-70: #bcc0c4;
+ --gray-80: #c9ccd1;
+ --gray-90: #e4e6eb;
+ --gray-95: #f0f2f5;
+ --gray-100: #f5f7fa;
+ --primary-blue: #037dba;
+ --secondary-blue: #0396df;
+ --tertiary-blue: #c6efff;
+ --flash-blue: #4cf7ff;
+ --outline-blue: rgba(4, 164, 244, 0.6);
+ --navy-blue: #035e8c;
+ --red-25: #bd0d2a;
+ --secondary-text: #65676b;
+ --white: #fff;
+ --yellow: #fffae1;
+
+ --outline-box-shadow: 0 0 0 2px var(--outline-blue);
+ --outline-box-shadow-contrast: 0 0 0 2px var(--navy-blue);
+
+ /* Fonts */
+ --sans-serif: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ Ubuntu, Helvetica, sans-serif;
+ --monospace: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console,
+ monospace;
+}
+
+html {
+ font-size: 100%;
+}
+
+body {
+ font-family: var(--sans-serif);
+ background: var(--gray-100);
+ font-weight: 400;
+ line-height: 1.75;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5 {
+ margin: 0;
+ font-weight: 700;
+ line-height: 1.3;
+}
+
+h1 {
+ font-size: 3.052rem;
+}
+h2 {
+ font-size: 2.441rem;
+}
+h3 {
+ font-size: 1.953rem;
+}
+h4 {
+ font-size: 1.563rem;
+}
+h5 {
+ font-size: 1.25rem;
+}
+small,
+.text_small {
+ font-size: 0.8rem;
+}
+pre,
+code {
+ font-family: var(--monospace);
+ border-radius: 6px;
+}
+pre {
+ background: var(--gray-95);
+ padding: 12px;
+ line-height: 1.5;
+}
+code {
+ background: var(--yellow);
+ padding: 0 3px;
+ font-size: 0.94rem;
+ word-break: break-word;
+}
+pre code {
+ background: none;
+}
+a {
+ color: var(--primary-blue);
+}
+
+.text-with-markdown h1,
+.text-with-markdown h2,
+.text-with-markdown h3,
+.text-with-markdown h4,
+.text-with-markdown h5 {
+ margin-block: 2rem 0.7rem;
+ margin-inline: 0;
+}
+
+.text-with-markdown blockquote {
+ font-style: italic;
+ color: var(--gray-20);
+ border-left: 3px solid var(--gray-80);
+ padding-left: 10px;
+}
+
+hr {
+ border: 0;
+ height: 0;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.3);
+}
+
+/* ---------------------------------------------------------------------------*/
+.main {
+ display: flex;
+ height: 100vh;
+ width: 100%;
+ overflow: hidden;
+}
+
+.col {
+ height: 100%;
+}
+.col:last-child {
+ flex-grow: 1;
+}
+
+.logo {
+ height: 20px;
+ width: 22px;
+ margin-inline-end: 10px;
+}
+
+.edit-button {
+ border-radius: 100px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ padding: 6px 20px 8px;
+ cursor: pointer;
+ font-weight: 700;
+ outline-style: none;
+}
+.edit-button--solid {
+ background: var(--primary-blue);
+ color: var(--white);
+ border: none;
+ margin-inline-start: 6px;
+ transition: all 0.2s ease-in-out;
+}
+.edit-button--solid:hover {
+ background: var(--secondary-blue);
+}
+.edit-button--solid:focus {
+ box-shadow: var(--outline-box-shadow-contrast);
+}
+.edit-button--outline {
+ background: var(--white);
+ color: var(--primary-blue);
+ border: 1px solid var(--primary-blue);
+ margin-inline-start: 12px;
+ transition: all 0.1s ease-in-out;
+}
+.edit-button--outline:disabled {
+ opacity: 0.5;
+}
+.edit-button--outline:hover:not([disabled]) {
+ background: var(--primary-blue);
+ color: var(--white);
+}
+.edit-button--outline:focus {
+ box-shadow: var(--outline-box-shadow);
+}
+
+ul.notes-list {
+ padding: 16px 0;
+}
+.notes-list > li {
+ padding: 0 16px;
+}
+.notes-empty {
+ padding: 16px;
+}
+
+.sidebar {
+ background: var(--white);
+ box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1), 0px 2px 2px rgba(0, 0, 0, 0.1);
+ overflow-y: scroll;
+ z-index: 1000;
+ flex-shrink: 0;
+ max-width: 350px;
+ min-width: 250px;
+ width: 30%;
+}
+.sidebar-header {
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ padding: 36px 16px 16px;
+ display: flex;
+ align-items: center;
+}
+.sidebar-menu {
+ padding: 0 16px 16px;
+ display: flex;
+ justify-content: space-between;
+}
+.sidebar-menu > .search {
+ position: relative;
+ flex-grow: 1;
+}
+.sidebar-note-list-item {
+ position: relative;
+ margin-bottom: 12px;
+ padding: 16px;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ max-height: 100px;
+ transition: max-height 250ms ease-out;
+ transform: scale(1);
+}
+.sidebar-note-list-item.note-expanded {
+ max-height: 300px;
+ transition: max-height 0.5s ease;
+}
+.sidebar-note-list-item.flash {
+ animation-name: flash;
+ animation-duration: 0.6s;
+}
+
+.sidebar-note-open {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ z-index: 0;
+ border: none;
+ border-radius: 6px;
+ text-align: start;
+ background: var(--gray-95);
+ cursor: pointer;
+ outline-style: none;
+ color: transparent;
+ font-size: 0px;
+}
+.sidebar-note-open:focus {
+ box-shadow: var(--outline-box-shadow);
+}
+.sidebar-note-open:hover {
+ background: var(--gray-90);
+}
+.sidebar-note-header {
+ z-index: 1;
+ max-width: 85%;
+ pointer-events: none;
+}
+.sidebar-note-header > strong {
+ display: block;
+ font-size: 1.25rem;
+ line-height: 1.2;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.sidebar-note-toggle-expand {
+ z-index: 2;
+ border-radius: 50%;
+ height: 24px;
+ border: 1px solid var(--gray-60);
+ cursor: pointer;
+ flex-shrink: 0;
+ visibility: hidden;
+ opacity: 0;
+ cursor: default;
+ transition: visibility 0s linear 20ms, opacity 300ms;
+ outline-style: none;
+}
+.sidebar-note-toggle-expand:focus {
+ box-shadow: var(--outline-box-shadow);
+}
+.sidebar-note-open:hover + .sidebar-note-toggle-expand,
+.sidebar-note-open:focus + .sidebar-note-toggle-expand,
+.sidebar-note-toggle-expand:hover,
+.sidebar-note-toggle-expand:focus {
+ visibility: visible;
+ opacity: 1;
+ transition: visibility 0s linear 0s, opacity 300ms;
+}
+.sidebar-note-toggle-expand img {
+ width: 10px;
+ height: 10px;
+}
+
+.sidebar-note-excerpt {
+ pointer-events: none;
+ z-index: 2;
+ flex: 1 1 250px;
+ color: var(--secondary-text);
+ position: relative;
+ animation: slideIn 100ms;
+}
+
+.search input {
+ padding: 0 16px;
+ border-radius: 100px;
+ border: 1px solid var(--gray-90);
+ width: 100%;
+ height: 100%;
+ outline-style: none;
+}
+.search input:focus {
+ box-shadow: var(--outline-box-shadow);
+}
+.search .spinner {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+}
+
+.note-viewer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.note {
+ background: var(--white);
+ box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1), 0px 0px 1px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+ height: 95%;
+ width: 95%;
+ min-width: 400px;
+ padding: 8%;
+ overflow-y: auto;
+}
+.note--empty-state {
+ margin-inline: 20px 20px;
+}
+.note-text--empty-state {
+ font-size: 1.5rem;
+}
+.note-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap-reverse;
+ margin-inline-start: -12px;
+}
+.note-menu {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-grow: 1;
+}
+.note-title {
+ line-height: 1.3;
+ flex-grow: 1;
+ overflow-wrap: break-word;
+ margin-inline-start: 12px;
+}
+.note-updated-at {
+ color: var(--secondary-text);
+ white-space: nowrap;
+ margin-inline-start: 12px;
+}
+.note-preview {
+ margin-block-start: 50px;
+}
+
+.note-editor {
+ background: var(--white);
+ display: flex;
+ height: 100%;
+ width: 100%;
+ padding: 58px;
+ overflow-y: auto;
+}
+.note-editor .label {
+ margin-bottom: 20px;
+}
+.note-editor-form {
+ display: flex;
+ flex-direction: column;
+ width: 400px;
+ flex-shrink: 0;
+ position: sticky;
+ top: 0;
+}
+.note-editor-form input,
+.note-editor-form textarea {
+ background: none;
+ border: 1px solid var(--gray-70);
+ border-radius: 2px;
+ font-family: var(--monospace);
+ font-size: 0.8rem;
+ padding: 12px;
+ outline-style: none;
+}
+.note-editor-form input:focus,
+.note-editor-form textarea:focus {
+ box-shadow: var(--outline-box-shadow);
+}
+.note-editor-form input {
+ height: 44px;
+ margin-bottom: 16px;
+}
+.note-editor-form textarea {
+ height: 100%;
+ max-width: 400px;
+}
+.note-editor-menu {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ margin-bottom: 12px;
+}
+.note-editor-preview {
+ margin-inline-start: 40px;
+ width: 100%;
+}
+.note-editor-done,
+.note-editor-delete {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-radius: 100px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ padding: 6px 20px 8px;
+ cursor: pointer;
+ font-weight: 700;
+ margin-inline-start: 12px;
+ outline-style: none;
+ transition: all 0.2s ease-in-out;
+}
+.note-editor-done:disabled,
+.note-editor-delete:disabled {
+ opacity: 0.5;
+}
+.note-editor-done {
+ border: none;
+ background: var(--primary-blue);
+ color: var(--white);
+}
+.note-editor-done:focus {
+ box-shadow: var(--outline-box-shadow-contrast);
+}
+.note-editor-done:hover:not([disabled]) {
+ background: var(--secondary-blue);
+}
+.note-editor-delete {
+ border: 1px solid var(--red-25);
+ background: var(--white);
+ color: var(--red-25);
+}
+.note-editor-delete:focus {
+ box-shadow: var(--outline-box-shadow);
+}
+.note-editor-delete:hover:not([disabled]) {
+ background: var(--red-25);
+ color: var(--white);
+}
+/* Hack to color our svg */
+.note-editor-delete:hover:not([disabled]) img {
+ filter: grayscale(1) invert(1) brightness(2);
+}
+.note-editor-done > img {
+ width: 14px;
+}
+.note-editor-delete > img {
+ width: 10px;
+}
+.note-editor-done > img,
+.note-editor-delete > img {
+ margin-inline-end: 12px;
+}
+.note-editor-done[disabled],
+.note-editor-delete[disabled] {
+ opacity: 0.5;
+}
+
+.label {
+ display: inline-block;
+ border-radius: 100px;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ font-weight: 700;
+ padding: 4px 14px;
+}
+.label--preview {
+ background: rgba(38, 183, 255, 0.15);
+ color: var(--primary-blue);
+}
+
+.text-with-markdown p {
+ margin-bottom: 16px;
+}
+.text-with-markdown img {
+ width: 100%;
+}
+
+/* https://codepen.io/mandelid/pen/vwKoe */
+.spinner {
+ display: inline-block;
+ transition: opacity linear 0.1s 0.2s;
+ width: 20px;
+ height: 20px;
+ border: 3px solid rgba(80, 80, 80, 0.5);
+ border-radius: 50%;
+ border-top-color: #fff;
+ animation: spin 1s ease-in-out infinite;
+ opacity: 0;
+}
+.spinner--active {
+ opacity: 1;
+}
+
+.skeleton::after {
+ content: 'Loading...';
+}
+.skeleton {
+ height: 100%;
+ background-color: #eee;
+ background-image: linear-gradient(90deg, #eee, #f5f5f5, #eee);
+ background-size: 200px 100%;
+ background-repeat: no-repeat;
+ border-radius: 4px;
+ display: block;
+ line-height: 1;
+ width: 100%;
+ animation: shimmer 1.2s ease-in-out infinite;
+ color: transparent;
+}
+.skeleton:first-of-type {
+ margin: 0;
+}
+.skeleton--button {
+ border-radius: 100px;
+ padding: 6px 20px 8px;
+ width: auto;
+}
+.v-stack + .v-stack {
+ margin-block-start: 0.8em;
+}
+
+.offscreen {
+ border: 0;
+ clip: rect(0, 0, 0, 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ width: 1px;
+ position: absolute;
+}
+
+/* ---------------------------------------------------------------------------*/
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes shimmer {
+ 0% {
+ background-position: -200px 0;
+ }
+ 100% {
+ background-position: calc(200px + 100%) 0;
+ }
+}
+
+@keyframes slideIn {
+ 0% {
+ top: -10px;
+ opacity: 0;
+ }
+ 100% {
+ top: 0;
+ opacity: 1;
+ }
+}
+
+@keyframes flash {
+ 0% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ 50% {
+ transform: scale(1.05);
+ opacity: 0.9;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
diff --git a/tests/integration/basic-next-app/tests/index.test.ts b/tests/integration/basic-next-app/tests/index.test.ts
new file mode 100644
index 000000000000..da3ddccade38
--- /dev/null
+++ b/tests/integration/basic-next-app/tests/index.test.ts
@@ -0,0 +1,103 @@
+import fs from 'fs';
+import path from 'path';
+import puppeteer from 'puppeteer';
+import {
+ launchApp,
+ killApp,
+ getPort,
+ modernBuild,
+ modernServe,
+ launchOptions,
+} from '../../../utils/modernTestUtils';
+
+import { SequenceWait } from '../../../utils/testInSequence';
+
+const appDir = path.resolve(__dirname, '../');
+
+function existsSync(filePath: string) {
+ return fs.existsSync(path.join(appDir, 'dist', filePath));
+}
+
+const curSequence = new SequenceWait();
+curSequence.add('dev');
+
+describe('test dev', () => {
+ afterAll(async () => {
+ await curSequence.done('dev');
+ });
+ test(`should render page correctly`, async () => {
+ const appPort = await getPort();
+ const app = await launchApp(appDir, appPort, {}, {});
+ const errors = [];
+ const browser = await puppeteer.launch(launchOptions as any);
+ const page = await browser.newPage();
+ page.on('pageerror', error => {
+ errors.push(error.message);
+ });
+ await page.goto(`http://localhost:${appPort}`, {
+ waitUntil: ['networkidle0'],
+ });
+
+ const root = await page.$('.description');
+ const targetText = await page.evaluate(el => el?.textContent, root);
+ expect(targetText?.trim()).toEqual('Get started by editing src/App.tsx');
+ expect(errors.length).toEqual(0);
+
+ await killApp(app);
+ await page.close();
+ await browser.close();
+ });
+});
+
+describe('test build', () => {
+ let port = 8080;
+ let buildRes: { code: number };
+ let app: any;
+ beforeAll(async () => {
+ await curSequence.waitUntil('dev');
+ port = await getPort();
+
+ process.env.DEBUG = 'rsbuild';
+ buildRes = await modernBuild(appDir);
+
+ app = await modernServe(appDir, port, {
+ cwd: appDir,
+ });
+ });
+
+ afterAll(async () => {
+ await killApp(app);
+ delete process.env.DEBUG;
+ });
+
+ test(`should get right alias build!`, async () => {
+ expect(buildRes.code === 0).toBe(true);
+ expect(existsSync('route.json')).toBe(true);
+ expect(existsSync('html/main/index.html')).toBe(true);
+ });
+
+ test(`should not use babel-loader`, async () => {
+ const configPath = path.join(
+ appDir,
+ 'dist',
+ '.rsbuild/rspack.config.web.mjs',
+ );
+ const configContent = fs.readFileSync(configPath, { encoding: 'utf-8' });
+
+ expect(configContent.includes('babel-loader')).toBeFalsy();
+ });
+
+ test('should support enableInlineScripts', async () => {
+ const host = `http://localhost`;
+ expect(buildRes.code === 0).toBe(true);
+ const browser = await puppeteer.launch(launchOptions as any);
+ const page = await browser.newPage();
+ await page.goto(`${host}:${port}`);
+
+ const description = await page.$('.description');
+ const targetText = await page.evaluate(el => el?.textContent, description);
+ expect(targetText?.trim()).toEqual('Get started by editing src/App.tsx');
+ await page.close();
+ await browser.close();
+ });
+});
diff --git a/tests/integration/basic-next-app/tests/tsconfig.json b/tests/integration/basic-next-app/tests/tsconfig.json
new file mode 100644
index 000000000000..10f49432232c
--- /dev/null
+++ b/tests/integration/basic-next-app/tests/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "@modern-js/tsconfig/base",
+ "compilerOptions": {
+ "declaration": true,
+ "jsx": "preserve",
+ "baseUrl": "./",
+ "emitDeclarationOnly": true,
+ "isolatedModules": true,
+ "paths": {},
+ "types": ["node", "jest"]
+ }
+}
diff --git a/tests/integration/basic-next-app/tsconfig.json b/tests/integration/basic-next-app/tsconfig.json
new file mode 100644
index 000000000000..8f335281e09f
--- /dev/null
+++ b/tests/integration/basic-next-app/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "@modern-js/tsconfig/base",
+ "compilerOptions": {
+ "declaration": false,
+ "jsx": "react-jsx",
+ "baseUrl": "./",
+ "module": "CommonJS",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@shared/*": ["./shared/*"]
+ }
+ },
+ "include": ["src", "server", "shared", "config", "credentials.js"]
+}
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/README.md b/tests/integration/basic-rsc-ssr/README.md
new file mode 100644
index 000000000000..497cb9568523
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/README.md
@@ -0,0 +1,7 @@
+Issues
+
+1. Client Root should be in `@modern-js/runtime`. Client should build client root as entry, Server(SSR) should build server root(App Root) as entry.
+
+2. Streaming SSR can single run without `react-router`
+
+
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...
}>
+
+
+
+
+ );
+}
diff --git a/tests/integration/basic-rsc-ssr/src/components/client/Button.jsx b/tests/integration/basic-rsc-ssr/src/components/client/Button.jsx
new file mode 100644
index 000000000000..352167521f19
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/components/client/Button.jsx
@@ -0,0 +1,7 @@
+'use client';
+
+export default function Button(props) {
+ const { onClick } = props;
+
+ return +1 ;
+}
diff --git a/tests/integration/basic-rsc-ssr/src/components/client/Counter.jsx b/tests/integration/basic-rsc-ssr/src/components/client/Counter.jsx
new file mode 100644
index 000000000000..fb4c82657221
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/components/client/Counter.jsx
@@ -0,0 +1,16 @@
+'use client';
+import { useState } from 'react';
+import Button from './Button';
+
+export default function Counter(props) {
+ const { count: initialCount } = props;
+
+ const [count, setCount] = useState(initialCount);
+
+ return (
+
+ Counter: {count}
+ setCount(count + 1)} />
+
+ );
+}
diff --git a/tests/integration/basic-rsc-ssr/src/components/server/Footer.jsx b/tests/integration/basic-rsc-ssr/src/components/server/Footer.jsx
new file mode 100644
index 000000000000..622ef80907dd
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/components/server/Footer.jsx
@@ -0,0 +1,3 @@
+export default function Footer() {
+ return Footer
;
+}
diff --git a/tests/integration/basic-rsc-ssr/src/components/server/Header.jsx b/tests/integration/basic-rsc-ssr/src/components/server/Header.jsx
new file mode 100644
index 000000000000..cbd4d3c5fb08
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/components/server/Header.jsx
@@ -0,0 +1,3 @@
+export default function Header() {
+ return Header
;
+}
diff --git a/tests/integration/basic-rsc-ssr/src/components/server/Show.jsx b/tests/integration/basic-rsc-ssr/src/components/server/Show.jsx
new file mode 100644
index 000000000000..6081faa2977c
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/components/server/Show.jsx
@@ -0,0 +1,19 @@
+import Counter from '../client/Counter';
+
+function getData() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve(101);
+ }, 1000);
+ });
+}
+
+export default async function Show() {
+ const count = await getData();
+
+ return (
+
+ Show:
+
+ );
+}
diff --git a/tests/integration/basic-rsc-ssr/src/entry.jsx b/tests/integration/basic-rsc-ssr/src/entry.jsx
new file mode 100644
index 000000000000..d6921e3c47cb
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/entry.jsx
@@ -0,0 +1,13 @@
+import Root from './root';
+import { hydrateRoot, createRoot } from 'react-dom/client';
+
+const rootDom = document.getElementById('root');
+
+if (rootDom) {
+ hydrateRoot(rootDom, );
+
+ // const root = createRoot(rootDom);
+ // root.render( );
+} else {
+ throw new Error('Root element not found');
+}
diff --git a/tests/integration/basic-rsc-ssr/src/root/index.js b/tests/integration/basic-rsc-ssr/src/root/index.js
new file mode 100644
index 000000000000..8bf73233f1e9
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/root/index.js
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ *
+ */
+
+// ---------------------------------------------------------
+// Note: this code would usually be provided by a framework.
+// ---------------------------------------------------------
+
+import { ErrorBoundary } from 'react-error-boundary';
+import { Router } from './router';
+
+export default function Root() {
+ return (
+
+
+
+ );
+}
+
+function ErrorComponent({ error }) {
+ return (
+
+
Application Error
+
{error.stack}
+
+ );
+}
diff --git a/tests/integration/basic-rsc-ssr/src/root/router.js b/tests/integration/basic-rsc-ssr/src/root/router.js
new file mode 100644
index 000000000000..43a32ccc3590
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/src/root/router.js
@@ -0,0 +1,117 @@
+/**
+ * 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.
+ *
+ */
+
+'use client';
+
+// ---------------------------------------------------------
+// Note: this code would usually be provided by a framework.
+// ---------------------------------------------------------
+
+import React from 'react';
+import {
+ createFromFetch,
+ createFromReadableStream,
+} from 'react-server-dom-webpack/client';
+
+const { createContext, startTransition, useContext, useState, use } =
+ React.default || React;
+
+const RouterContext = createContext();
+const initialCache = new Map();
+
+// 向服务端发送 location,拿到响应,然后渲染
+export function Router() {
+ const [cache, setCache] = useState(initialCache);
+ const [location, setLocation] = useState({
+ selectedId: null,
+ isEditing: false,
+ searchText: '',
+ });
+
+ const locationKey = JSON.stringify(location);
+ let content = cache.get(locationKey);
+ if (!content) {
+ content = createFromFetch(
+ fetch(`/react?location=${encodeURIComponent(locationKey)}`),
+ );
+ cache.set(locationKey, content);
+ }
+
+ function refresh(response) {
+ startTransition(() => {
+ const nextCache = new Map();
+ if (response != null) {
+ const locationKey = response.headers.get('X-Location');
+ const nextLocation = JSON.parse(locationKey);
+ const nextContent = createFromReadableStream(response.body);
+ nextCache.set(locationKey, nextContent);
+ navigate(nextLocation);
+ }
+ setCache(nextCache);
+ });
+ }
+
+ function navigate(nextLocation) {
+ startTransition(() => {
+ setLocation(loc => ({
+ ...loc,
+ ...nextLocation,
+ }));
+ });
+ }
+
+ return (
+
+ {use(content)}
+
+ );
+}
+
+export function useRouter() {
+ return useContext(RouterContext);
+}
+
+export function useMutation({ endpoint, method }) {
+ const { refresh } = useRouter();
+ const [isSaving, setIsSaving] = useState(false);
+ const [didError, setDidError] = useState(false);
+ const [error, setError] = useState(null);
+ if (didError) {
+ // Let the nearest error boundary handle errors while saving.
+ throw error;
+ }
+
+ async function performMutation(payload, requestedLocation) {
+ setIsSaving(true);
+ try {
+ const response = await fetch(
+ `${endpoint}?location=${encodeURIComponent(
+ JSON.stringify(requestedLocation),
+ )}`,
+ {
+ method,
+ body: JSON.stringify(payload),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ );
+ if (!response.ok) {
+ throw new Error(await response.text());
+ }
+ refresh(response);
+ } catch (e) {
+ setDidError(true);
+ setError(e);
+ } finally {
+ setIsSaving(false);
+ }
+ }
+
+ return [isSaving, performMutation];
+}
diff --git a/tests/integration/basic-rsc-ssr/tsconfig.json b/tests/integration/basic-rsc-ssr/tsconfig.json
new file mode 100644
index 000000000000..8f335281e09f
--- /dev/null
+++ b/tests/integration/basic-rsc-ssr/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "@modern-js/tsconfig/base",
+ "compilerOptions": {
+ "declaration": false,
+ "jsx": "react-jsx",
+ "baseUrl": "./",
+ "module": "CommonJS",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@shared/*": ["./shared/*"]
+ }
+ },
+ "include": ["src", "server", "shared", "config", "credentials.js"]
+}