[译]source-map api

248 阅读1分钟

仓库地址:source-map

source map api

这是一个用于生成和使用此处描述的源码映射格式的库。

在 Node 中使用

$ npm install source-map

在 Web 上使用

<script src="https://unpkg.com/source-map@0.7.3/dist/source-map.js"></script>
<script>
  sourceMap.SourceMapConsumer.initialize({
    "lib/mappings.wasm": "https://unpkg.com/source-map@0.7.3/lib/mappings.wasm",
  });
</script>

目录

示例

使用源码映射

const 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",
};

const whatever = await SourceMapConsumer.with(rawSourceMap, null, consumer => {
  console.log(consumer.sources);
  // [ 'http://example.com/www/js/one.js',
  //   'http://example.com/www/js/two.js' ]

  console.log(
    consumer.originalPositionFor({
      line: 2,
      column: 28,
    })
  );
  // { source: 'http://example.com/www/js/two.js',
  //   line: 2,
  //   column: 10,
  //   name: 'n' }

  console.log(
    consumer.generatedPositionFor({
      source: "http://example.com/www/js/two.js",
      line: 2,
      column: 10,
    })
  );
  // { line: 2, column: 28 }

  consumer.eachMapping(function (m) {
    // ...
  });

  return computeWhatever();
});

生成源码映射

深入指南: 编译为 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("错误的 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");

// 浏览器构建
var sourceMap = window.sourceMap;

// 在 Firefox 中
const sourceMap = require("devtools/toolkit/sourcemap/source-map.js");

SourceMapConsumer

SourceMapConsumer 实例表示一个已解析的源码映射,我们可以查询有关原始文件位置的信息,方法是提供生成源中的文件位置。

SourceMapConsumer.initialize(options)

在 node.js 之外(例如在 Web 上)使用 SourceMapConsumer 时,它需要知道从哪个 URL 加载 lib/mappings.wasm。您必须在构造任何 SourceMapConsumer 之前通过调用 initialize 来告知它。

options 对象具有以下属性:

  • "lib/mappings.wasm": 包含 lib/mappings.wasm 文件 URL 的 String,或包含 lib/mappings.wasm 内容的 ArrayBuffer
sourceMap.SourceMapConsumer.initialize({
  "lib/mappings.wasm": "https://example.com/source-map/lib/mappings.wasm",
});
new SourceMapConsumer(rawSourceMap)

唯一的参数是原始源码映射(可以是可以 JSON.parse 的字符串,也可以是对象)。根据规范,源码映射具有以下属性:

  • version: 此映射遵循的源码映射规范版本
  • sources: 原始源文件的 URL 数组。
  • names: 可以由单个映射引用的标识符数组。
  • sourceRoot: 可选。所有源都相对于的 URL 根。
  • sourcesContent: 可选。原始源文件内容的数组。
  • mappings: 包含实际映射的 base64 VLQ 字符串。
  • file: 可选。与此源码映射关联的生成的文件名。
  • x_google_ignoreList: 可选。一个附加扩展字段,它是一个数组,包含引用 sources 数组中的 URL 的索引。这用于识别开发人员在调试时可能想要避免的第三方源。阅读更多

返回构造的源码映射使用者的 promise。

当不再使用 SourceMapConsumer 时,您必须调用其 destroy 方法。

const consumer = await new sourceMap.SourceMapConsumer(rawSourceMapJsonData);
doStuffWith(consumer);
consumer.destroy();

或者,您可以使用 SourceMapConsumer.with 来避免需要记住调用 destroy

SourceMapConsumer.with

rawSourceMapsourceMapUrl 构造一个新的 SourceMapConsumer (有关详细信息,请参阅 SourceMapConsumer 构造函数)。然后,使用新构造的使用者调用 async function f(SourceMapConsumer) -> T,等待 f 完成,对使用者调用 destroy,并返回 f 的返回值。

f 完成后,您不能再使用该使用者!

