Skip to content

Commit acbf73e

Browse files
authored
feat(server): support viewing served files via /rsbuild-dev-server route (#3896)
1 parent 29d0b3e commit acbf73e

File tree

8 files changed

+235
-0
lines changed

8 files changed

+235
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { dev, rspackOnlyTest } from '@e2e/helper';
2+
import { expect } from '@playwright/test';
3+
4+
rspackOnlyTest(
5+
'should show assets on /rsbuild-dev-server path',
6+
async ({ page }) => {
7+
const rsbuild = await dev({
8+
cwd: __dirname,
9+
page,
10+
rsbuildConfig: {},
11+
});
12+
13+
await page.goto(`http://localhost:${rsbuild.port}/rsbuild-dev-server`);
14+
15+
await page.waitForSelector('h1', { state: 'attached' });
16+
await page.waitForSelector('ul', { state: 'attached' });
17+
18+
const titles = await page.$$eval('h1', (nodes) =>
19+
nodes.map((n) => n.textContent),
20+
);
21+
expect(titles.includes('Assets Report:')).toBe(true);
22+
23+
// check all href are valid
24+
const hrefList = await page.$$eval('ul li a', (nodes) =>
25+
nodes.map((node) => node?.textContent),
26+
);
27+
for (const href of hrefList) {
28+
const status = await page.evaluate(async (url) => {
29+
const response = await fetch(url as string);
30+
return response.status;
31+
}, href);
32+
33+
expect(status).toBeGreaterThanOrEqual(200);
34+
}
35+
36+
await rsbuild.close();
37+
},
38+
);
39+
40+
rspackOnlyTest(
41+
'should show assets on /rsbuild-dev-server path with server.base and assetPrefix',
42+
async ({ page }) => {
43+
const rsbuild = await dev({
44+
cwd: __dirname,
45+
page,
46+
rsbuildConfig: {
47+
dev: {
48+
assetPrefix: '/testing/assets/',
49+
},
50+
server: {
51+
base: '/testing',
52+
},
53+
},
54+
});
55+
56+
await page.goto(
57+
`http://localhost:${rsbuild.port}/testing/rsbuild-dev-server`,
58+
);
59+
await page.waitForSelector('h1', { state: 'attached' });
60+
await page.waitForSelector('ul', { state: 'attached' });
61+
const titles = await page.$$eval('h1', (nodes) =>
62+
nodes.map((n) => n.textContent),
63+
);
64+
expect(titles.includes('Assets Report:')).toBe(true);
65+
66+
// check all href are valid
67+
const hrefList = await page.$$eval('ul li a', (nodes) =>
68+
nodes.map((node) => node?.textContent),
69+
);
70+
for (const href of hrefList) {
71+
const status = await page.evaluate(async (url) => {
72+
const response = await fetch(url as string);
73+
return response.status;
74+
}, href);
75+
76+
expect(status).toBeGreaterThanOrEqual(200);
77+
}
78+
79+
await rsbuild.close();
80+
},
81+
);
82+
83+
rspackOnlyTest(
84+
'should show assets on /rsbuild-dev-server path with environments',
85+
async ({ page }) => {
86+
const entry1 = './src/index.tsx';
87+
const entry2 = './src2/index.tsx';
88+
89+
const rsbuild = await dev({
90+
cwd: __dirname,
91+
page,
92+
rsbuildConfig: {
93+
environments: {
94+
test1: {
95+
source: {
96+
entry: {
97+
index: entry1,
98+
},
99+
},
100+
},
101+
test2: {
102+
source: {
103+
entry: {
104+
index: entry2,
105+
},
106+
},
107+
},
108+
},
109+
},
110+
});
111+
112+
await page.goto(`http://localhost:${rsbuild.port}/rsbuild-dev-server`);
113+
await page.waitForSelector('h1', { state: 'attached' });
114+
await page.waitForSelector('ul', { state: 'attached' });
115+
const titles = await page.$$eval('h1', (nodes) =>
116+
nodes.map((n) => n.textContent),
117+
);
118+
const subTitles = await page.$$eval('h2', (nodes) =>
119+
nodes.map((n) => n.textContent),
120+
);
121+
expect(titles.includes('Assets Report:')).toBe(true);
122+
expect(subTitles.includes('Compilation: test1')).toBe(true);
123+
expect(subTitles.includes('Compilation: test2')).toBe(true);
124+
125+
// check all href are valid
126+
const hrefList = await page.$$eval('ul li a', (nodes) =>
127+
nodes.map((node) => node?.textContent),
128+
);
129+
for (const href of hrefList) {
130+
const status = await page.evaluate(async (url) => {
131+
const response = await fetch(url as string);
132+
return response.status;
133+
}, href);
134+
135+
expect(status).toBeGreaterThanOrEqual(200);
136+
}
137+
138+
await rsbuild.close();
139+
},
140+
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineConfig } from '@rsbuild/core';
2+
import { pluginReact } from '@rsbuild/plugin-react';
3+
4+
export default defineConfig({
5+
plugins: [pluginReact()],
6+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const App = () => {
2+
return (
3+
<div className="content">
4+
<h1>Rsbuild with React</h1>
5+
<p>Start building amazing things with Rsbuild.</p>
6+
</div>
7+
);
8+
};
9+
10+
export default App;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import App from './App';
4+
5+
const root = ReactDOM.createRoot(document.getElementById('root')!);
6+
root.render(
7+
<React.StrictMode>
8+
<App />
9+
</React.StrictMode>,
10+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const App = () => {
2+
return (
3+
<div className="content">
4+
<h1>Rsbuild with React</h1>
5+
<p>Start building amazing things with Rsbuild.</p>
6+
</div>
7+
);
8+
};
9+
10+
export default App;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import App from './App';
4+
5+
const root = ReactDOM.createRoot(document.getElementById('root')!);
6+
root.render(
7+
<React.StrictMode>
8+
<App />
9+
</React.StrictMode>,
10+
);

packages/core/src/server/getDevMiddlewares.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
getHtmlCompletionMiddleware,
1818
getHtmlFallbackMiddleware,
1919
getRequestLoggerMiddleware,
20+
viewingServedFilesMiddleware,
2021
} from './middlewares';
2122

2223
export type CompileMiddlewareAPI = {
@@ -75,6 +76,7 @@ const applyDefaultMiddlewares = async ({
7576
output,
7677
pwd,
7778
outputFileSystem,
79+
environments,
7880
}: RsbuildDevMiddlewareOptions & {
7981
middlewares: Middlewares;
8082
}): Promise<{
@@ -126,6 +128,8 @@ const applyDefaultMiddlewares = async ({
126128
);
127129
middlewares.push(['/__open-in-editor', launchEditorMiddleware()]);
128130

131+
middlewares.push(viewingServedFilesMiddleware({ environments }));
132+
129133
if (compileMiddlewareAPI) {
130134
middlewares.push(compileMiddlewareAPI.middleware);
131135

packages/core/src/server/middlewares.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import color from 'picocolors';
55
import { addTrailingSlash } from '../helpers';
66
import { logger } from '../logger';
77
import type {
8+
EnvironmentAPI,
89
HtmlFallback,
910
RequestHandler as Middleware,
1011
Rspack,
@@ -244,3 +245,47 @@ export const getHtmlFallbackMiddleware: (params: {
244245
next();
245246
};
246247
};
248+
249+
/**
250+
* Support viewing served files via /rsbuild-dev-server route
251+
*/
252+
export const viewingServedFilesMiddleware: (params: {
253+
environments: EnvironmentAPI;
254+
}) => Middleware =
255+
({ environments }) =>
256+
async (req, res, next) => {
257+
const url = req.url!;
258+
const pathname = getUrlPathname(url);
259+
260+
if (pathname === '/rsbuild-dev-server') {
261+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
262+
res.write(
263+
'<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><h1>Assets Report:</h1>',
264+
);
265+
try {
266+
for (const key in environments) {
267+
const list = [];
268+
res.write(`<h2>Compilation: ${key}</h2>`);
269+
const environment = environments[key];
270+
const stats = await environment.getStats();
271+
const statsForPrint = stats.toJson();
272+
const { assets = [] } = statsForPrint;
273+
res.write('<ul>');
274+
for (const asset of assets) {
275+
list.push(
276+
`<li><a target="_blank" href="${asset?.name}">${asset?.name}</a></li>`,
277+
);
278+
}
279+
res.write(list?.join(''));
280+
res.write('</ul>');
281+
}
282+
res.end('</body></html>');
283+
} catch (err) {
284+
logger.error(err);
285+
res.writeHead(500);
286+
res.end('Failed to list the files');
287+
}
288+
} else {
289+
next();
290+
}
291+
};

0 commit comments

Comments
 (0)