Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): support load SSR module #2528

Merged
merged 24 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions e2e/cases/server/ssr/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { rspackOnlyTest, updateConfigForTest } from '@e2e/helper';
import { expect } from '@playwright/test';
import { startDevServer } from './scripts/server.mjs';

rspackOnlyTest('support SSR', async ({ page }) => {
const { config, close } = await startDevServer(
__dirname,
await updateConfigForTest({}, __dirname),
);

const url1 = new URL(`http://localhost:${config.port}`);

const res = await page.goto(url1.href);

expect(await res?.text()).toMatch(/Rsbuild with React/);

await close();
});

rspackOnlyTest('support SSR with esm target', async ({ page }) => {
process.env.TEST_ESM_LIBRARY = '1';
const { config, close } = await startDevServer(
__dirname,
await updateConfigForTest({}, __dirname),
);

const url1 = new URL(`http://localhost:${config.port}`);

const res = await page.goto(url1.href);

expect(await res?.text()).toMatch(/Rsbuild with React/);

await close();

delete process.env.TEST_ESM_LIBRARY;
});

rspackOnlyTest('support SSR with split chunk', async ({ page }) => {
process.env.TEST_SPLIT_CHUNK = '1';
const { config, close } = await startDevServer(
__dirname,
await updateConfigForTest({}, __dirname),
);

const url1 = new URL(`http://localhost:${config.port}`);

const res = await page.goto(url1.href);

expect(await res?.text()).toMatch(/Rsbuild with React/);

await close();

delete process.env.TEST_SPLIT_CHUNK;
});
12 changes: 12 additions & 0 deletions e2e/cases/server/ssr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"private": true,
"name": "@e2e/custom-server-ssr",
"version": "1.0.0",
"scripts": {
"dev": "node ./scripts/index.mjs",
"dev:esm": "TEST_ESM_LIBRARY=true node --experimental-vm-modules ./scripts/index.mjs"
},
"dependencies": {
"polka": "^0.5.2"
}
}
77 changes: 77 additions & 0 deletions e2e/cases/server/ssr/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';

export default defineConfig({
plugins: [pluginReact()],
environments: {
web: {
output: {
target: 'web',
},
source: {
entry: {
index: './src/index',
},
},
},
ssr: {
output: {
target: 'node',
},
source: {
entry: {
index: './src/index.server',
},
},
tools: {
rspack: (config) => {
if (process.env.TEST_ESM_LIBRARY) {
return {
...config,
experiments: {
...config.experiments,
outputModule: true,
},
output: {
...config.output,
filename: '[name].mjs',
chunkFilename: '[name].mjs',
chunkFormat: 'module',
chunkLoading: 'import',
library: {
type: 'module',
},
},
};
}

if (process.env.TEST_SPLIT_CHUNK) {
return {
...config,
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all',
enforceSizeThreshold: 50000,
minSize: 0,
cacheGroups: {
'lib-react': {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler|react-refresh|@rspack[\\/]plugin-react-refresh)[\\/]/,
priority: 0,
name: 'lib-react',
reuseExistingChunk: true,
},
},
},
},
};
}
return config;
},
},
},
},
html: {
template: './template.html',
},
});
3 changes: 3 additions & 0 deletions e2e/cases/server/ssr/scripts/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { startDevServer } from './server.mjs';

startDevServer(process.cwd());
72 changes: 72 additions & 0 deletions e2e/cases/server/ssr/scripts/server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { createRsbuild, logger } from '@rsbuild/core';
import { loadConfig } from '@rsbuild/core';
import polka from 'polka';

export const serverRender = (rsbuildServer) => async (_req, res, _next) => {
const indexModule = await rsbuildServer.environments.ssr.loadBundle('index');

const markup = indexModule.render();

const template =
await rsbuildServer.environments.web.getTransformedHtml('index');

const html = template.replace('<!--app-content-->', markup);

res.writeHead(200, {
'Content-Type': 'text/html',
});
res.end(html);
};

export async function startDevServer(fixtures, overridsConfig) {
process.env.NODE_ENV = 'development';

const { content } = overridsConfig
? { content: overridsConfig }
: await loadConfig({
cwd: fixtures,
});

const rsbuild = await createRsbuild({
cwd: fixtures,
rsbuildConfig: content,
});

const app = polka();

const rsbuildServer = await rsbuild.createDevServer();

const serverRenderMiddleware = serverRender(rsbuildServer);

app.use('/', async (req, res, next) => {
if (req.method === 'GET' && req.url === '/') {
try {
await serverRenderMiddleware(req, res, next);
} catch (err) {
logger.error('ssr render error, downgrade to csr...\n', err);
9aoy marked this conversation as resolved.
Show resolved Hide resolved
next();
}
} else {
next();
}
});

app.use(rsbuildServer.middlewares);

const { port } = rsbuildServer;

const { server } = app.listen({ port }, async () => {
await rsbuildServer.afterListen();
});

// subscribe the server's http upgrade event to handle WebSocket upgrade
server.on('upgrade', rsbuildServer.onHTTPUpgrade);

return {
config: { port },
close: async () => {
await rsbuildServer.close();
server.close();
},
};
}
26 changes: 26 additions & 0 deletions e2e/cases/server/ssr/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
body {
margin: 0;
color: #fff;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
background-image: linear-gradient(to bottom, #020917, #101725);
}

.content {
display: flex;
min-height: 100vh;
line-height: 1.1;
text-align: center;
flex-direction: column;
justify-content: center;
}

.content h1 {
font-size: 3.6rem;
font-weight: 700;
}

.content p {
font-size: 1.2rem;
font-weight: 400;
opacity: 0.5;
}
12 changes: 12 additions & 0 deletions e2e/cases/server/ssr/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import './App.css';

const App = () => {
return (
<div className="content">
<h1>Rsbuild with React</h1>
<p>Start building amazing things with Rsbuild.</p>
</div>
);
};

export default App;
1 change: 1 addition & 0 deletions e2e/cases/server/ssr/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@rsbuild/core/types" />
14 changes: 14 additions & 0 deletions e2e/cases/server/ssr/src/index.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';

// test dynamic import
import('./test');

export function render() {
return ReactDOMServer.renderToString(
<React.StrictMode>
<App />
</React.StrictMode>,
);
}
10 changes: 10 additions & 0 deletions e2e/cases/server/ssr/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
3 changes: 3 additions & 0 deletions e2e/cases/server/ssr/src/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const getA = () => {
return 'A';
};
11 changes: 11 additions & 0 deletions e2e/cases/server/ssr/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rsbuild + React + TS</title>
</head>
<body>
<div id="root"><!--app-content--></div>
</body>
</html>
2 changes: 1 addition & 1 deletion e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "1.0.0",
"scripts": {
"test": "pnpm test:rspack && pnpm test:webpack",
"test:rspack": "playwright test",
"test:rspack": "cross-env NODE_OPTIONS=--experimental-vm-modules playwright test",
"test:webpack": "cross-env PROVIDE_TYPE=webpack playwright test"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion e2e/scripts/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export async function getRandomPort(
}
}

const updateConfigForTest = async (
export const updateConfigForTest = async (
originalConfig: RsbuildConfig,
cwd: string = process.cwd(),
) => {
Expand Down
Loading
Loading