2019-04-28 20:48:35 +01:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
|
*/
|
|
|
|
|
import {SourceMapConverter, commentRegex, fromJSON, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
|
|
|
|
import MagicString from 'magic-string';
|
|
|
|
|
import {RawSourceMap, SourceMapConsumer, SourceMapGenerator} from 'source-map';
|
|
|
|
|
import * as ts from 'typescript';
|
2019-06-06 20:22:32 +01:00
|
|
|
import {resolve, FileSystem, absoluteFromSourceFile, dirname, basename, absoluteFrom} from '../../../src/ngtsc/file_system';
|
2019-04-28 20:48:35 +01:00
|
|
|
import {Logger} from '../logging/logger';
|
|
|
|
|
import {FileToWrite} from './utils';
|
|
|
|
|
|
|
|
|
|
export interface SourceMapInfo {
|
|
|
|
|
source: string;
|
|
|
|
|
map: SourceMapConverter|null;
|
|
|
|
|
isInline: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the map from the source (note whether it is inline or external)
|
|
|
|
|
*/
|
|
|
|
|
export function extractSourceMap(
|
|
|
|
|
fs: FileSystem, logger: Logger, file: ts.SourceFile): SourceMapInfo {
|
|
|
|
|
const inline = commentRegex.test(file.text);
|
|
|
|
|
const external = mapFileCommentRegex.exec(file.text);
|
|
|
|
|
|
|
|
|
|
if (inline) {
|
|
|
|
|
const inlineSourceMap = fromSource(file.text);
|
|
|
|
|
return {
|
|
|
|
|
source: removeComments(file.text).replace(/\n\n$/, '\n'),
|
|
|
|
|
map: inlineSourceMap,
|
|
|
|
|
isInline: true,
|
|
|
|
|
};
|
|
|
|
|
} else if (external) {
|
|
|
|
|
let externalSourceMap: SourceMapConverter|null = null;
|
|
|
|
|
try {
|
|
|
|
|
const fileName = external[1] || external[2];
|
2019-06-06 20:22:32 +01:00
|
|
|
const filePath = resolve(dirname(absoluteFromSourceFile(file)), fileName);
|
2019-04-28 20:48:35 +01:00
|
|
|
const mappingFile = fs.readFile(filePath);
|
|
|
|
|
externalSourceMap = fromJSON(mappingFile);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e.code === 'ENOENT') {
|
|
|
|
|
logger.warn(
|
|
|
|
|
`The external map file specified in the source code comment "${e.path}" was not found on the file system.`);
|
2019-06-06 20:22:32 +01:00
|
|
|
const mapPath = absoluteFrom(file.fileName + '.map');
|
|
|
|
|
if (basename(e.path) !== basename(mapPath) && fs.exists(mapPath) &&
|
2019-04-28 20:48:35 +01:00
|
|
|
fs.stat(mapPath).isFile()) {
|
|
|
|
|
logger.warn(
|
2019-06-06 20:22:32 +01:00
|
|
|
`Guessing the map file name from the source file name: "${basename(mapPath)}"`);
|
2019-04-28 20:48:35 +01:00
|
|
|
try {
|
|
|
|
|
externalSourceMap = fromObject(JSON.parse(fs.readFile(mapPath)));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger.error(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
source: removeMapFileComments(file.text).replace(/\n\n$/, '\n'),
|
|
|
|
|
map: externalSourceMap,
|
|
|
|
|
isInline: false,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return {source: file.text, map: null, isInline: false};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Merge the input and output source-maps, replacing the source-map comment in the output file
|
|
|
|
|
* with an appropriate source-map comment pointing to the merged source-map.
|
|
|
|
|
*/
|
|
|
|
|
export function renderSourceAndMap(
|
|
|
|
|
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileToWrite[] {
|
2019-06-06 20:22:32 +01:00
|
|
|
const outputPath = absoluteFromSourceFile(sourceFile);
|
|
|
|
|
const outputMapPath = absoluteFrom(`${outputPath}.map`);
|
|
|
|
|
const relativeSourcePath = basename(outputPath);
|
2019-04-28 20:48:35 +01:00
|
|
|
const relativeMapPath = `${relativeSourcePath}.map`;
|
|
|
|
|
|
|
|
|
|
const outputMap = output.generateMap({
|
|
|
|
|
source: outputPath,
|
|
|
|
|
includeContent: true,
|
|
|
|
|
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
|
|
|
|
|
// the merge algorithm.
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// we must set this after generation as magic string does "manipulation" on the path
|
|
|
|
|
outputMap.file = relativeSourcePath;
|
|
|
|
|
|
|
|
|
|
const mergedMap =
|
|
|
|
|
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
|
|
|
|
|
|
|
|
|
|
const result: FileToWrite[] = [];
|
|
|
|
|
if (input.isInline) {
|
|
|
|
|
result.push({path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`});
|
|
|
|
|
} else {
|
|
|
|
|
result.push({
|
|
|
|
|
path: outputPath,
|
|
|
|
|
contents: `${output.toString()}\n${generateMapFileComment(relativeMapPath)}`
|
|
|
|
|
});
|
|
|
|
|
result.push({path: outputMapPath, contents: mergedMap.toJSON()});
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Merge the two specified source-maps into a single source-map that hides the intermediate
|
|
|
|
|
* source-map.
|
|
|
|
|
* E.g. Consider these mappings:
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* OLD_SRC -> OLD_MAP -> INTERMEDIATE_SRC -> NEW_MAP -> NEW_SRC
|
|
|
|
|
* ```
|
|
|
|
|
*
|
|
|
|
|
* this will be replaced with:
|
|
|
|
|
*
|
|
|
|
|
* ```
|
|
|
|
|
* OLD_SRC -> MERGED_MAP -> NEW_SRC
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
export function mergeSourceMaps(
|
|
|
|
|
oldMap: RawSourceMap | null, newMap: RawSourceMap): SourceMapConverter {
|
|
|
|
|
if (!oldMap) {
|
|
|
|
|
return fromObject(newMap);
|
|
|
|
|
}
|
|
|
|
|
const oldMapConsumer = new SourceMapConsumer(oldMap);
|
|
|
|
|
const newMapConsumer = new SourceMapConsumer(newMap);
|
|
|
|
|
const mergedMapGenerator = SourceMapGenerator.fromSourceMap(newMapConsumer);
|
|
|
|
|
mergedMapGenerator.applySourceMap(oldMapConsumer);
|
|
|
|
|
const merged = fromJSON(mergedMapGenerator.toString());
|
|
|
|
|
return merged;
|
|
|
|
|
}
|