prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
257 lines (202 loc) • 9.02 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SourceMapManager = void 0;
var _invariant = _interopRequireDefault(require("../invariant.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/* strict-local */
/**
* Sourcemap paths can come in one of two formats:
* - Relative: The paths include `../` and can be followed from the sourcemap's location
* to arrive at the original source's location. In this format, path conversion
* requires two different prefixes (MapDifference and CommonPrefix) that must be
* discovered from the input paths.
* - Common Directory: The paths take the format of an absolute path (`/foo/bar`) and
* assume there is a common prefix to the path that, when added, will make the path an
* valid absolute path. This prefix is passed in as the `buckRoot` argument.
* Example:
* In a directory structure with /A/B/map.js and /A/C/original.js,
* the sourcemaps would have the following path structures:
* - Relative: ../C/original.js, with `CP` = /A and 'MD' = ../
* - Common Directory: /C/original.js, with `buckRoot` = /A
*/
class SourceMapManager {
constructor(buckRoot, sourceMaps) {
// Use presence of buck root argument to indicate which path format sourcemap prefixes take on.
if (buckRoot !== undefined) {
if (sourceMaps === undefined) {
throw new Error("Invalid input: Can't provide a sourcemap directory root without having sourcemaps present");
}
this._buckRoot = buckRoot;
if (this._buckRoot[this._buckRoot.length - 1] === "/") {
// Remove trailing slash to prepare for prepending to internal paths.
this._buckRoot = this._buckRoot.slice(0, -1);
}
} else {
// If sourcemaps don't exist, set prefixes to undefined and break.
if (sourceMaps && sourceMaps.length > 0) {
for (let map of sourceMaps) {
if (map.sourceMapContents === undefined || map.sourceMapContents === "") {
this._sourcemapCommonPrefix = undefined;
this._sourcemapMapDifference = undefined;
return;
}
}
} else {
this._sourcemapCommonPrefix = undefined;
this._sourcemapMapDifference = undefined;
return;
} // Extract common prefix and map difference
let originalSourcePaths = [];
let mapPaths = [];
for (let map of sourceMaps) {
(0, _invariant.default)(map.sourceMapContents !== undefined); // Checked above.
let parsed = JSON.parse(map.sourceMapContents); // Two formats for sourcemaps exist.
if ("sections" in parsed) {
for (let section of parsed.sections) {
for (let source of section.map.sources) {
originalSourcePaths.push(this._getAbsoluteSourcePath(map.filePath, source));
}
}
} else {
for (let source of parsed.sources) {
originalSourcePaths.push(this._getAbsoluteSourcePath(map.filePath, source));
}
}
mapPaths.push(this._stripEmptyStringBookends(map.filePath.split("/")));
}
let originalSourceCommonPrefix = this._findCommonPrefix(originalSourcePaths);
let originalSourceCPElements = this._stripEmptyStringBookends(originalSourceCommonPrefix.split("/"));
let mapCommonPrefix = this._findCommonPrefix(mapPaths);
let mapCPElements = this._stripEmptyStringBookends(mapCommonPrefix.split("/"));
this._sourcemapCommonPrefix = this._findCommonPrefix([originalSourceCPElements, mapCPElements]);
this._sourcemapMapDifference = this._findMapDifference(this._sourcemapCommonPrefix, mapCommonPrefix);
}
} // Prefixes used to translate between relative paths stored in AST nodes and absolute paths given to IDE.
// For paths relative to directory root. (Used in Buck format)
/**
* Assumes that input file and sourcemap are in the same directory.
* Assumes pathToInput is an absolute path and pathToSource is relative.
* Uses pathToSource to find absolute path of original source file.
*/
_getAbsoluteSourcePath(pathToInput, pathToSource) {
// pathToInput is an absolute path to the file being prepacked.
let fullPath = this._stripEmptyStringBookends(pathToInput.split("/")); // Remove last entry because it is the filename, while we want the parent directory of the input file.
fullPath.pop(); // Traverse the path to the source file.
let steps = pathToSource.split("/");
for (let step of steps) {
switch (step) {
case ".":
break;
case "..":
fullPath.pop();
break;
default:
fullPath.push(step);
break;
}
}
return fullPath;
}
/**
* Finds the longest possible prefix common to all input paths.
* Input paths must be absolute.
* Input is nested array because each path must be separated into elements.
*/
_findCommonPrefix(paths) {
// Find the point at which the paths diverge.
let divergenceIndex = 0;
let allPathsMatch = true;
let maxDivergenceIndex = Math.max(...paths.map(path => path.length));
while (allPathsMatch && divergenceIndex < maxDivergenceIndex) {
let entry = paths[0][divergenceIndex]; // Arbitrary choice of 0th path, since we're checking if all entires match.
for (let path of paths) {
if (path[divergenceIndex] !== entry) {
allPathsMatch = false;
break;
}
}
if (allPathsMatch) divergenceIndex += 1;
} // Edge case: if there's only one path, it will match itself, including the filename at the end.
// For 2+ paths, even if they all share a prefix, the filenames will not match, so this is not needed.
if (paths.length === 1) divergenceIndex -= 1; // Concatenate prefix into string that's bookended by slashes for use as an absolute path prefix.
return `/${paths[0].slice(0, divergenceIndex).join("/")}/`;
}
/**
* Finds the path that must be followed to arrive at the directory of the
* common prefix from the sourcemap.
*/
_findMapDifference(commonPrefix, mapPrefix) {
// Find difference in path between the map's location and the common prefix.
let mapPrefixUniqueElements = this._stripEmptyStringBookends(mapPrefix.replace(commonPrefix, "").split("/"));
let mapDifference = "";
for (let i = 0; i < mapPrefixUniqueElements.length; i++) {
mapDifference = mapDifference.concat("../");
}
return mapDifference;
}
/**
* Takes in ["", "foo", "bar", ""] and returns ["foo", "bar"]
*/
_stripEmptyStringBookends(path) {
if (path[0] === "") path.shift();
if (path[path.length - 1] === "") path.pop();
return path;
}
/**
* Used by DebugAdapter to convert relative paths (used internally in debugging/Prepack engine)
* into absolute paths (used by debugging UI/IDE).
*/
relativeToAbsolute(path) {
let absolute;
if (this._buckRoot !== undefined) {
let dirRoot = this._buckRoot;
if ( // If the "relative" path is actually absolute, then don't prepend anything.
this._stripEmptyStringBookends(path.split("/"))[0] === this._stripEmptyStringBookends(this._buckRoot.split("/"))[0]) {
absolute = path;
} else {
let separator = path[0] === "/" ? "" : "/";
absolute = dirRoot + separator + path;
}
} else {
if (this._sourcemapCommonPrefix !== undefined && this._sourcemapMapDifference !== undefined) {
absolute = path.replace(this._sourcemapMapDifference, "");
(0, _invariant.default)(this._sourcemapCommonPrefix !== undefined);
absolute = this._sourcemapCommonPrefix + absolute;
} else {
absolute = path;
}
}
return absolute;
}
/**
* Used by DebugAdapter to convert absolute paths (used by debugging UI/IDE)
* into relative paths (used internally in debugging/Prepack engine).
*/
absoluteToRelative(path) {
let relative;
if (this._buckRoot !== undefined) {
relative = path.replace(this._buckRoot, "");
} else {
if (this._sourcemapCommonPrefix !== undefined && this._sourcemapMapDifference !== undefined) {
relative = path.replace(this._sourcemapCommonPrefix, "");
(0, _invariant.default)(this._sourcemapMapDifference !== undefined);
relative = this._sourcemapMapDifference + relative;
} else {
relative = path;
}
}
return relative;
}
}
exports.SourceMapManager = SourceMapManager;
//# sourceMappingURL=SourceMapManager.js.map