fix(compiler-cli): ensure source-maps can handle webpack:// protocol (#32912)
Webpack and other build tools sometimes inline the contents of the source files in their generated source-maps, and at the same time change the paths to be prefixed with a protocol, such as `webpack://`. This can confuse tools that need to read these paths, so now it is possible to provide a mapping to where these files originated. PR Close #32912
This commit is contained in:
parent
6abb8d0d91
commit
decd95e7f0
|
@ -35,7 +35,7 @@ export function renderSourceAndMap(
|
|||
{file: generatedPath, source: generatedPath, includeContent: true});
|
||||
|
||||
try {
|
||||
const loader = new SourceFileLoader(fs, logger);
|
||||
const loader = new SourceFileLoader(fs, logger, {});
|
||||
const generatedFile = loader.loadSourceFile(
|
||||
generatedPath, generatedContent, {map: generatedMap, mapPath: generatedMapPath});
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import {Logger} from '../../logging';
|
|||
import {RawSourceMap} from './raw_source_map';
|
||||
import {SourceFile} from './source_file';
|
||||
|
||||
const SCHEME_MATCHER = /^([a-z][a-z0-9.-]*):\/\//i;
|
||||
|
||||
/**
|
||||
* This class can be used to load a source file, its associated source map and any upstream sources.
|
||||
*
|
||||
|
@ -25,7 +27,10 @@ import {SourceFile} from './source_file';
|
|||
export class SourceFileLoader {
|
||||
private currentPaths: AbsoluteFsPath[] = [];
|
||||
|
||||
constructor(private fs: FileSystem, private logger: Logger) {}
|
||||
constructor(
|
||||
private fs: FileSystem, private logger: Logger,
|
||||
/** A map of URL schemes to base paths. The scheme name should be lowercase. */
|
||||
private schemeMap: Record<string, AbsoluteFsPath>) {}
|
||||
|
||||
/**
|
||||
* Load a source file, compute its source map, and recursively load any referenced source files.
|
||||
|
@ -128,9 +133,10 @@ export class SourceFileLoader {
|
|||
* source file and its associated source map.
|
||||
*/
|
||||
private processSources(basePath: AbsoluteFsPath, map: RawSourceMap): (SourceFile|null)[] {
|
||||
const sourceRoot = this.fs.resolve(this.fs.dirname(basePath), map.sourceRoot || '');
|
||||
const sourceRoot = this.fs.resolve(
|
||||
this.fs.dirname(basePath), this.replaceSchemeWithPath(map.sourceRoot || ''));
|
||||
return map.sources.map((source, index) => {
|
||||
const path = this.fs.resolve(sourceRoot, source);
|
||||
const path = this.fs.resolve(sourceRoot, this.replaceSchemeWithPath(source));
|
||||
const content = map.sourcesContent && map.sourcesContent[index] || null;
|
||||
return this.loadSourceFile(path, content, null);
|
||||
});
|
||||
|
@ -168,6 +174,19 @@ export class SourceFileLoader {
|
|||
}
|
||||
this.currentPaths.push(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace any matched URL schemes with their corresponding path held in the schemeMap.
|
||||
*
|
||||
* Some build tools replace real file paths with scheme prefixed paths - e.g. `webpack://`.
|
||||
* We use the `schemeMap` passed to this class to convert such paths to "real" file paths.
|
||||
* In some cases, this is not possible, since the file was actually synthesized by the build tool.
|
||||
* But the end result is better than prefixing the sourceRoot in front of the scheme.
|
||||
*/
|
||||
private replaceSchemeWithPath(path: string): string {
|
||||
return path.replace(
|
||||
SCHEME_MATCHER, (_: string, scheme: string) => this.schemeMap[scheme.toLowerCase()] || '');
|
||||
}
|
||||
}
|
||||
|
||||
/** A small helper structure that is returned from `loadSourceMap()`. */
|
||||
|
|
|
@ -23,7 +23,7 @@ runInEachFileSystem(() => {
|
|||
fs = getFileSystem();
|
||||
logger = new MockLogger();
|
||||
_ = absoluteFrom;
|
||||
registry = new SourceFileLoader(fs, logger);
|
||||
registry = new SourceFileLoader(fs, logger, {webpack: _('/foo')});
|
||||
});
|
||||
|
||||
describe('loadSourceFile', () => {
|
||||
|
@ -279,6 +279,59 @@ runInEachFileSystem(() => {
|
|||
|
||||
expect(() => registry.loadSourceFile(aPath)).not.toThrow();
|
||||
});
|
||||
|
||||
for (const {scheme, mappedPath} of
|
||||
[{scheme: 'WEBPACK://', mappedPath: '/foo/src/index.ts'},
|
||||
{scheme: 'webpack://', mappedPath: '/foo/src/index.ts'},
|
||||
{scheme: 'missing://', mappedPath: '/src/index.ts'},
|
||||
]) {
|
||||
it(`should handle source paths that are protocol mapped [scheme:"${scheme}"]`, () => {
|
||||
fs.ensureDir(_('/foo/src'));
|
||||
|
||||
const indexSourceMap = createRawSourceMap({
|
||||
file: 'index.js',
|
||||
sources: [`${scheme}/src/index.ts`],
|
||||
'sourcesContent': ['original content']
|
||||
});
|
||||
fs.writeFile(_('/foo/src/index.js.map'), JSON.stringify(indexSourceMap));
|
||||
const sourceFile = registry.loadSourceFile(_('/foo/src/index.js'), 'generated content');
|
||||
if (sourceFile === null) {
|
||||
return fail('Expected source file to be defined');
|
||||
}
|
||||
const originalSource = sourceFile.sources[0];
|
||||
if (originalSource === null) {
|
||||
return fail('Expected source file to be defined');
|
||||
}
|
||||
expect(originalSource.contents).toEqual('original content');
|
||||
expect(originalSource.sourcePath).toEqual(_(mappedPath));
|
||||
expect(originalSource.rawMap).toEqual(null);
|
||||
expect(originalSource.sources).toEqual([]);
|
||||
});
|
||||
|
||||
it(`should handle source roots that are protocol mapped [scheme:"${scheme}"]`, () => {
|
||||
fs.ensureDir(_('/foo/src'));
|
||||
|
||||
const indexSourceMap = createRawSourceMap({
|
||||
file: 'index.js',
|
||||
sources: ['index.ts'],
|
||||
'sourcesContent': ['original content'],
|
||||
sourceRoot: `${scheme}/src`,
|
||||
});
|
||||
fs.writeFile(_('/foo/src/index.js.map'), JSON.stringify(indexSourceMap));
|
||||
const sourceFile = registry.loadSourceFile(_('/foo/src/index.js'), 'generated content');
|
||||
if (sourceFile === null) {
|
||||
return fail('Expected source file to be defined');
|
||||
}
|
||||
const originalSource = sourceFile.sources[0];
|
||||
if (originalSource === null) {
|
||||
return fail('Expected source file to be defined');
|
||||
}
|
||||
expect(originalSource.contents).toEqual('original content');
|
||||
expect(originalSource.sourcePath).toEqual(_(mappedPath));
|
||||
expect(originalSource.rawMap).toEqual(null);
|
||||
expect(originalSource.sources).toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue