源地图JS
原始差异 源图:
这是原创的一个分支 [email protected],但具有性能优化。
这段旅程从 [email protected]. 它的一部分被重写为Rust, WASM和API变成了异步。
它仍然是许多库的主要障碍,比如PostCSS或Sass,因为它们需要将整个API迁移到异步方式。这就是为什么0.6.1的下载量是0.7.3的两倍,而且速度是0.7.3的几倍。

更重要的是,WASM版本在JS代码中也有一些优化。这就是为什么 社区要求为0.6版本创建分支 并移植这些优化,但遗憾的是,答案是“不”。过了一会儿我才发现 这个问题 由 本·罗斯曼(@benthemonkey) 没有任何回应。
罗曼·德沃诺夫(@lahmatiy) 写了一个 几个帖子 (俄语,只有,对不起)关于他自己的电报频道的源地图库。他提到了那篇文章 也许你不需要Rust和WASM来加速你的JS 写的 Vyacheslav Egorov (@mraleph). 本文包含与WASM实现相比性能几乎相同的优化和hack。
我决定派生原始源映射,并从本文和原始源映射的其他几个PR中移植这些优化。
这是一个生成和使用源映射格式的库 这里描述。
与Node一起使用
$ npm install source-map-js
目录表
- 例子
- API
- SourceMapConsumer
- 新SourceMapConsumer (rawSourceMap)
- SourceMapConsumer.prototype.computeColumnSpans ()
- SourceMapConsumer.prototype.originalPositionFor (generatedPosition)
- SourceMapConsumer.prototype.generatedPositionFor (originalPosition)
- SourceMapConsumer.prototype.allGeneratedPositionsFor (originalPosition)
- SourceMapConsumer.prototype.hasContentsOfAllSources ()
- SourceMapConsumer.prototype。sourceContentFor(源[,returnNullOnMissing])
- SourceMapConsumer.prototype。eachMapping(callback, context, order)
- SourceMapGenerator
- 新SourceMapGenerator ([startOfSourceMap])
- SourceMapGenerator.fromSourceMap (sourceMapConsumer)
- SourceMapGenerator.prototype.addMapping(映射)
- SourceMapGenerator.prototype。sourceContent setSourceContent(源文件)
- SourceMapGenerator.prototype。applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])
- SourceMapGenerator.prototype.toString ()
- SourceNode
- new SourceNode([line, column, source[, chunk[, name]])
- SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])
- SourceNode.prototype.add(块)
- SourceNode.prototype.prepend(块)
- SourceNode.prototype。sourceContent setSourceContent(源文件)
- SourceNode.prototype.walk (fn)
- SourceNode.prototype.walkSourceContents (fn)
- SourceNode.prototype.join(9月)
- SourceNode.prototype。replaceRight(模式、更换)
- SourceNode.prototype.toString ()
- SourceNode.prototype.toStringWithSourceMap ([startOfSourceMap])
- SourceMapConsumer
例子
使用源映射
var rawSourceMap = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourceRoot: 'http://example.com/www/js/',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
var smc = new SourceMapConsumer(rawSourceMap);
console.log(smc.sources);
// [ 'http://example.com/www/js/one.js',
// 'http://example.com/www/js/two.js' ]
console.log(smc.originalPositionFor({
line: 2,
column: 28
}));
// { source: 'http://example.com/www/js/two.js',
// line: 2,
// column: 10,
// name: 'n' }
console.log(smc.generatedPositionFor({
source: 'http://example.com/www/js/two.js',
line: 2,
column: 10
}));
// { line: 2, column: 28 }
smc.eachMapping(function (m) {
// ...
});
生成源映射
深入指南: 编译成JavaScript,并调试与源映射
使用SourceNode(高级API)
function compile(ast) {
switch (ast.type) {
case 'BinaryExpression':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
[compile(ast.left), " + ", compile(ast.right)]
);
case 'Literal':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
String(ast.value)
);
// ...
default:
throw new Error("Bad AST");
}
}
var ast = parse("40 + 2", "add.js");
console.log(compile(ast).toStringWithSourceMap({
file: 'add.js'
}));
// { code: '40 + 2',
// map: [object SourceMapGenerator] }
使用SourceMapGenerator(低级API)
var map = new SourceMapGenerator({
file: "source-mapped.js"
});
map.addMapping({
generated: {
line: 10,
column: 35
},
source: "foo.js",
original: {
line: 33,
column: 2
},
name: "christopher"
});
console.log(map.toString());
// '{"version":3,"file":"source-mapped.js","sources":["foo.js"],"names":["christopher"],"mappings":";;;;;;;;;mCAgCEA"}'
API
获取该模块的引用:
// Node.js
var sourceMap = require('source-map');
// Browser builds
var sourceMap = window.sourceMap;
// Inside Firefox
const sourceMap = require("devtools/toolkit/sourcemap/source-map.js");
SourceMapConsumer
SourceMapConsumer实例表示可以查询的解析源映射 通过给它一个文件位置来获取有关原始文件位置的信息 在生成的源文件中。
新SourceMapConsumer (rawSourceMap)
唯一的参数是原始源映射(可以是字符串,也可以是
JSON.parse'd,或一个对象)。根据规范,源映射具有
以下属性:
-
version:此映射遵循源映射规范的哪个版本。 -
sources:指向原始源文件的url数组。 -
names:可供个人引用的标识符数组 映射。 -
sourceRoot:可选的。URL根,所有源都是相对的。 -
sourcesContent:可选的。原始源文件的内容数组。 -
mappings:包含实际映射的base64 vlq字符串。 -
file:可选的。与此源映射相关联的生成的文件名。
var consumer = new sourceMap.SourceMapConsumer(rawSourceMapJsonData);
SourceMapConsumer.prototype.computeColumnSpans ()
为每个生成的映射计算最后一列。最后一列是 包容性。
// Before:
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" })
// [ { line: 2,
// column: 1 },
// { line: 2,
// column: 10 },
// { line: 2,
// column: 20 } ]
consumer.computeColumnSpans();
// After:
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" })
// [ { line: 2,
// column: 1,
// lastColumn: 9 },
// { line: 2,
// column: 10,
// lastColumn: 19 },
// { line: 2,
// column: 20,
// lastColumn: Infinity } ]
SourceMapConsumer.prototype.originalPositionFor (generatedPosition)
返回生成的对象的原始源、行和列信息 提供源的行和列位置。唯一的实参是一个带的对象 以下属性:
-
line:生成源文件中的行号。中的行号 这个库是基于1的(注意底层的源映射) 规范使用基于0的行号——这个库处理 翻译)。 -
column:生成源中的列号。列数字 在这个库中是基于0的。 -
bias:要么SourceMapConsumer.GREATEST_LOWER_BOUND或SourceMapConsumer.LEAST_UPPER_BOUND。指定是否返回最近的 小于或大于我们要查找的元素, 分别,如果找不到确切的元素。默认为SourceMapConsumer.GREATEST_LOWER_BOUND。
返回一个具有以下属性的对象:
-
source:原始的源文件,如果该信息不是,则为空 可用。 -
line:原始源代码中的行号,如果该信息为,则为空 不可用。行号从1开始。 -
column:原始源中的列号,如果是,则为空 没有相关信息。列号从0开始。 -
name:原始标识符,如果此信息不可用,则为空。
consumer.originalPositionFor({ line: 2, column: 10 })
// { source: 'foo.coffee',
// line: 2,
// column: 2,
// name: null }
consumer.originalPositionFor({ line: 99999999999999999, column: 999999999999999 })
// { source: null,
// line: null,
// column: null,
// name: null }
SourceMapConsumer.prototype.generatedPositionFor (originalPosition)
返回为原始源生成的行和列信息, 提供了行和列位置。唯一的实参是一个带的对象 以下属性:
-
source:原始源文件的文件名。 -
line:原始源代码中的行号。行号为 基于。 -
column:原源中的列号。列 数字是从0开始的。
返回一个具有以下属性的对象:
-
line:生成源文件中的行号,或者为空。线 数字是从1开始的。 -
column:生成源中的列号,或为空。这个 列号从0开始。
consumer.generatedPositionFor({ source: "example.js", line: 2, column: 10 })
// { line: 1,
// column: 56 }
SourceMapConsumer.prototype.allGeneratedPositionsFor (originalPosition)
返回为原始源、行、 列提供。如果没有提供列,则返回所有映射 对应于我们正在搜索的行或下一个最近的行 它有任何映射。对象对应的所有映射 给定的行和我们正在搜索的列或下一个最近的列 它有任何抵消。
唯一的参数是一个具有以下属性的对象:
-
source:原始源文件的文件名。 -
line:原始源代码中的行号。行号为 基于。 -
column:可选的。原始源中的列号。这个 列号从0开始。
并返回一个对象数组,每个对象都具有以下属性:
-
line:生成源文件中的行号,或者为空。线 数字是从1开始的。 -
column:生成源中的列号,或为空。这个 列号从0开始。
consumer.allGeneratedpositionsfor({ line: 2, source: "foo.coffee" })
// [ { line: 2,
// column: 1 },
// { line: 2,
// column: 10 },
// { line: 2,
// column: 20 } ]
SourceMapConsumer.prototype.hasContentsOfAllSources ()
中列出的每个源都嵌入了源内容,则返回true 源映射,否则为false。
换句话说,如果这个方法返回 true,然后
consumer.sourceContentFor(s) 每个来源都能成功吗 s 在
consumer.sources。
// ...
if (consumer.hasContentsOfAllSources()) {
consumerReadyCallback(consumer);
} else {
fetchSources(consumer, consumerReadyCallback);
}
// ...
SourceMapConsumer.prototype。sourceContentFor(源[,returnNullOnMissing])
返回所提供源的原始源内容。唯一的 参数是原始源文件的URL。
如果没有找到给定源的源内容,则会出现错误
抛出。可选地,通过 true 作为第二个参数 null 返回
代替。
consumer.sources
// [ "my-cool-lib.clj" ]
consumer.sourceContentFor("my-cool-lib.clj")
// "..."
consumer.sourceContentFor("this is not in the source map");
// Error: "this is not in the source map" is not in the source map
consumer.sourceContentFor("this is not in the source map", true);
// null
SourceMapConsumer.prototype。eachMapping(callback, context, order)
遍历原始源/行/列与a之间的每个映射 在此源映射中生成的行/列。
-
callback:每次映射调用的函数。映射具有 形式{ source, generatedLine, generatedColumn, originalLine, originalColumn, name } -
context:可选的。如果指定,该对象将是的值this每次callback被称为。 -
order:要么SourceMapConsumer.GENERATED_ORDER或SourceMapConsumer.ORIGINAL_ORDER。指定是否要遍历 按生成文件的行/列顺序或 原文的来源/行/列顺序分别。默认为SourceMapConsumer.GENERATED_ORDER。
consumer.eachMapping(function (m) { console.log(m); })
// ...
// { source: 'illmatic.js',
// generatedLine: 1,
// generatedColumn: 0,
// originalLine: 1,
// originalColumn: 0,
// name: null }
// { source: 'illmatic.js',
// generatedLine: 2,
// generatedColumn: 0,
// originalLine: 2,
// originalColumn: 0,
// name: null }
// ...
SourceMapGenerator
SourceMapGenerator的一个实例表示正在被生成的源映射 逐步建立的。
新SourceMapGenerator ([startOfSourceMap])
你可以传递一个具有以下属性的对象:
-
file:此源映射所生成的源的文件名 联系在一起。 -
sourceRoot:此源映射中所有相对url的根。 -
skipValidation:可选的。当true,禁用映射的验证 它们是相加的。这可以提高性能,但应该与 谨慎,作为最后的手段。即使这样,也应该避免使用这个标志 运行测试,如果可能的话。 -
ignoreInvalidMapping:可选的。当true,而不是抛出错误 无效映射,它将被忽略。
var generator = new sourceMap.SourceMapGenerator({
file: "my-generated-javascript-file.js",
sourceRoot: "http://example.com/app/js/"
});
SourceMapGenerator.fromSourceMap (sourceMapConsumer sourceMapGeneratorOptions)
创建一个新的 SourceMapGenerator 从现有的 SourceMapConsumer 实例。
-
sourceMapConsumerSourceMap。 -
sourceMapGeneratorOptions选项,这些选项将被传递给SourceMapGenerator构造函数,该构造函数在底层使用。
var generator = sourceMap.SourceMapGenerator.fromSourceMap(consumer, {
ignoreInvalidMapping: true,
});
SourceMapGenerator.prototype.addMapping(映射)
将原始源行和列的单个映射添加到生成的 正在创建的源映射的源行和列。映射对象 应具有以下属性:
-
generated:具有生成的行和列位置的对象。 -
original:具有原始线和列位置的对象。 -
source:原始源文件(相对于sourceRoot)。 -
name:此映射的可选原始令牌名称。
generator.addMapping({
source: "module-one.scm",
original: { line: 128, column: 0 },
generated: { line: 3, column: 456 }
})
SourceMapGenerator.prototype。sourceContent setSourceContent(源文件)
为原始源文件设置源内容。
-
sourceFile原始源文件的URL。 -
sourceContent源文件的内容。
generator.setSourceContent("module-one.scm",
fs.readFileSync("path/to/module-one.scm"))
SourceMapGenerator.prototype。applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])
将源文件的SourceMap应用于SourceMap。 方法重写到所提供源文件的每个映射 SourceMap提供。注意:结果映射的分辨率 是这个地图和提供的地图的最小值。
-
sourceMapConsumer要应用的SourceMap。 -
sourceFile:可选的。源文件的文件名。 如果省略,则将使用sourceMapConsumer.file(如果存在)。 否则将抛出错误。 -
sourceMapPath:可选的。SourceMap的路径的dirname 待应用的如果是相对的,它是相对于SourceMap的。当两个SourceMaps不相同时需要此参数 目录,并且要应用的SourceMap包含相对源 路径。如果是这样,则需要重写这些相对源路径 相对于SourceMap。
如果省略,则假定两个SourceMaps都在同一目录中, 因此不需要任何重写。(提供
'.'具有相同的效果。)
SourceMapGenerator.prototype.toString ()
将生成的源映射呈现为字符串。
generator.toString()
// '{"version":3,"sources":["module-one.scm"],"names":[],"mappings":"...snip...","file":"my-generated-javascript-file.js","sourceRoot":"http://example.com/app/js/"}'
SourceNode
sourcenode提供了一种超越插值和/或连接的抽象方式 生成的JavaScript源代码片段,同时维护行和 在这些片段和原始源之间关联的列信息 代码。这是编译器可能使用的最终中间表示形式 在输出生成的JS和源映射之前使用。
new SourceNode([line, column, source[, chunk[, name]])
-
line:与此源节点关联的原始行号,如果为空,则为空 它与原始的线没有关联。行号从1开始。 -
column:与此源节点关联的原始列号,或者为空 如果它不与原始列关联。列号 是基于0。 -
source:原始源文件的文件名;如果没有提供文件名,则为空。 -
chunk:可选的。立即传递给SourceNode.prototype.add,看 在下面。 -
name:可选的。原始标识符。
var node = new SourceNode(1, 2, "a.cpp", [
new SourceNode(3, 4, "b.cpp", "extern int status;\n"),
new SourceNode(5, 6, "c.cpp", "std::string* make_string(size_t n);\n"),
new SourceNode(7, 8, "d.cpp", "int main(int argc, char** argv) {}\n"),
]);
SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])
从生成的代码和SourceMapConsumer中创建一个SourceNode。
-
code:生成的代码 -
sourceMapConsumer生成代码的SourceMap -
relativePath相对源所在的可选路径sourceMapConsumer应该是相对的。
var consumer = new SourceMapConsumer(fs.readFileSync("path/to/my-file.js.map", "utf8"));
var node = SourceNode.fromStringWithSourceMap(fs.readFileSync("path/to/my-file.js"),
consumer);
SourceNode.prototype.add(块)
将生成的JS块添加到该源节点。
chunk生成的JS代码的字符串片段,另一个实例SourceNode,或者一个数组,其中每个成员都是这些元素中的一个。
node.add(" + ");
node.add(otherNode);
node.add([leftHandOperandNode, " + ", rightHandOperandNode]);
SourceNode.prototype.prepend(块)
将生成的JS块添加到该源节点。
chunk生成的JS代码的字符串片段,另一个实例SourceNode,或者一个数组,其中每个成员都是这些元素中的一个。
node.prepend("/** Build Id: f783haef86324gf **/\n\n");
SourceNode.prototype。sourceContent setSourceContent(源文件)
设置源文件的源内容。这将被添加到
SourceMap 在 sourcesContent 字段。
-
sourceFile:源文件的文件名 -
sourceContent:源文件的内容
node.setSourceContent("module-one.scm",
fs.readFileSync("path/to/module-one.scm"))
SourceNode.prototype.walk (fn)
遍历该节点及其子节点中的JS片段树。行走 函数在JS的每个代码片段中调用一次,并传递该代码片段和 其原始关联源的行/列位置。
fn:遍历函数。
var node = new SourceNode(1, 2, "a.js", [
new SourceNode(3, 4, "b.js", "uno"),
"dos",
[
"tres",
new SourceNode(5, 6, "c.js", "quatro")
]
]);
node.walk(function (code, loc) { console.log("WALK:", code, loc); })
// WALK: uno { source: 'b.js', line: 3, column: 4, name: null }
// WALK: dos { source: 'a.js', line: 1, column: 2, name: null }
// WALK: tres { source: 'a.js', line: 1, column: 2, name: null }
// WALK: quatro { source: 'c.js', line: 5, column: 6, name: null }
SourceNode.prototype.walkSourceContents (fn)
浏览sourcenode树。每个函数都调用遍历函数 源文件内容,并传递文件名和源内容。
fn:遍历函数。
var a = new SourceNode(1, 2, "a.js", "generated from a");
a.setSourceContent("a.js", "original a");
var b = new SourceNode(1, 2, "b.js", "generated from b");
b.setSourceContent("b.js", "original b");
var c = new SourceNode(1, 2, "c.js", "generated from c");
c.setSourceContent("c.js", "original c");
var node = new SourceNode(null, null, null, [a, b, c]);
node.walkSourceContents(function (source, contents) { console.log("WALK:", source, ":", contents); })
// WALK: a.js : original a
// WALK: b.js : original b
// WALK: c.js : original c
SourceNode.prototype.join(9月)
就像 Array.prototype.join 除了sourcenode。插入分隔符
在此源节点的每个子节点之间。
sep:分隔符。
var lhs = new SourceNode(1, 2, "a.rs", "my_copy");
var operand = new SourceNode(3, 4, "a.rs", "=");
var rhs = new SourceNode(5, 6, "a.rs", "orig.clone()");
var node = new SourceNode(null, null, null, [ lhs, operand, rhs ]);
var joinedNode = node.join(" ");
SourceNode.prototype。replaceRight(模式、更换)
呼叫 String.prototype.replace 在最右边的源代码片段中。有用的
用于从源节点等的末端修剪空白。
-
pattern:要替换的图案。 -
replacement:用来代替图案的东西。
// Trim trailing white space.
node.replaceRight(/\s*$/, "");
SourceNode.prototype.toString ()
返回此源节点的字符串表示形式。走过树,然后 将所有不同的片段连接到一个字符串中。
var node = new SourceNode(1, 2, "a.js", [
new SourceNode(3, 4, "b.js", "uno"),
"dos",
[
"tres",
new SourceNode(5, 6, "c.js", "quatro")
]
]);
node.toString()
// 'unodostresquatro'
SourceNode.prototype.toStringWithSourceMap ([startOfSourceMap])
返回此源节点树的字符串表示形式,加上a SourceMapGenerator,它包含生成的和 最初的来源。
参数与to的参数相同 new SourceMapGenerator。
var node = new SourceNode(1, 2, "a.js", [
new SourceNode(3, 4, "b.js", "uno"),
"dos",
[
"tres",
new SourceNode(5, 6, "c.js", "quatro")
]
]);
node.toStringWithSourceMap({ file: "my-output-file.js" })
// { code: 'unodostresquatro',
// map: [object SourceMapGenerator] }
