Skip to content

Commit 61fb775

Browse files
authored
feat(plugin): support for source map merging in transform hook (#6050)
1 parent b38d946 commit 61fb775

File tree

22 files changed

+231
-94
lines changed

22 files changed

+231
-94
lines changed

e2e/cases/mode/basic/index.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ test('should allow to set development mode when building', async () => {
99
},
1010
});
1111

12-
const files = await rsbuild.getDistFiles(false);
12+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
1313

1414
// should not have filename hash in development mode
1515
const indexFile = Object.keys(files).find((key) =>
@@ -37,7 +37,7 @@ test('should allow to set none mode when building', async () => {
3737
},
3838
});
3939

40-
const files = await rsbuild.getDistFiles(false);
40+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
4141

4242
// should not have filename hash in none mode
4343
const indexFile = Object.keys(files).find((key) =>
@@ -64,7 +64,7 @@ rspackOnlyTest(
6464
},
6565
});
6666

67-
const files = await rsbuild.getDistFiles(false);
67+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
6868

6969
// should have filename hash in production mode
7070
const indexFile = Object.keys(files).find((key) =>

e2e/cases/output/inline-chunk/index.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test('inline all scripts should work and emit all source maps', async ({
4040
// test runtime
4141
expect(await page.evaluate('window.test')).toBe('aaaa');
4242

43-
const files = await rsbuild.getDistFiles(false);
43+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
4444

4545
// no entry chunks or runtime chunks in output
4646
expect(
@@ -68,7 +68,7 @@ test('using RegExp to inline scripts', async () => {
6868
tools: toolsConfig,
6969
},
7070
});
71-
const files = await rsbuild.getDistFiles(false);
71+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
7272

7373
// no index.js in output
7474
expect(
@@ -96,7 +96,7 @@ test('inline scripts by filename and file size', async () => {
9696
tools: toolsConfig,
9797
},
9898
});
99-
const files = await rsbuild.getDistFiles(false);
99+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
100100

101101
// no index.js in output
102102
expect(
@@ -122,7 +122,7 @@ test('using RegExp to inline styles', async () => {
122122
tools: toolsConfig,
123123
},
124124
});
125-
const files = await rsbuild.getDistFiles(false);
125+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
126126

127127
// no index.css in output
128128
expect(
@@ -144,7 +144,7 @@ test('inline styles by filename and file size', async () => {
144144
tools: toolsConfig,
145145
},
146146
});
147-
const files = await rsbuild.getDistFiles(false);
147+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
148148

149149
// no index.css in output
150150
expect(
@@ -242,7 +242,7 @@ test('inline scripts does not work when enable is false', async () => {
242242
tools: toolsConfig,
243243
},
244244
});
245-
const files = await rsbuild.getDistFiles(false);
245+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
246246

247247
// all index.js in output
248248
expect(
@@ -271,7 +271,7 @@ test('inline styles does not work when enable is false', async () => {
271271
tools: toolsConfig,
272272
},
273273
});
274-
const files = await rsbuild.getDistFiles(false);
274+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
275275

276276
// all index.css in output
277277
expect(
@@ -298,7 +298,7 @@ test('inline chunk works in production mode when enable is auto', async () => {
298298
tools: toolsConfig,
299299
},
300300
});
301-
const files = await rsbuild.getDistFiles(false);
301+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
302302

303303
// no index.js in output
304304
expect(

e2e/cases/output/source-map/index.test.ts

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
11
import { readFileSync } from 'node:fs';
2-
import { join } from 'node:path';
3-
import { build, dev } from '@e2e/helper';
2+
import path, { join } from 'node:path';
3+
import { build, dev, mapSourceMapPositions } from '@e2e/helper';
44
import { expect, test } from '@playwright/test';
55
import type { Rspack } from '@rsbuild/core';
6-
import sourceMap from 'source-map';
76

87
const fixtures = __dirname;
98

10-
async function validateSourceMap(
11-
rawSourceMap: string,
12-
generatedPositions: {
13-
line: number;
14-
column: number;
15-
}[],
16-
) {
17-
const consumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
18-
19-
const originalPositions = generatedPositions.map((generatedPosition) =>
20-
consumer.originalPositionFor({
21-
line: generatedPosition.line,
22-
column: generatedPosition.column,
23-
}),
24-
);
25-
26-
consumer.destroy();
27-
return originalPositions;
28-
}
29-
309
async function testSourceMapType(devtool: Rspack.Configuration['devtool']) {
3110
const rsbuild = await build({
3211
cwd: fixtures,
@@ -36,24 +15,31 @@ async function testSourceMapType(devtool: Rspack.Configuration['devtool']) {
3615
js: devtool,
3716
},
3817
legalComments: 'none',
18+
filenameHash: false,
3919
},
4020
},
4121
});
4222

43-
const files = await rsbuild.getDistFiles(false);
44-
const [, jsMapContent] = Object.entries(files).find(
45-
([name]) => name.includes('static/js/') && name.endsWith('.js.map'),
46-
)!;
23+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
4724

48-
const [, jsContent] = Object.entries(files).find(
49-
([name]) => name.includes('static/js/') && name.endsWith('.js'),
50-
)!;
25+
const indexSourceCode = readFileSync(
26+
path.join(__dirname, 'src/index.js'),
27+
'utf-8',
28+
);
29+
const appSourceCode = readFileSync(
30+
path.join(__dirname, 'src/App.jsx'),
31+
'utf-8',
32+
);
33+
const outputCode =
34+
files[Object.keys(files).find((file) => file.endsWith('index.js'))!];
35+
const sourceMap =
36+
files[Object.keys(files).find((file) => file.endsWith('index.js.map'))!];
5137

52-
const AppContentIndex = jsContent.indexOf('Hello Rsbuild!');
53-
const indexContentIndex = jsContent.indexOf('window.aa');
38+
const AppContentIndex = outputCode.indexOf('Hello Rsbuild!');
39+
const indexContentIndex = outputCode.indexOf('window.test');
5440

55-
const originalPositions = (
56-
await validateSourceMap(jsMapContent, [
41+
const positions = (
42+
await mapSourceMapPositions(sourceMap, [
5743
{
5844
line: 1,
5945
column: AppContentIndex,
@@ -65,22 +51,23 @@ async function testSourceMapType(devtool: Rspack.Configuration['devtool']) {
6551
])
6652
).map((o) => ({
6753
...o,
68-
source: o.source!.split('source-map/')[1] || o.source,
54+
source: o.source?.split('source-map/')[1] || o.source,
6955
}));
7056

71-
expect(originalPositions[0]).toEqual({
72-
source: 'src/App.jsx',
73-
line: 2,
74-
column: 24,
75-
name: null,
76-
});
77-
78-
expect(originalPositions[1]).toEqual({
79-
source: 'src/index.js',
80-
line: 7,
81-
column: 0,
82-
name: 'window',
83-
});
57+
expect(positions).toEqual([
58+
{
59+
source: 'src/App.jsx',
60+
line: 2,
61+
column: appSourceCode.split('\n')[1].indexOf('Hello Rsbuild!'),
62+
name: null,
63+
},
64+
{
65+
source: 'src/index.js',
66+
line: 7,
67+
column: indexSourceCode.split('\n')[6].indexOf('window'),
68+
name: 'window',
69+
},
70+
]);
8471
}
8572

8673
const productionDevtools: Rspack.Configuration['devtool'][] = [
@@ -101,7 +88,7 @@ test('should not generate source map by default in production build', async () =
10188
cwd: fixtures,
10289
});
10390

104-
const files = await rsbuild.getDistFiles(false);
91+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
10592

10693
const jsMapFiles = Object.keys(files).filter((files) =>
10794
files.endsWith('.js.map'),
@@ -123,7 +110,7 @@ test('should generate source map if `output.sourceMap` is true', async () => {
123110
},
124111
});
125112

126-
const files = await rsbuild.getDistFiles(false);
113+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
127114

128115
const jsMapFiles = Object.keys(files).filter((files) =>
129116
files.endsWith('.js.map'),
@@ -145,7 +132,7 @@ test('should not generate source map if `output.sourceMap` is false', async () =
145132
},
146133
});
147134

148-
const files = await rsbuild.getDistFiles(false);
135+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
149136

150137
const jsMapFiles = Object.keys(files).filter((files) =>
151138
files.endsWith('.js.map'),
@@ -165,7 +152,7 @@ test('should generate source map correctly in development build', async ({
165152
page,
166153
});
167154

168-
const files = await rsbuild.getDistFiles(false);
155+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
169156

170157
const jsMapFile = Object.keys(files).find((files) =>
171158
files.endsWith('.js.map'),
@@ -200,7 +187,7 @@ test('should allow to only generate source map for CSS files', async () => {
200187
},
201188
});
202189

203-
const files = await rsbuild.getDistFiles(false);
190+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
204191

205192
const jsMapFiles = Object.keys(files).filter((files) =>
206193
files.endsWith('.js.map'),
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
{
22
"private": true,
33
"name": "@e2e/webpack-source-map",
4-
"version": "1.0.0",
5-
"dependencies": {
6-
"source-map": "0.7.6"
7-
}
4+
"version": "1.0.0"
85
}

e2e/cases/output/source-map/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import App from './App';
44

55
import './index.css';
66

7-
window.aa = 2;
7+
window.test = 2;
88

99
const container = document.getElementById('root');
1010
if (container) {

e2e/cases/performance/external-helpers/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test('should externalHelpers by default', async () => {
2121
},
2222
},
2323
});
24-
const files = await rsbuild.getDistFiles(false);
24+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
2525

2626
const content =
2727
files[

e2e/cases/performance/moment-locale/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test('removeMomentLocale false (default)', async () => {
2828
runServer: false,
2929
});
3030

31-
const files = await rsbuild.getDistFiles(false);
31+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
3232

3333
const fileName = Object.keys(files).find(
3434
(file) => file.includes('moment-js') && file.endsWith('.js.map'),
@@ -67,7 +67,7 @@ test('removeMomentLocale true', async () => {
6767
runServer: false,
6868
});
6969

70-
const files = await rsbuild.getDistFiles(false);
70+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
7171

7272
const fileName = Object.keys(files).find(
7373
(file) => file.includes('moment-js') && file.endsWith('.js.map'),
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { readFileSync } from 'node:fs';
2+
import path from 'node:path';
3+
import { build, dev, mapSourceMapPositions, rspackOnlyTest } from '@e2e/helper';
4+
import { expect } from '@playwright/test';
5+
6+
const expectSourceMap = async (files: Record<string, string>) => {
7+
const sourceCode = readFileSync(
8+
path.join(__dirname, 'src/index.ts'),
9+
'utf-8',
10+
);
11+
const outputCode =
12+
files[Object.keys(files).find((file) => file.endsWith('index.js'))!];
13+
const sourceMap =
14+
files[Object.keys(files).find((file) => file.endsWith('index.js.map'))!];
15+
16+
const argsPosition = outputCode.indexOf('"args"');
17+
const helloPosition = outputCode.indexOf('"hello"');
18+
19+
const positions = await mapSourceMapPositions(sourceMap, [
20+
{
21+
line: 1,
22+
column: argsPosition,
23+
},
24+
{
25+
line: 1,
26+
column: helloPosition,
27+
},
28+
]);
29+
30+
const sourceLines = sourceCode.split('\n');
31+
expect(positions).toEqual([
32+
{
33+
source: 'webpack:///src/index.ts',
34+
line: 2,
35+
column: sourceLines[1].indexOf(`'args'`),
36+
name: null,
37+
},
38+
{
39+
source: 'webpack:///src/index.ts',
40+
line: 5,
41+
column: sourceLines[4].indexOf(`'hello'`),
42+
name: null,
43+
},
44+
]);
45+
};
46+
47+
rspackOnlyTest(
48+
'should merge source map when plugin transforms code in production mode',
49+
async () => {
50+
const rsbuild = await build({
51+
cwd: __dirname,
52+
});
53+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
54+
55+
await expectSourceMap(files);
56+
await rsbuild.close();
57+
},
58+
);
59+
60+
rspackOnlyTest(
61+
'should merge source map when plugin transforms code in development mode',
62+
async ({ page }) => {
63+
const rsbuild = await dev({
64+
cwd: __dirname,
65+
page,
66+
});
67+
const files = await rsbuild.getDistFiles({ sourceMaps: true });
68+
69+
await expectSourceMap(files);
70+
await rsbuild.close();
71+
},
72+
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { type RsbuildPlugin, rspack } from '@rsbuild/core';
2+
3+
export const myPlugin: RsbuildPlugin = {
4+
name: 'my-plugin',
5+
setup(api) {
6+
api.transform({ test: /\.ts$/, order: 'post' }, async ({ code }) => {
7+
const result = await rspack.experiments.swc.transform(code, {
8+
jsc: {
9+
target: 'es5',
10+
},
11+
sourceMaps: true,
12+
});
13+
return {
14+
code: result.code,
15+
map: result.map,
16+
};
17+
});
18+
},
19+
};

0 commit comments

Comments
 (0)