Skip to content

Commit 4a0c8fb

Browse files
refactor: next
1 parent 96fadea commit 4a0c8fb

15 files changed

+4123
-950
lines changed

.github/workflows/nodejs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
matrix:
5757
os: [ubuntu-latest, windows-latest, macos-latest]
5858
node-version: [10.x, 12.x, 14.x]
59-
webpack-version: [4, latest]
59+
webpack-version: [latest]
6060

6161
runs-on: ${{ matrix.os }}
6262

package-lock.json

+449-503
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+10-13
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,17 @@
3838
"dist"
3939
],
4040
"peerDependencies": {
41-
"webpack": "^4.0.0 || ^5.0.0"
41+
"webpack": "^5.0.0"
4242
},
4343
"dependencies": {
4444
"abab": "^2.0.5",
4545
"iconv-lite": "^0.6.2",
46-
"loader-utils": "^2.0.0",
47-
"schema-utils": "^3.0.0",
48-
"source-map": "^0.6.1",
49-
"whatwg-mimetype": "^2.3.0"
46+
"source-map": "^0.6.1"
5047
},
5148
"devDependencies": {
52-
"@babel/cli": "^7.12.8",
53-
"@babel/core": "^7.12.9",
54-
"@babel/preset-env": "^7.12.7",
49+
"@babel/cli": "^7.12.10",
50+
"@babel/core": "^7.12.10",
51+
"@babel/preset-env": "^7.12.11",
5552
"@commitlint/cli": "^11.0.0",
5653
"@commitlint/config-conventional": "^11.0.0",
5754
"@webpack-contrib/defaults": "^6.3.0",
@@ -60,17 +57,17 @@
6057
"cross-env": "^7.0.3",
6158
"del": "^6.0.0",
6259
"del-cli": "^3.0.1",
63-
"eslint": "^7.14.0",
64-
"eslint-config-prettier": "^6.15.0",
60+
"eslint": "^7.16.0",
61+
"eslint-config-prettier": "^7.1.0",
6562
"eslint-plugin-import": "^2.22.1",
66-
"husky": "^4.3.0",
63+
"husky": "^4.3.6",
6764
"jest": "^26.6.3",
68-
"lint-staged": "^10.5.2",
65+
"lint-staged": "^10.5.3",
6966
"memfs": "^3.2.0",
7067
"npm-run-all": "^4.1.5",
7168
"prettier": "^2.2.1",
7269
"standard-version": "^9.0.0",
73-
"webpack": "^5.9.0"
70+
"webpack": "^5.11.0"
7471
},
7572
"keywords": [
7673
"webpack"

src/index.js

+1-11
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@
44
*/
55
import path from "path";
66

7-
import { getOptions } from "loader-utils";
8-
import { validate } from "schema-utils";
9-
107
import schema from "./options.json";
118
import { getSourceMappingURL, fetchFromURL, flattenSourceMap } from "./utils";
129

1310
export default async function loader(input, inputMap) {
14-
const options = getOptions(this);
15-
16-
validate(schema, options, {
17-
name: "Source Map Loader",
18-
baseDataPath: "options",
19-
});
20-
11+
const options = this.getOptions(schema);
2112
const { sourceMappingURL, replacementString } = getSourceMappingURL(input);
22-
2313
const callback = this.async();
2414

2515
if (!sourceMappingURL) {

src/options.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"title": "Source Map Loader options",
23
"type": "object",
34
"additionalProperties": false,
45
"properties": {

src/parse-data-url.js

+200-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,67 @@
1-
import MIMEType from "whatwg-mimetype";
21
import { atob } from "abab";
32

3+
const removeLeadingAndTrailingHTTPWhitespace = (string) =>
4+
string.replace(/^[ \t\n\r]+/, "").replace(/[ \t\n\r]+$/, "");
5+
6+
const removeTrailingHTTPWhitespace = (string) =>
7+
string.replace(/[ \t\n\r]+$/, "");
8+
9+
const isHTTPWhitespaceChar = (char) =>
10+
char === " " || char === "\t" || char === "\n" || char === "\r";
11+
12+
const solelyContainsHTTPTokenCodePoints = (string) =>
13+
/^[-!#$%&'*+.^_`|~A-Za-z0-9]*$/.test(string);
14+
15+
const soleyContainsHTTPQuotedStringTokenCodePoints = (string) =>
16+
/^[\t\u0020-\u007E\u0080-\u00FF]*$/.test(string);
17+
18+
const asciiLowercase = (string) =>
19+
string.replace(/[A-Z]/g, (l) => l.toLowerCase());
20+
21+
const collectAnHTTPQuotedString = (input, position) => {
22+
let value = "";
23+
24+
// eslint-disable-next-line no-param-reassign
25+
position += 1;
26+
27+
// eslint-disable-next-line no-constant-condition
28+
while (true) {
29+
while (
30+
position < input.length &&
31+
input[position] !== '"' &&
32+
input[position] !== "\\"
33+
) {
34+
value += input[position];
35+
// eslint-disable-next-line no-param-reassign
36+
position += 1;
37+
}
38+
39+
if (position >= input.length) {
40+
break;
41+
}
42+
43+
const quoteOrBackslash = input[position];
44+
45+
// eslint-disable-next-line no-param-reassign
46+
position += 1;
47+
48+
if (quoteOrBackslash === "\\") {
49+
if (position >= input.length) {
50+
value += "\\";
51+
break;
52+
}
53+
54+
value += input[position];
55+
// eslint-disable-next-line no-param-reassign
56+
position += 1;
57+
} else {
58+
break;
59+
}
60+
}
61+
62+
return [value, position];
63+
};
64+
465
function isASCIIHex(c) {
566
return (
667
(c >= 0x30 && c <= 0x39) ||
@@ -56,14 +117,16 @@ export default function parseDataUrl(stringInput) {
56117
const input = parsedUrl.toString().substring(5);
57118

58119
let position = 0;
59-
let mimeType = "";
120+
let mediaType = "";
60121

61122
while (position < input.length && input[position] !== ",") {
62-
mimeType += input[position];
123+
mediaType += input[position];
63124
position += 1;
64125
}
65126

66-
mimeType = mimeType.replace(/^[ \t\n\f\r]+/, "").replace(/[ \t\n\f\r]+$/, "");
127+
mediaType = mediaType
128+
.replace(/^[ \t\n\f\r]+/, "")
129+
.replace(/[ \t\n\f\r]+$/, "");
67130

68131
if (position === input.length) {
69132
return null;
@@ -76,7 +139,9 @@ export default function parseDataUrl(stringInput) {
76139
let body = Buffer.from(percentDecodeBytes(Buffer.from(encodedBody, "utf-8")));
77140

78141
// Can't use /i regexp flag because it isn't restricted to ASCII.
79-
const mimeTypeBase64MatchResult = /(.*); *[Bb][Aa][Ss][Ee]64$/.exec(mimeType);
142+
const mimeTypeBase64MatchResult = /(.*); *[Bb][Aa][Ss][Ee]64$/.exec(
143+
mediaType
144+
);
80145

81146
if (mimeTypeBase64MatchResult) {
82147
const stringBody = body.toString("binary");
@@ -88,20 +153,141 @@ export default function parseDataUrl(stringInput) {
88153

89154
body = Buffer.from(asString, "binary");
90155

91-
[, mimeType] = mimeTypeBase64MatchResult;
156+
[, mediaType] = mimeTypeBase64MatchResult;
92157
}
93158

94-
if (mimeType.startsWith(";")) {
95-
mimeType = `text/plain ${mimeType}`;
159+
if (mediaType.startsWith(";")) {
160+
mediaType = `text/plain ${mediaType}`;
96161
}
97162

98-
let mimeTypeRecord;
163+
const result = {
164+
// eslint-disable-next-line no-undefined
165+
type: undefined,
166+
// eslint-disable-next-line no-undefined
167+
subtype: undefined,
168+
parameters: new Map(),
169+
isBase64: Boolean(mimeTypeBase64MatchResult),
170+
body,
171+
};
99172

100-
try {
101-
mimeTypeRecord = new MIMEType(mimeType);
102-
} catch (e) {
103-
mimeTypeRecord = new MIMEType("text/plain;charset=US-ASCII");
173+
if (!mediaType) {
174+
return result;
175+
}
176+
177+
const inputMediaType = removeLeadingAndTrailingHTTPWhitespace(mediaType);
178+
179+
let positionMediaType = 0;
180+
let type = "";
181+
182+
while (
183+
positionMediaType < inputMediaType.length &&
184+
inputMediaType[positionMediaType] !== "/"
185+
) {
186+
type += inputMediaType[positionMediaType];
187+
positionMediaType += 1;
188+
}
189+
190+
if (type.length === 0 || !solelyContainsHTTPTokenCodePoints(type)) {
191+
return result;
192+
}
193+
194+
if (positionMediaType >= inputMediaType.length) {
195+
return result;
196+
}
197+
198+
// Skips past "/"
199+
positionMediaType += 1;
200+
201+
let subtype = "";
202+
203+
while (
204+
positionMediaType < inputMediaType.length &&
205+
inputMediaType[positionMediaType] !== ";"
206+
) {
207+
subtype += inputMediaType[positionMediaType];
208+
positionMediaType += 1;
209+
}
210+
211+
subtype = removeTrailingHTTPWhitespace(subtype);
212+
213+
if (subtype.length === 0 || !solelyContainsHTTPTokenCodePoints(subtype)) {
214+
return result;
215+
}
216+
217+
result.type = asciiLowercase(type);
218+
result.subtype = asciiLowercase(subtype);
219+
220+
while (positionMediaType < inputMediaType.length) {
221+
// Skip past ";"
222+
positionMediaType += 1;
223+
224+
while (isHTTPWhitespaceChar(inputMediaType[positionMediaType])) {
225+
positionMediaType += 1;
226+
}
227+
228+
let parameterName = "";
229+
230+
while (
231+
positionMediaType < inputMediaType.length &&
232+
inputMediaType[positionMediaType] !== ";" &&
233+
inputMediaType[positionMediaType] !== "="
234+
) {
235+
parameterName += inputMediaType[positionMediaType];
236+
positionMediaType += 1;
237+
}
238+
239+
parameterName = asciiLowercase(parameterName);
240+
241+
if (positionMediaType < inputMediaType.length) {
242+
if (inputMediaType[positionMediaType] === ";") {
243+
// eslint-disable-next-line no-continue
244+
continue;
245+
}
246+
247+
// Skip past "="
248+
positionMediaType += 1;
249+
}
250+
251+
let parameterValue = "";
252+
253+
if (inputMediaType[positionMediaType] === '"') {
254+
[parameterValue, positionMediaType] = collectAnHTTPQuotedString(
255+
inputMediaType,
256+
positionMediaType
257+
);
258+
259+
while (
260+
positionMediaType < inputMediaType.length &&
261+
inputMediaType[positionMediaType] !== ";"
262+
) {
263+
positionMediaType += 1;
264+
}
265+
} else {
266+
while (
267+
positionMediaType < inputMediaType.length &&
268+
inputMediaType[positionMediaType] !== ";"
269+
) {
270+
parameterValue += inputMediaType[positionMediaType];
271+
positionMediaType += 1;
272+
}
273+
274+
parameterValue = removeTrailingHTTPWhitespace(parameterValue);
275+
276+
if (parameterValue === "") {
277+
// eslint-disable-next-line no-continue
278+
continue;
279+
}
280+
}
281+
282+
if (
283+
parameterName.length > 0 &&
284+
solelyContainsHTTPTokenCodePoints(parameterName) &&
285+
soleyContainsHTTPQuotedStringTokenCodePoints(parameterValue) &&
286+
!result.parameters.has(parameterName)
287+
) {
288+
result.parameters.set(parameterName, parameterValue);
289+
}
104290
}
105291

106-
return { mimeType: mimeTypeRecord, body };
292+
return result;
107293
}

src/utils.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import path from "path";
22
import urlUtils from "url";
33

44
import sourceMap from "source-map";
5-
65
import { decode } from "iconv-lite";
7-
import { urlToRequest } from "loader-utils";
86

97
import parseDataURL from "./parse-data-url";
108
import labelsToNames from "./labels-to-names";
@@ -97,15 +95,13 @@ function getSourceMappingURL(code) {
9795
};
9896
}
9997

100-
function getAbsolutePath(context, url, sourceRoot) {
101-
const request = urlToRequest(url, true);
102-
98+
function getAbsolutePath(context, request, sourceRoot) {
10399
if (sourceRoot) {
104100
if (path.isAbsolute(sourceRoot)) {
105101
return path.join(sourceRoot, request);
106102
}
107103

108-
return path.join(context, urlToRequest(sourceRoot, true), request);
104+
return path.join(context, sourceRoot, request);
109105
}
110106

111107
return path.join(context, request);
@@ -115,10 +111,12 @@ function fetchFromDataURL(loaderContext, sourceURL) {
115111
const dataURL = parseDataURL(sourceURL);
116112

117113
if (dataURL) {
118-
dataURL.encodingName =
119-
labelToName(dataURL.mimeType.parameters.get("charset")) || "UTF-8";
114+
// https://tools.ietf.org/html/rfc4627
115+
// JSON text SHALL be encoded in Unicode. The default encoding is UTF-8.
116+
const encodingName =
117+
labelToName(dataURL.parameters.get("charset")) || "UTF-8";
120118

121-
return decode(dataURL.body, dataURL.encodingName);
119+
return decode(dataURL.body, encodingName);
122120
}
123121

124122
throw new Error(`Failed to parse source map from "data" URL: ${sourceURL}`);

0 commit comments

Comments
 (0)