通过使用 with,您不必记住手动调用 destroy,因为一旦 f 完成,它就会自动调用。

const xSquared = await SourceMapConsumer.with(
  myRawSourceMap,
  null,
  async function (consumer) {
    // 在这里使用 `consumer`,不用担心记住
    // 调用 `destroy`。

    const x = await whatever(consumer);
    return x * x;
  }
);

// 您不能在这里再使用那个 `consumer`;它已经
// 被销毁了。但您可以使用 `xSquared`。
console.log(xSquared);
SourceMapConsumer.prototype.destroy()

释放此源码映射使用者关联的手动管理的 wasm 数据。

consumer.destroy();

或者,您可以使用 SourceMapConsumer.with 来避免需要记住调用 destroy

SourceMapConsumer.prototype.computeColumnSpans()

计算每个生成的映射的最后一列。最后一列是包含的。

// 之前:
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" });
// [ { line: 2,
//     column: 1 },
//   { line: 2,
//     column: 10 },
//   { line: 2,
//     column: 20 } ]

consumer.computeColumnSpans();

// 之后:
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_BOUNDSourceMapConsumer.LEAST_UPPER_BOUND。指定在找不到精确元素时是返回小于还是大于我们正在搜索的元素的最接近元素。默认为SourceMapConsumer.GREATEST_LOWER_BOUND

返回一个具有以下属性的对象:

  • source: 原始源文件,如果此信息不可用则为 null。
  • line: 原始源中的行号,如果此信息不可用则为 null。行号是基于1的。
  • column: 原始源中的列号,如果此信息不可用则为 null。列号是基于0的。
  • name: 原始标识符,如果此信息不可用则为 null。
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: 原始源中的行号。行号是基于1的。
  • column: 原始源中的列号。列号是基于0的。

返回一个具有以下属性的对象:

  • line: 生成源中的行号,如果为 null。行号是基于1的。
  • column: 生成源中的列号,如果为 null。列号是基于0的。
consumer.generatedPositionFor({ source: "example.js", line: 2, column: 10 });
// { line: 1,
//   column: 56 }
SourceMapConsumer.prototype.allGeneratedPositionsFor(originalPosition)

返回为提供的原始源、行和列提供的所有生成的行和列信息。如果未提供列,则返回与我们正在搜索的行或具有任何映射的下一个最接近行相对应的所有映射。否则,返回与给定行相对应的所有映射,以及我们正在搜索的列或具有任何偏移的下一个最接近列。

唯一的参数是一个具有以下属性的对象:

  • source: 原始源文件的文件名。
  • line: 原始源中的行号。行号是基于1的。
  • column: 可选。原始源中的列号。列号是基于0的。

返回一个对象数组,每个对象具有以下属性:

  • line: 生成源中的行号,如果为 null。行号是基于1的。
  • column: 生成源中的列号,如果为 null。列号是基于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,则对于源码映射中的每个源 s,consumer.sourceContentFor(s) 都将成功。

// ...
if (consumer.hasContentsOfAllSources()) {
  consumerReadyCallback(consumer);
} else {
  fetchSources(consumer, consumerReadyCallback);
}
// ...
SourceMapConsumer.prototype.sourceContentFor(source[, 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)

遍历此源码映射中原始源/行/列和生成的行/列之间的每个映射。

  • callback: 对每个映射调用的函数。映射的形式为 { source, generatedLine, generatedColumn, originalLine, originalColumn, name }
  • context: 可选。如果指定,每次调用 callback 时,此对象将是 this 的值。
  • order: 可以是 SourceMapConsumer.GENERATED_ORDERSourceMapConsumer.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 的实例表示正在逐步构建的源码映射。

new SourceMapGenerator([startOfSourceMap])

您可以传递一个具有以下属性的对象:

  • file: 与此源码映射关联的生成源的文件名。
  • sourceRoot: 此源码映射中所有相对 URL 的根。
  • skipValidation: 可选。当为 true 时,禁用添加映射时的验证。这可以提高性能,但应谨慎使用,作为最后的手段。即使这样,也应尽可能避免在运行测试时使用此标志。
var generator = new sourceMap.SourceMapGenerator({
  file: "my-generated-javascript-file.js",
  sourceRoot: "http://example.com/app/js/",
});
SourceMapGenerator.fromSourceMap(sourceMapConsumer)

从现有的 SourceMapConsumer 实例创建一个新的 SourceMapGenerator

  • sourceMapConsumer 源码映射。
var generator = sourceMap.SourceMapGenerator.fromSourceMap(consumer);
SourceMapGenerator.prototype.addMapping(mapping)

为正在创建的此源码映射添加从原始源行和列到生成源的行和列的单个映射。映射对象应具有以下属性:

  • generated: 具有生成的行和列位置的对象。
  • original: 具有原始行和列位置的对象。
  • source: 原始源文件(相对于 sourceRoot)。
  • name: 此映射的可选原始令牌名称。
generator.addMapping({
  source: "module-one.scm",
  original: { line: 128, column: 0 },
  generated: { line: 3, column: 456 },
});
SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)

为原始源文件设置源内容。

  • 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。

    当两个 SourceMap 不在同一目录中,并且要应用的 SourceMap 包含相对源路径时,需要此参数。如果是这样,这些相对源路径需要相对于 SourceMap 重写。

    如果省略,则假定两个 SourceMap 在同一目录中,因此不需要任何重写。(提供 '.' 具有相同的效果。)

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

SourceNodes 提供了一种抽象生成的 JavaScript 源代码片段的插值和/或连接的方法,同时保持这些片段与原始源代码之间的行和列信息。这对于编译器在输出生成的 JS 和源映射之前可能使用的最终中间表示很有用。

new SourceNode([line, column, source[, chunk[, name]]])
  • line: 与此源节点关联的原始行号,如果它与原始行不关联则为 null。行号是基于1的。
  • column: 与此源节点关联的原始列号,如果它与原始列不关联则为 null。列号是基于0的。
  • source: 原始源的文件名;如果未提供文件名,则为 null。
  • 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 中相对源应该相对于的可选路径。
const consumer = await new SourceMapConsumer(
  fs.readFileSync("path/to/my-file.js.map", "utf8")
);
const node = SourceNode.fromStringWithSourceMap(
  fs.readFileSync("path/to/my-file.js"),
  consumer
);
SourceNode.prototype.add(chunk)

向此源节点添加一个生成的 JS 代码块。

  • chunk: 生成的 JS 代码的字符串片段、SourceNode 的另一个实例,或其中每个成员都是这些内容之一的数组。
node.add(" + ");
node.add(otherNode);
node.add([leftHandOperandNode, " + ", rightHandOperandNode]);
SourceNode.prototype.prepend(chunk)

在此源节点前面添加一个生成的 JS 代码块。

  • chunk: 生成的 JS 代码的字符串片段、SourceNode 的另一个实例,或其中每个成员都是这些内容之一的数组。
node.prepend("/** Build Id: f783haef86324gf **/\n\n");
SourceNode.prototype.setSourceContent(sourceFile, sourceContent)

为源文件设置源内容。这将被添加到 SourceMapsourcesContent 字段中。

  • 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)

遍历 SourceNodes 树。遍历函数对每个源文件内容调用,并传递文件名和源内容。

  • 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(sep)

类似于 Array.prototype.join,但用于 SourceNodes。在此源节点的每个子节点之间插入分隔符。

  • 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(pattern, replacement)

在最右边的源片段上调用 String.prototype.replace。用于修剪源节点末尾的空白等。

  • pattern: 要替换的模式。
  • replacement: 用于替换模式的内容。
// 修剪尾随空白。
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])

返回此源节点树的字符串表示,以及包含生成源和原始源之间所有映射的 SourceMapGenerator。

参数与 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] }