Skip to content

Feat absolute path for sources #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 22, 2020
Merged
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"dependencies": {
"data-urls": "^2.0.0",
"loader-utils": "^2.0.0",
"neo-async": "^2.6.1",
"schema-utils": "^2.6.6",
"source-map": "^0.6.0",
"whatwg-encoding": "^1.0.5"
Expand Down
157 changes: 99 additions & 58 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
*/
import fs from 'fs';
import path from 'path';
import urlUtils from 'url';

import validateOptions from 'schema-utils';
import async from 'neo-async';
import parseDataURL from 'data-urls';

import { SourceMapConsumer } from 'source-map';
import { labelToName, decode } from 'whatwg-encoding';
import { getOptions, urlToRequest } from 'loader-utils';

import flattenSourceMap from './utils/flatten';

import schema from './options.json';
import {
flattenSourceMap,
readFile,
getContentFromSourcesContent,
isUrlRequest,
} from './utils';

// Matches only the last occurrence of sourceMappingURL
const baseRegex =
Expand All @@ -40,7 +46,7 @@ export default function loader(input, inputMap) {
return;
}

const [, url] = match;
let [, url] = match;

const dataURL = parseDataURL(url);

Expand Down Expand Up @@ -78,6 +84,28 @@ export default function loader(input, inputMap) {
return;
}

if (!isUrlRequest(url)) {
const { protocol } = urlUtils.parse(url);

if (protocol !== 'file:') {
emitWarning(`URL scheme not supported: ${protocol}`);

callback(null, input, inputMap);

return;
}

try {
url = urlUtils.fileURLToPath(url);
} catch (error) {
emitWarning(error);

callback(null, input, inputMap);

return;
}
}

resolve(context, urlToRequest(url, true), (resolveError, result) => {
if (resolveError) {
emitWarning(`Cannot find SourceMap '${url}': ${resolveError}`);
Expand Down Expand Up @@ -121,70 +149,83 @@ export default function loader(input, inputMap) {
map = await flattenSourceMap(map);
}

if (map.sourcesContent && map.sourcesContent.length >= map.sources.length) {
callback(null, input.replace(match[0], ''), map);

return;
}

const sourcePrefix = map.sourceRoot ? `${map.sourceRoot}${path.sep}` : '';
const mapConsumer = await new SourceMapConsumer(map);

// eslint-disable-next-line no-param-reassign
map.sources = map.sources.map((s) => sourcePrefix + s);
let resolvedSources;

// eslint-disable-next-line no-param-reassign
delete map.sourceRoot;

const missingSources = map.sourcesContent
? map.sources.slice(map.sourcesContent.length)
: map.sources;
try {
resolvedSources = await Promise.all(
map.sources.map(async (source) => {
const fullPath = map.sourceRoot
? `${map.sourceRoot}${path.sep}${source}`
: source;

const originalData = getContentFromSourcesContent(
mapConsumer,
source
);

if (path.isAbsolute(fullPath)) {
return originalData
? { source: fullPath, content: originalData }
: readFile(fullPath, 'utf-8', emitWarning);
}

async.map(
missingSources,
// eslint-disable-next-line no-shadow
(source, callback) => {
resolve(context, urlToRequest(source, true), (resolveError, result) => {
if (resolveError) {
emitWarning(`Cannot find source file '${source}': ${resolveError}`);
return new Promise((promiseResolve) => {
resolve(
context,
urlToRequest(fullPath, true),
(resolveError, result) => {
if (resolveError) {
emitWarning(
`Cannot find source file '${source}': ${resolveError}`
);

return originalData
? promiseResolve({
source: fullPath,
content: originalData,
})
: promiseResolve({ source: fullPath, content: null });
}

return originalData
? promiseResolve({ source: result, content: originalData })
: promiseResolve(readFile(result, 'utf-8', emitWarning));
}
);
});
})
);
} catch (error) {
emitWarning(error);

callback(null, null);
callback(null, input, inputMap);
}

return;
}
const resultMap = { ...map };
resultMap.sources = [];
resultMap.sourcesContent = [];

addDependency(result);
delete resultMap.sourceRoot;

fs.readFile(result, 'utf-8', (readFileError, content) => {
if (readFileError) {
emitWarning(
`Cannot open source file '${result}': ${readFileError}`
);
resolvedSources.forEach((res) => {
// eslint-disable-next-line no-param-reassign
resultMap.sources.push(path.normalize(res.source));
resultMap.sourcesContent.push(res.content);

callback(null, null);
if (res.source) {
addDependency(res.source);
}
});

return;
}
const sourcesContentIsEmpty =
resultMap.sourcesContent.filter((entry) => !!entry).length === 0;

callback(null, { source: result, content });
});
});
},
(err, info) => {
// eslint-disable-next-line no-param-reassign
map.sourcesContent = map.sourcesContent || [];

info.forEach((res) => {
if (res) {
// eslint-disable-next-line no-param-reassign
map.sources[map.sourcesContent.length] = res.source;
map.sourcesContent.push(res.content);
} else {
map.sourcesContent.push(null);
}
});
if (sourcesContentIsEmpty) {
delete resultMap.sourcesContent;
}

processMap(map, context, callback);
}
);
callback(null, input.replace(match[0], ''), resultMap);
}
}
46 changes: 44 additions & 2 deletions src/utils/flatten.js → src/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import fs from 'fs';
import path from 'path';

import sourceMap from 'source-map';

async function FlattenSourceMap(map) {
async function flattenSourceMap(map) {
const consumer = await new sourceMap.SourceMapConsumer(map);
let generatedMap;

Expand Down Expand Up @@ -43,4 +46,43 @@ async function FlattenSourceMap(map) {
return generatedMap.toJSON();
}

module.exports = FlattenSourceMap;
function readFile(fullPath, charset, emitWarning) {
return new Promise((resolve) => {
fs.readFile(fullPath, charset, (readFileError, content) => {
if (readFileError) {
emitWarning(`Cannot open source file '${fullPath}': ${readFileError}`);

resolve({ source: null, content: null });
}

resolve({ source: fullPath, content });
});
});
}

function getContentFromSourcesContent(consumer, source) {
return consumer.sourceContentFor(source, true);
}

function isUrlRequest(url) {
// An URL is not an request if

// 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)) {
return false;
}

// 2. It's a protocol-relative
if (/^\/\//.test(url)) {
return false;
}

return true;
}

export {
flattenSourceMap,
readFile,
getContentFromSourcesContent,
isUrlRequest,
};
50 changes: 41 additions & 9 deletions test/__snapshots__/loader.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ Object {
}
`;

exports[`source-map-loader should process external SourceMaps: warnings 1`] = `Array []`;
exports[`source-map-loader should process external SourceMaps: warnings 1`] = `
Array [
"ModuleWarning: Module Warning (from \`replaced original path\`):
(Emitted value instead of an instance of Error) Cannot find source file 'external-source-map.txt': Error: Can't resolve './external-source-map.txt' in '/test/fixtures'",
]
`;

exports[`source-map-loader should process inlined SourceMaps with charset: css 1`] = `
"with SourceMap
Expand All @@ -64,7 +69,7 @@ Object {
"file": "charset-inline-source-map.js",
"mappings": "AAAA",
"sources": Array [
"charset-inline-source-map.txt",
"/test/fixtures/charset-inline-source-map.txt - (normalized for test)",
],
"sourcesContent": Array [
"with SourceMap",
Expand Down Expand Up @@ -96,7 +101,32 @@ Object {
}
`;

exports[`source-map-loader should process inlined SourceMaps: warnings 1`] = `Array []`;
exports[`source-map-loader should process inlined SourceMaps: warnings 1`] = `
Array [
"ModuleWarning: Module Warning (from \`replaced original path\`):
(Emitted value instead of an instance of Error) Cannot find source file 'inline-source-map.txt': Error: Can't resolve './inline-source-map.txt' in '/test/fixtures'",
]
`;

exports[`source-map-loader should reject http SourceMaps: css 1`] = `
"with SourceMap
//#sourceMappingURL=http://sampledomain.com/external-source-map2.map
// comment
"
`;

exports[`source-map-loader should reject http SourceMaps: errors 1`] = `Array []`;

exports[`source-map-loader should reject http SourceMaps: warnings 1`] = `
Array [
"ModuleWarning: Module Warning (from \`replaced original path\`):
(Emitted value instead of an instance of Error) URL scheme not supported: http:",
]
`;

exports[`source-map-loader should reject not exist file: SourceMaps: errors 1`] = `Array []`;

exports[`source-map-loader should reject not exist file: SourceMaps: warnings 1`] = `"TypeError [ERR_INVALID_FILE"`;

exports[`source-map-loader should skip invalid base64 SourceMap: css 1`] = `
"without SourceMap
Expand Down Expand Up @@ -133,7 +163,7 @@ Object {
exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: warnings 1`] = `Array []`;

exports[`source-map-loader should support indexed sourcemaps: css 1`] = `
"with SourceMap
"console.log('with SourceMap')
// Map taken from here
// https://github.com/mozilla/source-map/blob/master/test/util.js - indexedTestMapDifferentSourceRoots
"
Expand All @@ -147,7 +177,7 @@ Object {
"mappings": "CAAC,IAAI,IAAM,SAAU,GAClB,OAAO,IAAI;CCDb,IAAI,IAAM,SAAU,GAClB,OAAO",
"names": Array [],
"sources": Array [
"/the/root/nested1.js",
"/test/fixtures/indexed-sourcemap/nested1.js - (normalized for test)",
"/different/root/nested2.js",
],
"sourcesContent": Array [
Expand Down Expand Up @@ -210,7 +240,12 @@ Object {
}
`;

exports[`source-map-loader should use last SourceMap directive: warnings 1`] = `Array []`;
exports[`source-map-loader should use last SourceMap directive: warnings 1`] = `
Array [
"ModuleWarning: Module Warning (from \`replaced original path\`):
(Emitted value instead of an instance of Error) Cannot find source file 'inline-source-map.txt': Error: Can't resolve './inline-source-map.txt' in '/test/fixtures'",
]
`;

exports[`source-map-loader should warn on invalid SourceMap: css 1`] = `
"with SourceMap
Expand Down Expand Up @@ -271,9 +306,6 @@ Object {
"sources": Array [
"missing-source-map2.txt",
],
"sourcesContent": Array [
null,
],
"version": 3,
}
`;
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/charset-inline-source-map.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Content
3 changes: 3 additions & 0 deletions test/fixtures/file-source-map-windows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
with SourceMap
// #sourceMappingURL=file:///unresolvedPath/map.map
// comment
3 changes: 3 additions & 0 deletions test/fixtures/file-source-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
with SourceMap
// #sourceMappingURL=file://relative-sourceRoot-source-map.map
// comment
3 changes: 3 additions & 0 deletions test/fixtures/http-source-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
with SourceMap
//#sourceMappingURL=http://sampledomain.com/external-source-map2.map
// comment
2 changes: 1 addition & 1 deletion test/fixtures/indexed-sourcemap/file.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
with SourceMap
console.log('with SourceMap')
//#sourceMappingURL=file.js.map
// Map taken from here
// https://github.com/mozilla/source-map/blob/master/test/util.js - indexedTestMapDifferentSourceRoots
Loading