From a41a733506926c279ed157ac9340b8ac6c58053e Mon Sep 17 00:00:00 2001 From: Binote <35365908+Binote@users.noreply.github.com> Date: Tue, 10 Aug 2021 19:52:15 +0800 Subject: [PATCH] Update plugin-handbook.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 细节优化 detail optimizing --- translations/zh-Hans/plugin-handbook.md | 259 +++++++++++++----------- 1 file changed, 144 insertions(+), 115 deletions(-) diff --git a/translations/zh-Hans/plugin-handbook.md b/translations/zh-Hans/plugin-handbook.md index 6e0a7c81..1a723fed 100644 --- a/translations/zh-Hans/plugin-handbook.md +++ b/translations/zh-Hans/plugin-handbook.md @@ -9,22 +9,22 @@ # 目录 * [介绍](#toc-introduction) - * [基础](#toc-basics) + * [基础](#toc-basics) * [抽象语法树(ASTs)](#toc-asts) * [Babel 的处理步骤](#toc-stages-of-babel) - * [解析](#toc-parse) + * [解析](#toc-parse) * [词法分析](#toc-lexical-analysis) * [语法分析](#toc-syntactic-analysis) * [转换](#toc-transform) * [生成](#toc-generate) * [遍历](#toc-traversal) * [Visitors(访问者)](#toc-visitors) - * [Paths(路径)](#toc-paths) + * [Paths(路径)](#toc-paths) * [Paths in Visitors(存在于访问者中的路径)](#toc-paths-in-visitors) * [State(状态)](#toc-state) - * [Scopes(作用域)](#toc-scopes) + * [Scopes(作用域)](#toc-scopes) * [Bindings(绑定)](#toc-bindings) - * [API](#toc-api) + * [API](#toc-api) * [babylon](#toc-babylon) * [babel-traverse](#toc-babel-traverse) * [babel-types](#toc-babel-types) @@ -35,7 +35,7 @@ * [babel-generator](#toc-babel-generator) * [babel-template](#toc-babel-template) * [编写你的第一个 Babel 插件](#toc-writing-your-first-babel-plugin) - * [转换操作](#toc-transformation-operations) + * [转换操作](#toc-transformation-operations) * [访问](#toc-visiting) * [获取子节点的Path](#toc-get-the-path-of-a-sub-node) * [检查节点(Node)类型](#toc-check-if-a-node-is-a-certain-type) @@ -58,11 +58,11 @@ * [生成UID](#toc-generating-a-uid) * [提升变量声明至父级作用域](#toc-pushing-a-variable-declaration-to-a-parent-scope) * [重命名绑定及其引用](#toc-rename-a-binding-and-its-references) - * [插件选项](#toc-plugin-options) + * [插件选项](#toc-plugin-options) * [插件的准备和收尾工作](#toc-pre-and-post-in-plugins) * [在插件中启用其他语法](#toc-enabling-syntax-in-plugins) * [构建节点](#toc-building-nodes) - * [最佳实践](#toc-best-practices) + * [最佳实践](#toc-best-practices) * [尽量避免遍历抽象语法树(AST)](#toc-avoid-traversing-the-ast-as-much-as-possible) * [及时合并访问者对象](#toc-merge-visitors-whenever-possible) * [可以手动查找就不要遍历](#toc-do-not-traverse-when-manual-lookup-will-do) @@ -78,7 +78,8 @@ Babel 是一个通用的多功能的 JavaScript 编译器。此外它还拥有 你可以使用 Babel 创建多种类型的工具来帮助你更有效率并且写出更好的程序。 -> ***在 Twitter 上关注 [@thejameskyle](https://twitter.com/thejameskyle),第一时间获取更新。*** +> ***在 Twitter 上关注 [@thejameskyle](https://twitter.com/thejameskyle) +> 第一时间获取更新。*** * * * @@ -397,7 +398,7 @@ Called! 让我们以上面那棵树为例子走一遍这个过程。 - * 进入 `FunctionDeclaration` + * 进入 `FunctionDeclaration` * 进入 `Identifier (id)` * 走到尽头 * 退出 `Identifier (id)` @@ -405,12 +406,12 @@ Called! * 走到尽头 * 退出 `Identifier (params[0])` * 进入 `BlockStatement (body)` - * 进入 `ReturnStatement (body)` + * 进入 `ReturnStatement (body)` * 进入 `BinaryExpression (argument)` - * 进入 `Identifier (left)` + * 进入 `Identifier (left)` * 走到尽头 * 退出 `Identifier (left)` - * 进入 `Identifier (right)` + * 进入 `Identifier (right)` * 走到尽头 * 退出 `Identifier (right)` * 退出 `BinaryExpression (argument)` @@ -1311,9 +1312,9 @@ path.getStatementParent(); * 使用 `path.container`获取路径的容器(包含所有同级节点的数组) * 使用 `path.listKey`获取容器的key -> 这些API用于 babel-minify 中使用的 transform-merge-sibling-variables 插件.

-> -> ```js +> 这些API用于 babel-minify `中使用的 transform-merge-sibling-variables `插件. + +```js var a = 1; // pathA, path.key = 0 var b = 2; // pathB, path.key = 1 var c = 3; // pathC, path.key = 2 @@ -1403,11 +1404,11 @@ ReturnStatement(path) { } ``` -> **注意:当用多个节点替换一个表达式时,它们必须是   声明。 这是因为Babel在更换节点时广泛使用启发式算法,这意味着您可以做一些非常疯狂的转换,否则将会非常冗长。

-> -> ### 用字符串源码替换节点 -> -> ```js +> **注意:当用多个节点替换一个表达式时,它们必须是   声明。 这是因为Babel在更换节点时广泛使用启发式算法,这意味着您可以做一些非常疯狂的转换,否则将会非常冗长。 + +### 用字符串源码替换节点 + +```js FunctionDeclaration(path) { path.replaceWithSourceString(`function add(a, b) { return a + b; @@ -1423,11 +1424,11 @@ FunctionDeclaration(path) { } ``` -> **注意:不建议使用这个API,除非您正在处理动态的源码字符串,否则在访问者外部解析代码更有效率。

-> -> ### 插入兄弟节点 -> -> ```js +> **注意:不建议使用这个API,除非您正在处理动态的源码字符串,否则在访问者外部解析代码更有效率。 + +### 插入兄弟节点 + +```js FunctionDeclaration(path) { path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go."))); path.insertAfter(t.expressionStatement(t.stringLiteral("A little high, little low."))); @@ -1442,14 +1443,14 @@ FunctionDeclaration(path) { + "A little high, little low."; ``` -> 注意:这里同样应该使用声明或者一个声明数组。 这个使用了在用多个节点替换一个节点中提到的相同的启发式算法。.

-> -> ### 插入到容器(container)中 -> +> 注意:`这里同样应该使用声明或者一个声明数组。 这个使用了在用多个节点替换一个节点`中提到的相同的启发式算法。.

+ +### 插入到容器(container)中 + > 如果您想要在AST节点属性中插入一个像` body 那样的数组。 -它与 insertBefore `/` insertAfter ` 类似, 但您必须指定 ` listKey ` (通常是 ` 正文 `). -> -> ```js +它与 `insertBefore`/`insertAfter ` 类似, 但您必须指定 ` listKey` (通常是 `正文`). + +```js ClassMethod(path) { path.get('body').unshiftContainer('body', t.expressionStatement(t.stringLiteral('before'))); path.get('body').pushContainer('body', t.expressionStatement(t.stringLiteral('after'))); @@ -1482,14 +1483,16 @@ FunctionDeclaration(path) { ### 替换父节点 -只需使用parentPath:` path.parentPath 调用 replaceWith 即可

+只需使用parentPath:` path.parentPath `调用` replaceWith `即可

+ -
BinaryExpression(path) {
+```js
+BinaryExpression(path) {
   path.parentPath.replaceWith(
     t.expressionStatement(t.stringLiteral("Anyway the wind blows, doesn't really matter to me, to me."))
   );
 }
-`
+``` ```diff function square(n) { @@ -1526,7 +1529,7 @@ FunctionDeclaration(path) { 这将遍历范围树并检查特定的绑定。 -您也可以检查一个作用域是否有**自己的绑定:

+您也可以检查一个作用域是否有**自己的`绑定:

```js FunctionDeclaration(path) { @@ -1551,14 +1554,16 @@ FunctionDeclaration(path) { ### 提升变量声明至父级作用域 -有时你可能想要推送一个` VariableDeclaration ,这样你就可以分配给它。

+有时你可能想要推送一个` VariableDeclaration `,这样你就可以分配给它。

-
FunctionDeclaration(path) {
+```js
+
+FunctionDeclaration(path) {
   const id = path.scope.generateUidIdentifierBasedOnNode(path.node.id);
   path.remove();
   path.scope.parent.push({ id, init: path.node });
 }
-`
+``` ```diff - function square(n) { @@ -1617,9 +1622,10 @@ FunctionDeclaration(path) { } ``` -这些选项会通过`状态对象传递给插件访问者:

+这些选项会通过`状态`对象传递给插件访问者:

-
export default function({ types: t }) {
+```js
+export default function({ types: t }) {
   return {
     visitor: {
       FunctionDeclaration(path, state) {
@@ -1629,7 +1635,7 @@ FunctionDeclaration(path) {
     }
   }
 }
-`
+``` 这些选项是特定于插件的,您不能访问其他插件中的选项。 @@ -1657,7 +1663,7 @@ export default function({ types: t }) { ## 在插件中启用其他语法 -插件可以启用babylon plugins,以便用户不需要安装/启用它们。 这可以防止解析错误,而不会继承语法插件。

+插件可以启用`babylon plugins`,以便用户不需要安装/启用它们。 这可以防止解析错误,而不会继承语法插件。

```js export default function({ types: t }) { @@ -1693,21 +1699,22 @@ export default function({ types: t }) { 10 | 11 | "Hovering over a node highlights the \ 12 | corresponding part in the source code", - + * * * # 构建节点 -编写转换时,通常需要构建一些要插入的节点进入AST。 如前所述,您可以使用` babel-types 包中的builder 方法。

+编写转换时,通常需要构建一些要插入的节点进入AST。 如前所述,您可以使用babel-types 包中的
builder方法。

-

构建器的方法名称就是您想要的节点类型的名称,除了第一个字母小写。 例如,如果您想建立一个 MemberExpression 您可以使用 t.memberExpression(...).

+

构建器的方法名称就是您想要的节点类型的名称,除了第一个字母小写。 例如,如果您想建立一个 MemberExpression 您可以使用 t.memberExpression(...).

这些构建器的参数由节点定义决定。 有一些正在做的工作,以生成易于阅读的文件定义,但现在他们都可以在此处找到。.

节点定义如下所示:

-
defineType("MemberExpression", {
+```js
+defineType("MemberExpression", {
   builder: ["object", "property", "computed"],
   visitor: ["object", "property"],
   aliases: ["Expression", "LVal"],
@@ -1726,34 +1733,41 @@ export default function({ types: t }) {
     }
   }
 });
-`
+``` 在这里你可以看到关于这个特定节点类型的所有信息,包括如何构建它,遍历它,并验证它。 -通过查看 ` 生成器 ` 属性, 可以看到调用生成器方法所需的3个参数 (` t. 情况 `). +通过查看 `生成器` 属性, 可以看到调用生成器方法所需的3个参数 (`t. 情况`). ```js + 生成器: ["object", "property", "computed"], + ``` -> 请注意,有时在节点上可以定制的属性比``构建器数组包含的属性更多。 这是为了防止生成器有太多的参数。 在这些情况下,您需要手动设置属性。 一个例子是 ClassMethod .

- +> 请注意,有时在节点上可以定制的属性比`构建器`数组包含的属性更多。 这是为了防止生成器有太多的参数。 在这些情况下,您需要手动设置属性。 一个例子是`ClassMethod ` -
// Example
-// because the builder doesn't contain `async` as a property
+```js
+// 示例
+// 因为构造器不包含`async`作为属性
 var node = t.classMethod(
   "constructor",
   t.identifier("constructor"),
   params,
   body
 )
-// set it manually after creation
+// 创建完成后需要手动设置
 node.async = true;
-``
-> -> You can see the validation for the builder arguments with the `fields` object. -> -> ```js +``` + +> 你可以通过`fields`对象看到构建器参数的验证。 + + + + + + +```js fields: { object: { validate: assertNodeType("Expression") @@ -1770,9 +1784,11 @@ fields: { } ``` -You can see that `object` needs to be an `Expression`, `property` either needs to be an `Expression` or an `Identifier` depending on if the member expression is `computed` or not and `computed` is simply a boolean that defaults to `false`. +你可以看到`object`需要是`Expression`, ` property `需要是` Expression `或` Identifier `取决于成员表达式是` computed `还是不是` computed `, ` computed `只是一个默认为` false `的布尔值。 + -So we can construct a `MemberExpression` by doing the following: + +因此,我们可以通过以下步骤构造一个`MemberExpression`: ```js t.memberExpression( @@ -1782,21 +1798,24 @@ t.memberExpression( ); ``` -Which will result in: +这将导致: ```js object.property ``` -However, we said that `object` needed to be an `Expression` so why is `Identifier` valid? -Well if we look at the definition of `Identifier` we can see that it has an `aliases` property which states that it is also an expression. +然而,我们说过`object`需要是`Expression`,那么为什么`Identifier`是有效的呢? + + + +如果我们看看`Identifier`的定义,我们可以看到它有一个`aliases`属性,表明它也是一个表达式。 ```js aliases: ["Expression", "LVal"], ``` -So since `MemberExpression` is a type of `Expression`, we could set it as the `object` of another `MemberExpression`: +所以由于`MemberExpression`是`Expression`的一种类型,我们可以将它设置为另一个`MemberExpression`的`object`: ```js t.memberExpression( @@ -1808,23 +1827,23 @@ t.memberExpression( ) ``` -Which will result in: +这将导致: ```js member.expression.property ``` -It's very unlikely that you will ever memorize the builder method signatures for every node type. So you should take some time and understand how they are generated from the node definitions. +您不太可能记住每个节点类型的构建器方法签名。因此,您应该花一些时间来理解它们是如何从节点定义生成的。 -You can find all of the actual [definitions here](https://github.com/babel/babel/tree/master/packages/babel-types/src/definitions) and you can see them [documented here](https://github.com/babel/babel/blob/master/doc/ast/spec.md) +你会发现所有的定义 [在这里](https://github.com/babel/babel/tree/master/packages/babel-types/src/definitions) ,并且你可以看到它们的文档 [在这里](https://github.com/babel/babel/blob/master/doc/ast/spec.md) * * * # 最佳实践 -## Create Helper Builders and Checkers +## 创建Helper Builders和Checkers -It's pretty simple to extract certain checks (if a node is a certain type) into their own helper functions as well as extracting out helpers for specific node types. +将某些检查(如果某个节点是某种类型)提取到它们自己的helper函数中,以及提取特定节点类型的helper非常简单。 ```js function isAssignment(node) { @@ -1838,13 +1857,16 @@ function buildAssignment(left, right) { ## 尽量避免遍历抽象语法树(AST) -Traversing the AST is expensive, and it's easy to accidentally traverse the AST more than necessary. This could be thousands if not tens of thousands of extra operations. +遍历AST的开销很大,而且很容易意外地超出必要的范围遍历AST。这可能需要成千上万的额外操作。 + + -Babel optimizes this as much as possible, merging visitors together if it can in order to do everything in a single traversal. +Babel尽可能地优化了这一点,将访问者合并在一起,以便在一次遍历中完成所有操作。 ### 及时合并访问者对象 -When writing visitors, it may be tempting to call `path.traverse` in multiple places where they are logically necessary. +当写访问者对象时,可能很容易用`path.traverse`。在逻辑上需要的多个地方遍历。 + ```js path.traverse({ @@ -1860,7 +1882,7 @@ path.traverse({ }); ``` -However, it is far better to write these as a single visitor that only gets run once. Otherwise you are traversing the same tree multiple times for no reason. +然而,将它们写成只运行一次的单个访问者要好得多。否则,您将无缘无故地多次遍历同一棵树。 ```js path.traverse({ @@ -1875,7 +1897,9 @@ path.traverse({ ### 可以手动查找就不要遍历 -It may also be tempting to call `path.traverse` when looking for a particular node type. +它可能也很容易被称为`path.traverse`。查找特定节点类型时,遍历。 + + ```js const nestedVisitor = { @@ -1891,7 +1915,7 @@ const MyVisitor = { }; ``` -However, if you are looking for something specific and shallow, there is a good chance you can manually lookup the nodes you need without performing a costly traversal. +但是,如果您正在寻找特定的、浅层的节点,那么您可以手动查找所需的节点,而无需执行代价高昂的遍历。 ```js const MyVisitor = { @@ -1919,14 +1943,12 @@ const MyVisitor = { }; ``` -但是,每当调用`FunctionDeclaration()时都会创建一个新的访问者对象。 That can be costly, because Babel does some processing each time a new -visitor object is passed in (such as exploding keys containing multiple types, -performing validation, and adjusting the object structure). Because Babel stores -flags on visitor objects indicating that it's already performed that processing, -it's better to store the visitor in a variable and pass the same object each -time.

+但是,每当调用`FunctionDeclaration()`时都会创建一个新的访问者对象。 + +这可能是昂贵的,因为每次传入一个新的访问者对象时,Babel都会进行一些处理(比如分解包含多种类型的键、执行验证和调整对象结构)。因为Babel在访问者对象上存储标志,表明它已经执行了该处理,所以最好将访问者存储在一个变量中,并每次传递相同的对象。 -
const nestedVisitor = {
+```js
+const nestedVisitor = {
   Identifier(path) {
     // ...
   }
@@ -1937,7 +1959,7 @@ const MyVisitor = {
     path.traverse(nestedVisitor);
   }
 };
-`
+``` 如果您在嵌套的访问者中需要一些状态,像这样: @@ -1957,7 +1979,7 @@ const MyVisitor = { }; ``` -您可以将它作为状态传递给`traverse()方法,并有权访问this`在访问者中。 +您可以将它作为状态传递给`traverse()`方法,并有权访问`this`在访问者中。 ```js const nestedVisitor = { @@ -2008,9 +2030,10 @@ const MyVisitor = { } ``` -我们忽略了类可以嵌套的事实,使用遍历的话,上面我们也会得到一个嵌套的`构造函数:

+我们忽略了类可以嵌套的事实,使用遍历的话,上面我们也会得到一个嵌套的`构造函数`:

-
class Foo {
+```js
+class Foo {
   constructor() {
     class Bar {
       constructor() {
@@ -2019,11 +2042,11 @@ const MyVisitor = {
     }
   }
 }
-`
+``` ## 单元测试 -有几种主要的方法来测试babel插件:快照测试,AST测试和执行测试。 对于这个例子,我们将使用 jest ,因为它支持盒外快照测试。 我们在这里创建的示例是托管在这个 repo.

+有几种主要的方法来测试babel插件:快照测试,AST测试和执行测试。 对于这个例子,我们将使用 jest `,因为它支持盒外快照测试。 我们在这里创建的示例是托管在这个 repo`.

首先我们需要一个babel插件,我们将把它放在src / index.js中。 @@ -2043,10 +2066,11 @@ const MyVisitor = { ### 快照测试 -接下来,用`` npm install --save-dev babel-core jest 安装我们的依赖关系, -那么我们可以开始写我们的第一个测试:快照。 快照测试允许我们直观地检查我们的babel插件的输出。 我们给它一个输入,告诉它一个快照,并将其保存到一个文件。 我们检查快照到git中。 这允许我们来看看我们什么时候影响了我们任何试用例子测试的输出。 它也给出了使用差异在拉请求的时候。 当然,您可以用任何测试框架来做到这一点,但是要更新一下快照就像jest -u 一样简单.

+接下来,用`` npm install --save-dev babel-core jest `安装我们的依赖关系, +那么我们可以开始写我们的第一个测试:快照。 快照测试允许我们直观地检查我们的babel插件的输出。 我们给它一个输入,告诉它一个快照,并将其保存到一个文件。 我们检查快照到git中。 这允许我们来看看我们什么时候影响了我们任何试用例子测试的输出。 它也给出了使用差异在拉请求的时候。 当然,您可以用任何测试框架来做到这一点,但是要更新一下快照就像`jest -u `一样简单.

-
// src/__tests__/index-test.js
+```js
+// src/__tests__/index-test.js
 const babel = require('babel-core');
 const plugin = require('../');
 
@@ -2059,53 +2083,57 @@ it('works', () => {
   const {code} = babel.transform(example, {plugins: [plugin]});
   expect(code).toMatchSnapshot();
 });
-``
+``` -这给了我们一个快照文件在`` src / __ tests __ / __ snapshots __ / index-test.js.snap .

+这给了我们一个快照文件在`` src / __ tests __ / __ snapshots __ / index-test.js.snap `. -
exports[`test works 1`] = `
+```js
+exports[`test works 1`] = `
 "
 var bar = 1;
 if (bar) console.log(bar);"
 `;
-``
+``` -如果我们在插件中将“bar”更改为“baz”并再次运行,则可以得到以下结果: +如果我们在插件中将`bar`更改为`baz`并再次运行,则可以得到以下结果: ```diff + 接收到的值与存储的快照1不匹配。 - - Snapshot - + Received +- Snapshot ++ Received + +@@ -1,3 +1,3 @@ - @@ -1,3 +1,3 @@ - " - -var bar = 1; - -if (bar) console.log(bar);" - +var baz = 1; - +if (baz) console.log(baz);" +- var bar = 1; +- if (bar) console.log(bar); ++ var baz = 1; ++if (baz) console.log(baz); ``` -我们看到我们对插件代码的改变如何影响了我们插件的输出 如果输出看起来不错,我们可以运行`jest -u 来更新快照。

+我们看到我们对插件代码的改变如何影响了我们插件的输出 如果输出看起来不错,我们可以运行`jest -u `来更新快照。

AST 测试

-

除了快照测试外,我们还可以手动检查AST。 这是一个简单但是脆弱的例子。 对于更多涉及的情况,您可能希望利用Babel-遍历。 它允许您用访问者键指定一个对象,就像您使用插件本身。

+除了快照测试外,我们还可以手动检查AST。 这是一个简单但是脆弱的例子。 对于更多涉及的情况,您可能希望利用Babel-遍历。 它允许您用`访问者`键指定一个对象,就像您使用插件本身。 -
it('contains baz', () => {
+```js
+it('contains baz', () => {
   const {ast} = babel.transform(example, {plugins: [plugin]});
   const program = ast.program;
   const declaration = program.body[0].declarations[0];
   assert.equal(declaration.id.name, 'baz');
   // or babelTraverse(program, {visitor: ...})
 });
-`
+``` ### Exec Tests -在这里,我们将转换代码,然后评估它的行为是否正确。 请注意,我们在测试中没有使用``assert。 这确保如果我们的插件做了奇怪的操作,如意外删除断言线,但测试仍然失败。

+在这里,我们将转换代码,然后评估它的行为是否正确。 请注意,我们在测试中没有使用`assert`。 这确保如果我们的插件做了奇怪的操作,如意外删除断言线,但测试仍然失败。 -
it('foo is an alias to baz', () => {
+```js
+it('foo is an alias to baz', () => {
   var input = `
     var foo = 1;
     // test that foo was renamed to baz
@@ -2119,9 +2147,9 @@ if (bar) console.log(bar);"
   var res = f();
   assert(res === 1, 'res is 1');
 });
-``
+``` -Babel核心使用类似的方法去获取快照和执行测试。

+Babel核心使用类似的方法`去获取快照和执行测试。 ### [`babel-plugin-tester`](https://github.com/kentcdodds/babel-plugin-tester) @@ -2158,4 +2186,5 @@ pluginTester({ * * * -> ***对于将来的更新,请跟随 @thejameskyle 和 @babeljs 的Twitter。

\ No newline at end of file +> ***对于将来的更新,请跟随 @thejameskyle +> 和` @babeljs ` 的Twitter。***