diff --git a/README.md b/README.md index 9f1e8f6..ede88d2 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,155 @@ Be mindful in setting [include](https://webpack.js.org/configuration/module/#rul And run `webpack` via your preferred method. +## Options + +### `brokenMapUrlReportType` + +Type: `String` +Default: `warning` + +Type of error message, when the map failed to load. + +Possible values: + +- `ignore` +- `warning` +- `error` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.js$/, + enforce: 'pre', + use: [ + { + loader: 'source-map-loader', + options: { + brokenMapUrlReportType: 'ignore', + }, + }, + ], + }, + ], + }, +}; +``` + +### `brokenMapParseReportType` + +Type: `String` +Default: `warning` + +Type of error message, when the card is received, but cannot be correctly parsed. + +Possible values: + +- `ignore` +- `warning` +- `error` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.js$/, + enforce: 'pre', + use: [ + { + loader: 'source-map-loader', + options: { + brokenMapParseReportType: 'ignore', + }, + }, + ], + }, + ], + }, +}; +``` + +### `brokenSourceUrlReportType` + +Type: `String` +Default: `warning` + +Type of error message, when the source (from `map.sources`) failed to load. + +Possible values: + +- `ignore` +- `warning` +- `error` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.js$/, + enforce: 'pre', + use: [ + { + loader: 'source-map-loader', + options: { + brokenSourceUrlReportType: 'ignore', + }, + }, + ], + }, + ], + }, +}; +``` + +### `unresolveSourceFetcher` + +Type: `Function` +Default: `undefined` + +The option allows you to fetching the remote content. + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.js$/, + enforce: 'pre', + use: [ + { + loader: 'source-map-loader', + options: { + async unresolveSourceFetcher(url) { + if (/^https?:\/\//i.test(url)) { + const response = await fetch(url); + const result = await response.text(); + + return result; + } + + throw new Error(`${url} is not supported`); + }, + }, + }, + ], + }, + ], + }, +}; +``` + ## Contributing Please take a moment to read our contributing guidelines if you haven't yet done so. diff --git a/package-lock.json b/package-lock.json index 79a83e1..e4e0c79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12078,6 +12078,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index d6ba112..fac45d9 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "jest": "^26.0.1", "lint-staged": "^10.2.2", "memfs": "^3.1.2", + "node-fetch": "^2.6.0", "npm-run-all": "^4.1.5", "prettier": "^2.0.5", "standard-version": "^8.0.0", diff --git a/src/index.js b/src/index.js index 8c7f703..17f9894 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,12 @@ import validateOptions from 'schema-utils'; import { getOptions } from 'loader-utils'; import schema from './options.json'; -import { getSourceMappingURL, fetchFromURL, flattenSourceMap } from './utils'; +import { + getSourceMappingURL, + fetchFromURL, + flattenSourceMap, + getErrorReporter, +} from './utils'; export default async function loader(input, inputMap) { const options = getOptions(this); @@ -34,10 +39,17 @@ export default async function loader(input, inputMap) { ({ sourceURL, sourceContent } = await fetchFromURL( this, this.context, - sourceMappingURL + sourceMappingURL, + '', + false, + options.unresolveSourceFetcher )); } catch (error) { - this.emitWarning(error); + const brokenMapUrlReporter = getErrorReporter( + this, + options.brokenMapUrlReportType + ); + brokenMapUrlReporter(error); callback(null, input, inputMap); @@ -53,7 +65,11 @@ export default async function loader(input, inputMap) { try { map = JSON.parse(sourceContent.replace(/^\)\]\}'/, '')); } catch (parseError) { - this.emitWarning( + const brokenMapParseReporter = getErrorReporter( + this, + options.brokenMapParseReportType + ); + brokenMapParseReporter( new Error(`Cannot parse source map from '${sourceURL}': ${parseError}`) ); @@ -88,10 +104,15 @@ export default async function loader(input, inputMap) { context, source, map.sourceRoot, - skipReading + skipReading, + options.unresolveSourceFetcher )); } catch (error) { - this.emitWarning(error); + const brokenSourceUrlReporter = getErrorReporter( + this, + options.brokenSourceUrlReportType + ); + brokenSourceUrlReporter(error); sourceURL = source; } diff --git a/src/options.json b/src/options.json index 97ea2a4..c149cac 100644 --- a/src/options.json +++ b/src/options.json @@ -1,4 +1,18 @@ { "type": "object", + "properties": { + "brokenMapUrlReportType": { + "enum": ["ignore", "warning", "error"] + }, + "brokenMapParseReportType": { + "enum": ["ignore", "warning", "error"] + }, + "brokenSourceUrlReportType": { + "enum": ["ignore", "warning", "error"] + }, + "unresolveSourceFetcher": { + "instanceof": "Function" + } + }, "additionalProperties": false } diff --git a/src/utils.js b/src/utils.js index aa1336c..6801e07 100644 --- a/src/utils.js +++ b/src/utils.js @@ -60,9 +60,7 @@ async function flattenSourceMap(map) { }, }; - if (source) { - generatedMap.addMapping(mappings); - } + generatedMap.addMapping(mappings); }); return generatedMap.toJSON(); @@ -80,7 +78,7 @@ function getSourceMappingURL(code) { } return { - sourceMappingURL: match ? match[1] || match[2] || '' : null, + sourceMappingURL: match ? match[1] || match[2] : null, replacementString: match ? match[0] : null, }; } @@ -137,7 +135,8 @@ async function fetchFromURL( context, url, sourceRoot, - skipReading = false + skipReading, + unresolveSourceFetcher ) { // 1. It's an absolute url and it is not `windows` path like `C:\dir\file` if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !path.win32.isAbsolute(url)) { @@ -163,6 +162,16 @@ async function fetchFromURL( return { sourceURL, sourceContent }; } + if (skipReading) { + return { sourceURL: url, sourceContent: '' }; + } + + if (unresolveSourceFetcher) { + const sourceContent = await unresolveSourceFetcher(url); + + return { sourceURL: url, sourceContent }; + } + throw new Error(`Absolute '${url}' URL is not supported`); } @@ -196,4 +205,20 @@ async function fetchFromURL( return { sourceURL, sourceContent }; } -export { getSourceMappingURL, fetchFromURL, flattenSourceMap }; +function getErrorReporter(loaderContext, typeReport) { + switch (typeReport) { + case 'error': + return loaderContext.emitError; + case 'ignore': + return function ignore() {}; + default: + return loaderContext.emitWarning; + } +} + +export { + getSourceMappingURL, + fetchFromURL, + flattenSourceMap, + getErrorReporter, +}; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index 7ece89b..ea6e0c2 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -1,5 +1,70 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`source-map-loader should error on broken map url error SourceMap: css 1`] = ` +"without SourceMap +// @sourceMappingURL=data:application/source-map;base64,invalid/base64= +// comment" +`; + +exports[`source-map-loader should error on broken map url error SourceMap: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Can not parse inline source map: data:application/source-map;base64,invalid/base64=", +] +`; + +exports[`source-map-loader should error on broken map url error SourceMap: warnings 1`] = `Array []`; + +exports[`source-map-loader should error on broken source in map.sources: css 1`] = ` +"// Skip SourcesContent in SourceMap +// comment +" +`; + +exports[`source-map-loader should error on broken source in map.sources: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Cannot read '/test/fixtures/unresolved-file.js' file: Error: ENOENT: no such file or directory, open '/test/fixtures/unresolved-file.js'", +] +`; + +exports[`source-map-loader should error on broken source in map.sources: warnings 1`] = `Array []`; + +exports[`source-map-loader should error on map parse error: css 1`] = ` +"with SourceMap +//#sourceMappingURL=invalid-source-map.map +// comment" +`; + +exports[`source-map-loader should error on map parse error: errors 1`] = ` +Array [ + "ModuleError: Module Error (from \`replaced original path\`): +Cannot parse source map from '/test/fixtures/invalid-source-map.map': SyntaxError: Unexpected string in JSON at position 102", +] +`; + +exports[`source-map-loader should error on map parse error: warnings 1`] = `Array []`; + +exports[`source-map-loader should ignore report on broken map url error SourceMap: css 1`] = ` +"without SourceMap +// @sourceMappingURL=data:application/source-map;base64,invalid/base64= +// comment" +`; + +exports[`source-map-loader should ignore report on broken map url error SourceMap: errors 1`] = `Array []`; + +exports[`source-map-loader should ignore report on broken map url error SourceMap: warnings 1`] = `Array []`; + +exports[`source-map-loader should ignore report on map parse error: css 1`] = ` +"with SourceMap +//#sourceMappingURL=invalid-source-map.map +// comment" +`; + +exports[`source-map-loader should ignore report on map parse error: errors 1`] = `Array []`; + +exports[`source-map-loader should ignore report on map parse error: warnings 1`] = `Array []`; + exports[`source-map-loader should leave normal files untouched: css 1`] = `"without SourceMap"`; exports[`source-map-loader should leave normal files untouched: errors 1`] = `Array []`; @@ -17,6 +82,34 @@ exports[`source-map-loader should leave normal files with fake source-map untouc exports[`source-map-loader should leave normal files with fake source-map untouched: warnings 1`] = `Array []`; +exports[`source-map-loader should not resolve SourceMap.sources to http: css 1`] = ` +"// console.log('with SourceMap') +" +`; + +exports[`source-map-loader should not resolve SourceMap.sources to http: errors 1`] = `Array []`; + +exports[`source-map-loader should not resolve SourceMap.sources to http: map 1`] = ` +Object { + "file": "sources-http.js", + "mappings": "AAAA", + "sources": Array [ + "https://unresolved1.com/", + "https://unresolved1.com/", + ], + "version": 3, +} +`; + +exports[`source-map-loader should not resolve SourceMap.sources to http: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +body used already for: ", + "ModuleWarning: Module Warning (from \`replaced original path\`): +body used already for: ", +] +`; + exports[`source-map-loader should process css sourceMap: css 1`] = ` "* { box-sizing: border-box; } @@ -113,12 +206,7 @@ anInvalidDirective = \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\ } `; -exports[`source-map-loader should process css sourceMap: warnings 1`] = ` -Array [ - "ModuleWarning: Module Warning (from \`replaced original path\`): -Absolute 'webpack:///./src/app/app.scss' URL is not supported", -] -`; +exports[`source-map-loader should process css sourceMap: warnings 1`] = `Array []`; exports[`source-map-loader should process css sourceMap: warnings 2`] = ` Array [ @@ -295,6 +383,31 @@ Absolute 'ftp://exampleurl.com' URL is not supported", ] `; +exports[`source-map-loader should resolve SourceMap.sources to http: css 1`] = ` +"// console.log('with SourceMap') +" +`; + +exports[`source-map-loader should resolve SourceMap.sources to http: errors 1`] = `Array []`; + +exports[`source-map-loader should resolve SourceMap.sources to http: map 1`] = ` +Object { + "file": "sources-http.js", + "mappings": "AAAA", + "sources": Array [ + "https://github2.com/", + "https://github.com/", + ], + "sourcesContent": Array [ + "static content", + "downloaded content", + ], + "version": 3, +} +`; + +exports[`source-map-loader should resolve SourceMap.sources to http: warnings 1`] = `Array []`; + exports[`source-map-loader should skip invalid base64 SourceMap: css 1`] = ` "without SourceMap // @sourceMappingURL=data:application/source-map;base64,\\"something invalid\\" @@ -372,10 +485,12 @@ exports[`source-map-loader should support file protocol path: map 1`] = ` Object { "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA", "sources": Array [ + "/test/fixtures/normal-file.js - (normalized for test)", "/test/fixtures/normal-file.js - (normalized for test)", "/test/fixtures/normal-file2.js - (normalized for test)", ], "sourcesContent": Array [ + "static content", "without SourceMap", "// without SourceMap anInvalidDirective = \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\\"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+\\" */\\"; @@ -549,6 +664,36 @@ Object { exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: warnings 1`] = `Array []`; +exports[`source-map-loader should throw error for SourceMap.sources to http: css 1`] = ` +"// console.log('with SourceMap') +" +`; + +exports[`source-map-loader should throw error for SourceMap.sources to http: errors 1`] = `Array []`; + +exports[`source-map-loader should throw error for SourceMap.sources to http: map 1`] = ` +Object { + "file": "sources-http.js", + "mappings": "AAAA", + "sources": Array [ + "https://github2.com/", + "https://github.com/", + ], + "sourcesContent": Array [ + "static content", + "", + ], + "version": 3, +} +`; + +exports[`source-map-loader should throw error for SourceMap.sources to http: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +https://github.com/ is not supported", +] +`; + exports[`source-map-loader should use last SourceMap directive: css 1`] = ` "with SourceMap anInvalidDirective = \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\\"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+\\" */\\"; @@ -642,3 +787,33 @@ Array [ Cannot read '/test/fixtures/missing-source-map2.txt' file: Error: ENOENT: no such file or directory, open '/test/fixtures/missing-source-map2.txt'", ] `; + +exports[`source-map-loader should warning on broken map url error SourceMap: css 1`] = ` +"without SourceMap +// @sourceMappingURL=data:application/source-map;base64,invalid/base64= +// comment" +`; + +exports[`source-map-loader should warning on broken map url error SourceMap: errors 1`] = `Array []`; + +exports[`source-map-loader should warning on broken map url error SourceMap: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +Can not parse inline source map: data:application/source-map;base64,invalid/base64=", +] +`; + +exports[`source-map-loader should warning on map parse error: css 1`] = ` +"with SourceMap +//#sourceMappingURL=invalid-source-map.map +// comment" +`; + +exports[`source-map-loader should warning on map parse error: errors 1`] = `Array []`; + +exports[`source-map-loader should warning on map parse error: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +Cannot parse source map from '/test/fixtures/invalid-source-map.map': SyntaxError: Unexpected string in JSON at position 102", +] +`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 0577472..3f59a4b 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -1,49 +1,182 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`validate options should throw an error on the "brokenMapParseReportType" option with "() => 'test;'" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapParseReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapParseReportType" option with "[]" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapParseReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapParseReportType" option with "{}" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapParseReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapParseReportType" option with "1" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapParseReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapParseReportType" option with "false" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapParseReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapParseReportType" option with "true" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapParseReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapUrlReportType" option with "() => 'test;'" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapUrlReportType" option with "[]" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapUrlReportType" option with "{}" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapUrlReportType" option with "1" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapUrlReportType" option with "false" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenMapUrlReportType" option with "true" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenMapUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenSourceUrlReportType" option with "() => 'test;'" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenSourceUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenSourceUrlReportType" option with "[]" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenSourceUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenSourceUrlReportType" option with "{}" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenSourceUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenSourceUrlReportType" option with "1" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenSourceUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenSourceUrlReportType" option with "false" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenSourceUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + +exports[`validate options should throw an error on the "brokenSourceUrlReportType" option with "true" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.brokenSourceUrlReportType should be one of these: + \\"ignore\\" | \\"warning\\" | \\"error\\"" +`; + exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" `; exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = ` "Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'unknown'. These properties are valid: - object {}" + object { brokenMapUrlReportType?, brokenMapParseReportType?, brokenSourceUrlReportType?, unresolveSourceFetcher? }" +`; + +exports[`validate options should throw an error on the "unresolveSourceFetcher" option with "[]" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.unresolveSourceFetcher should be an instance of function." +`; + +exports[`validate options should throw an error on the "unresolveSourceFetcher" option with "{}" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.unresolveSourceFetcher should be an instance of function." +`; + +exports[`validate options should throw an error on the "unresolveSourceFetcher" option with "1" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.unresolveSourceFetcher should be an instance of function." +`; + +exports[`validate options should throw an error on the "unresolveSourceFetcher" option with "test" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.unresolveSourceFetcher should be an instance of function." +`; + +exports[`validate options should throw an error on the "unresolveSourceFetcher" option with "true" value 1`] = ` +"Invalid options object. Source Map Loader has been initialized using an options object that does not match the API schema. + - options.unresolveSourceFetcher should be an instance of function." `; diff --git a/test/fixtures/fetch/sources-http.js b/test/fixtures/fetch/sources-http.js new file mode 100644 index 0000000..af5eb0a --- /dev/null +++ b/test/fixtures/fetch/sources-http.js @@ -0,0 +1,2 @@ +// console.log('with SourceMap') +//#sourceMappingURL=sources-http.js.map diff --git a/test/fixtures/fetch/sources-http.js.map b/test/fixtures/fetch/sources-http.js.map new file mode 100644 index 0000000..e8aa81d --- /dev/null +++ b/test/fixtures/fetch/sources-http.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sources-http.js","sources":["https://github2.com/", "https://github.com/"],"sourcesContent":["static content"],"mappings":"AAAA"} diff --git a/test/fixtures/fetch/unresolved-sources-http.js b/test/fixtures/fetch/unresolved-sources-http.js new file mode 100644 index 0000000..c7efcbf --- /dev/null +++ b/test/fixtures/fetch/unresolved-sources-http.js @@ -0,0 +1,2 @@ +// console.log('with SourceMap') +//#sourceMappingURL=unresolved-sources-http.js.map diff --git a/test/fixtures/fetch/unresolved-sources-http.js.map b/test/fixtures/fetch/unresolved-sources-http.js.map new file mode 100644 index 0000000..c9c9dfd --- /dev/null +++ b/test/fixtures/fetch/unresolved-sources-http.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sources-http.js","sources":["https://unresolved1.com/", "https://unresolved1.com/"],"mappings":"AAAA"} diff --git a/test/loader.test.js b/test/loader.test.js index 338ec03..2df12ed 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -11,6 +11,12 @@ import { readAsset, } from './helpers'; +/* eslint-disable */ +jest.mock('node-fetch'); + +import fetch from 'node-fetch'; +/* eslint-enable */ + const isWin = process.platform === 'win32'; describe('source-map-loader', () => { @@ -95,6 +101,73 @@ describe('source-map-loader', () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); + it('should resolve SourceMap.sources to http', async () => { + const { Response } = jest.requireActual('node-fetch'); + fetch.mockReturnValue(Promise.resolve(new Response('downloaded content'))); + + const currentDirPath = path.join(__dirname, 'fixtures', 'fetch'); + + const testId = path.join(currentDirPath, 'sources-http.js'); + const compiler = getCompiler(testId, { + async unresolveSourceFetcher(url) { + if (/^https?:\/\//i.test(url)) { + const response = await fetch(url); + return response.text(); + } + + throw new Error(`${url} is not supported`); + }, + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should throw error for SourceMap.sources to http', async () => { + const currentDirPath = path.join(__dirname, 'fixtures', 'fetch'); + + const testId = path.join(currentDirPath, 'sources-http.js'); + const compiler = getCompiler(testId, { + async unresolveSourceFetcher(url) { + throw new Error(`${url} is not supported`); + }, + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should not resolve SourceMap.sources to http', async () => { + const currentDirPath = path.join(__dirname, 'fixtures', 'fetch'); + + const testId = path.join(currentDirPath, 'unresolved-sources-http.js'); + const compiler = getCompiler(testId, { + async unresolveSourceFetcher(url) { + if (/^https?:\/\//i.test(url)) { + const response = await fetch(url); + return response.text(); + } + + throw new Error(`${url} is not supported`); + }, + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + it('should reject http SourceMaps', async () => { const testId = 'http-source-map.js'; const compiler = getCompiler(testId); @@ -127,11 +200,15 @@ describe('source-map-loader', () => { const rawSourceMap = { version: 3, sources: [ + `file://${path + .resolve(__dirname, 'fixtures', 'normal-file.js') + .replace(/\\/g, '/')}`, 'normal-file.js', `file://${path .resolve(__dirname, 'fixtures', 'normal-file2.js') .replace(/\\/g, '/')}`, ], + sourcesContent: ['static content'], mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA', }; fs.writeFileSync(sourceMapPath, JSON.stringify(rawSourceMap)); @@ -576,4 +653,102 @@ describe('source-map-loader', () => { expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); }); + + it('should warning on broken map url error SourceMap', async () => { + const testId = 'invalid-inline-source-map2.js'; + const compiler = getCompiler(testId, { + brokenMapUrlReportType: 'warning', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should error on broken map url error SourceMap', async () => { + const testId = 'invalid-inline-source-map2.js'; + const compiler = getCompiler(testId, { + brokenMapUrlReportType: 'error', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should ignore report on broken map url error SourceMap', async () => { + const testId = 'invalid-inline-source-map2.js'; + const compiler = getCompiler(testId, { + brokenMapUrlReportType: 'ignore', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should warning on map parse error', async () => { + const testId = 'invalid-source-map.js'; + const compiler = getCompiler(testId, { + brokenMapParseReportType: 'warning', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should error on map parse error', async () => { + const testId = 'invalid-source-map.js'; + const compiler = getCompiler(testId, { + brokenMapParseReportType: 'error', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should ignore report on map parse error', async () => { + const testId = 'invalid-source-map.js'; + const compiler = getCompiler(testId, { + brokenMapParseReportType: 'ignore', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should error on broken source in map.sources', async () => { + const testId = 'skip-sourcesContent.js'; + const compiler = getCompiler(testId, { + brokenSourceUrlReportType: 'error', + }); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeDefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); }); diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 1a307e6..80bf829 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -2,6 +2,22 @@ import { getCompiler, compile } from './helpers'; describe('validate options', () => { const tests = { + brokenMapUrlReportType: { + success: ['warning', 'ignore', 'error'], + failure: [1, () => 'test;', [], {}, true, false], + }, + brokenMapParseReportType: { + success: ['warning', 'ignore', 'error'], + failure: [1, () => 'test;', [], {}, true, false], + }, + brokenSourceUrlReportType: { + success: ['warning', 'ignore', 'error'], + failure: [1, () => 'test;', [], {}, true, false], + }, + unresolveSourceFetcher: { + success: [() => 'test;'], + failure: [1, 'test', true, [], {}], + }, unknown: { success: [], failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }],