Skip to content

Commit 4c39c22

Browse files
fix: perf and crashing (#101)
1 parent b64f7d8 commit 4c39c22

File tree

7 files changed

+211
-15
lines changed

7 files changed

+211
-15
lines changed

src/index.js

+6-14
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,9 @@ import {
1919
readFile,
2020
getContentFromSourcesContent,
2121
isUrlRequest,
22+
getSourceMappingUrl,
2223
} from './utils';
2324

24-
// Matches only the last occurrence of sourceMappingURL
25-
const baseRegex =
26-
'\\s*[@#]\\s*sourceMappingURL\\s*=\\s*([^\\s]*)(?![\\S\\s]*sourceMappingURL)';
27-
// Matches /* ... */ comments
28-
const regex1 = new RegExp(`/\\*${baseRegex}\\s*\\*/`);
29-
// Matches // .... comments
30-
const regex2 = new RegExp(`//${baseRegex}($|\n|\r\n?)`);
31-
3225
export default function loader(input, inputMap) {
3326
const options = getOptions(this);
3427

@@ -37,17 +30,16 @@ export default function loader(input, inputMap) {
3730
baseDataPath: 'options',
3831
});
3932

40-
const match = input.match(regex1) || input.match(regex2);
33+
let { url } = getSourceMappingUrl(input);
34+
const { replacementString } = getSourceMappingUrl(input);
4135
const callback = this.async();
4236

43-
if (!match) {
37+
if (!url) {
4438
callback(null, input, inputMap);
4539

4640
return;
4741
}
4842

49-
let [, url] = match;
50-
5143
const dataURL = parseDataURL(url);
5244

5345
const { context, resolve, addDependency, emitWarning } = this;
@@ -60,7 +52,7 @@ export default function loader(input, inputMap) {
6052
labelToName(dataURL.mimeType.parameters.get('charset')) || 'UTF-8';
6153

6254
map = decode(dataURL.body, dataURL.encodingName);
63-
map = JSON.parse(map);
55+
map = JSON.parse(map.replace(/^\)\]\}'/, ''));
6456
} catch (error) {
6557
emitWarning(
6658
`Cannot parse inline SourceMap with Charset ${dataURL.encodingName}: ${error}`
@@ -226,6 +218,6 @@ export default function loader(input, inputMap) {
226218
delete resultMap.sourcesContent;
227219
}
228220

229-
callback(null, input.replace(match[0], ''), resultMap);
221+
callback(null, input.replace(replacementString, ''), resultMap);
230222
}
231223
}

src/utils.js

+40
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@ import path from 'path';
33

44
import sourceMap from 'source-map';
55

6+
// Matches only the last occurrence of sourceMappingURL
7+
const innerRegex = /\s*[#@]\s*sourceMappingURL\s*=\s*([^\s'"]*)\s*/;
8+
9+
/* eslint-disable prefer-template */
10+
const sourceMappingURLRegex = RegExp(
11+
'(?:' +
12+
'/\\*' +
13+
'(?:\\s*\r?\n(?://)?)?' +
14+
'(?:' +
15+
innerRegex.source +
16+
')' +
17+
'\\s*' +
18+
'\\*/' +
19+
'|' +
20+
'//(?:' +
21+
innerRegex.source +
22+
')' +
23+
')' +
24+
'\\s*'
25+
);
26+
/* eslint-enable prefer-template */
27+
628
async function flattenSourceMap(map) {
729
const consumer = await new sourceMap.SourceMapConsumer(map);
830
let generatedMap;
@@ -80,9 +102,27 @@ function isUrlRequest(url) {
80102
return true;
81103
}
82104

105+
function getSourceMappingUrl(code) {
106+
const lines = code.split(/^/m);
107+
let match;
108+
109+
for (let i = lines.length - 1; i >= 0; i--) {
110+
match = lines[i].match(sourceMappingURLRegex);
111+
if (match) {
112+
break;
113+
}
114+
}
115+
116+
return {
117+
url: match ? match[1] || match[2] || '' : null,
118+
replacementString: match ? match[0] : null,
119+
};
120+
}
121+
83122
export {
84123
flattenSourceMap,
85124
readFile,
86125
getContentFromSourcesContent,
87126
isUrlRequest,
127+
getSourceMappingUrl,
88128
};

test/__snapshots__/loader.test.js.snap

+17-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ exports[`source-map-loader should leave normal files untouched: errors 1`] = `Ar
66

77
exports[`source-map-loader should leave normal files untouched: warnings 1`] = `Array []`;
88

9+
exports[`source-map-loader should leave normal files with fake source-map untouched: css 1`] = `
10+
"// without SourceMap
11+
anInvalidDirective = \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\\"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+\\" */\\";
12+
// comment
13+
"
14+
`;
15+
16+
exports[`source-map-loader should leave normal files with fake source-map untouched: errors 1`] = `Array []`;
17+
18+
exports[`source-map-loader should leave normal files with fake source-map untouched: warnings 1`] = `Array []`;
19+
920
exports[`source-map-loader should process external SourceMaps (external sources): css 1`] = `
1021
"with SourceMap
1122
// comment"
@@ -136,7 +147,12 @@ exports[`source-map-loader should skip invalid base64 SourceMap: css 1`] = `
136147

137148
exports[`source-map-loader should skip invalid base64 SourceMap: errors 1`] = `Array []`;
138149

139-
exports[`source-map-loader should skip invalid base64 SourceMap: warnings 1`] = `Array []`;
150+
exports[`source-map-loader should skip invalid base64 SourceMap: warnings 1`] = `
151+
Array [
152+
"ModuleWarning: Module Warning (from \`replaced original path\`):
153+
(Emitted value instead of an instance of Error) Cannot parse inline SourceMap with Charset UTF-8: SyntaxError: Unexpected end of JSON input",
154+
]
155+
`;
140156

141157
exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: css 1`] = `
142158
"with SourceMap
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`source-map-loader should be null: result 1`] = `null`;
4+
5+
exports[`source-map-loader should be null: result 2`] = `null`;
6+
7+
exports[`source-map-loader should find last map: result 1`] = `"/sample-source-map-last.map"`;
8+
9+
exports[`source-map-loader should not include quotes: result 1`] = `"data:application/json;base64,"`;
10+
11+
exports[`source-map-loader should work: result 1`] = `"absolute-sourceRoot-source-map.map"`;
12+
13+
exports[`source-map-loader should work: result 2`] = `"absolute-sourceRoot-source-map.map"`;
14+
15+
exports[`source-map-loader should work: result 3`] = `"absolute-sourceRoot-source-map.map"`;
16+
17+
exports[`source-map-loader should work: result 4`] = `"absolute-sourceRoot-source-map.map"`;
18+
19+
exports[`source-map-loader should work: result 5`] = `"absolute-sourceRoot-source-map.map"`;
20+
21+
exports[`source-map-loader should work: result 6`] = `"absolute-sourceRoot-source-map.map"`;
22+
23+
exports[`source-map-loader should work: result 7`] = `"http://sampledomain.com/external-source-map2.map"`;
24+
25+
exports[`source-map-loader should work: result 8`] = `"data:application/source-map;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5saW5lLXNvdXJjZS1tYXAuanMiLCJzb3VyY2VzIjpbImlubGluZS1zb3VyY2UtbWFwLnR4dCJdLCJzb3VyY2VzQ29udGVudCI6WyJ3aXRoIFNvdXJjZU1hcCJdLCJtYXBwaW5ncyI6IkFBQUEifQ=="`;
26+
27+
exports[`source-map-loader should work: result 9`] = `"/sample-source-map.map"`;

test/fixtures/normal-file2.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// without SourceMap
2+
anInvalidDirective = "\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+" */";
3+
// comment

test/loader.test.js

+12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ describe('source-map-loader', () => {
2626
expect(getErrors(stats)).toMatchSnapshot('errors');
2727
});
2828

29+
it('should leave normal files with fake source-map untouched', async () => {
30+
const testId = 'normal-file2.js';
31+
const compiler = getCompiler(testId);
32+
const stats = await compile(compiler);
33+
const codeFromBundle = getCodeFromBundle(stats, compiler);
34+
35+
expect(codeFromBundle.map).toBeUndefined();
36+
expect(codeFromBundle.css).toMatchSnapshot('css');
37+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
38+
expect(getErrors(stats)).toMatchSnapshot('errors');
39+
});
40+
2941
it('should process inlined SourceMaps', async () => {
3042
const testId = 'inline-source-map.js';
3143
const compiler = getCompiler(testId);

test/sourceMapperRegexp.test.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { getSourceMappingUrl } from '../src/utils';
2+
3+
describe('source-map-loader', () => {
4+
it('should work', async () => {
5+
const code = `/*#sourceMappingURL=absolute-sourceRoot-source-map.map*/`;
6+
const { url } = getSourceMappingUrl(code);
7+
8+
expect(url).toMatchSnapshot('result');
9+
});
10+
11+
it('should work', async () => {
12+
const code = `/* #sourceMappingURL=absolute-sourceRoot-source-map.map */`;
13+
const { url } = getSourceMappingUrl(code);
14+
15+
expect(url).toMatchSnapshot('result');
16+
});
17+
18+
it('should work', async () => {
19+
const code = `//#sourceMappingURL=absolute-sourceRoot-source-map.map`;
20+
const { url } = getSourceMappingUrl(code);
21+
22+
expect(url).toMatchSnapshot('result');
23+
});
24+
25+
it('should work', async () => {
26+
const code = `//@sourceMappingURL=absolute-sourceRoot-source-map.map`;
27+
const { url } = getSourceMappingUrl(code);
28+
29+
expect(url).toMatchSnapshot('result');
30+
});
31+
32+
it('should work', async () => {
33+
const code = ` // #sourceMappingURL=absolute-sourceRoot-source-map.map`;
34+
const { url } = getSourceMappingUrl(code);
35+
36+
expect(url).toMatchSnapshot('result');
37+
});
38+
39+
it('should work', async () => {
40+
const code = ` // # sourceMappingURL = absolute-sourceRoot-source-map.map `;
41+
const { url } = getSourceMappingUrl(code);
42+
43+
expect(url).toMatchSnapshot('result');
44+
});
45+
46+
it('should work', async () => {
47+
const code = `// #sourceMappingURL = http://sampledomain.com/external-source-map2.map`;
48+
const { url } = getSourceMappingUrl(code);
49+
50+
expect(url).toMatchSnapshot('result');
51+
});
52+
53+
it('should work', async () => {
54+
const code = `// @sourceMappingURL=data:application/source-map;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5saW5lLXNvdXJjZS1tYXAuanMiLCJzb3VyY2VzIjpbImlubGluZS1zb3VyY2UtbWFwLnR4dCJdLCJzb3VyY2VzQ29udGVudCI6WyJ3aXRoIFNvdXJjZU1hcCJdLCJtYXBwaW5ncyI6IkFBQUEifQ==`;
55+
const { url } = getSourceMappingUrl(code);
56+
57+
expect(url).toMatchSnapshot('result');
58+
});
59+
60+
it('should work', async () => {
61+
const code = `
62+
with SourceMap
63+
64+
// #sourceMappingURL = /sample-source-map.map
65+
// comment
66+
`;
67+
const { url } = getSourceMappingUrl(code);
68+
69+
expect(url).toMatchSnapshot('result');
70+
});
71+
72+
it('should find last map', async () => {
73+
const code = `
74+
with SourceMap
75+
// #sourceMappingURL = /sample-source-map-1.map
76+
// #sourceMappingURL = /sample-source-map-2.map
77+
// #sourceMappingURL = /sample-source-map-last.map
78+
// comment
79+
`;
80+
const { url } = getSourceMappingUrl(code);
81+
82+
expect(url).toMatchSnapshot('result');
83+
});
84+
85+
it('should be null', async () => {
86+
const code = `"
87+
/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+" */";`;
88+
const { url } = getSourceMappingUrl(code);
89+
90+
expect(url).toMatchSnapshot('result');
91+
});
92+
93+
it('should be null', async () => {
94+
const code = `anInvalidDirective = "\\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+" */";`;
95+
const { url } = getSourceMappingUrl(code);
96+
97+
expect(url).toMatchSnapshot('result');
98+
});
99+
100+
it('should not include quotes', async () => {
101+
const code = `// # sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+"`;
102+
const { url } = getSourceMappingUrl(code);
103+
104+
expect(url).toMatchSnapshot('result');
105+
});
106+
});

0 commit comments

Comments
 (0)