最佳实践

Create Helper Builders and 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.

  1. function isAssignment(node) {
  2. return node && node.operator === opts.operator + "=";
  3. }
  4. function buildAssignment(left, right) {
  5. return t.assignmentExpression("=", left, right);
  6. }

尽量避免遍历抽象语法树(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.

Babel optimizes this as much as possible, merging visitors together if it can in order to do everything in a single traversal.

及时合并访问者对象

When writing visitors, it may be tempting to call path.traverse in multiple places where they are logically necessary.

  1. path.traverse({
  2. Identifier(path) {
  3. // ...
  4. }
  5. });
  6. path.traverse({
  7. BinaryExpression(path) {
  8. // ...
  9. }
  10. });

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.

  1. path.traverse({
  2. Identifier(path) {
  3. // ...
  4. },
  5. BinaryExpression(path) {
  6. // ...
  7. }
  8. });

可以手动查找就不要遍历

It may also be tempting to call path.traverse when looking for a particular node type.

  1. const nestedVisitor = {
  2. Identifier(path) {
  3. // ...
  4. }
  5. };
  6. const MyVisitor = {
  7. FunctionDeclaration(path) {
  8. path.get('params').traverse(nestedVisitor);
  9. }
  10. };

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.

  1. const MyVisitor = {
  2. FunctionDeclaration(path) {
  3. path.node.params.forEach(function() {
  4. // ...
  5. });
  6. }
  7. };

优化嵌套的访问者对象

当您嵌套访问者(visitor)时,把它们嵌套在您的代码中可能是有意义的。

  1. const MyVisitor = {
  2. FunctionDeclaration(path) {
  3. path.traverse({
  4. Identifier(path) {
  5. // ...
  6. }
  7. });
  8. }
  9. };

但是,每当调用`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.

  1. const nestedVisitor = {
  2. Identifier(path) {
  3. // ...
  4. }
  5. };
  6. const MyVisitor = {
  7. FunctionDeclaration(path) {
  8. path.traverse(nestedVisitor);
  9. }
  10. };

如果您在嵌套的访问者中需要一些状态,像这样:

  1. const MyVisitor = {
  2. FunctionDeclaration(path) {
  3. var exampleState = path.node.params[0].name;
  4. path.traverse({
  5. Identifier(path) {
  6. if (path.node.name === exampleState) {
  7. // ...
  8. }
  9. }
  10. });
  11. }
  12. };

您可以将它作为状态传递给traverse()</ 0>方法,并有权访问<code>this在访问者中。

  1. const nestedVisitor = {
  2. Identifier(path) {
  3. if (path.node.name === this.exampleState) {
  4. // ...
  5. }
  6. }
  7. };
  8. const MyVisitor = {
  9. FunctionDeclaration(path) {
  10. var exampleState = path.node.params[0].name;
  11. path.traverse(nestedVisitor, { exampleState });
  12. }
  13. };

留意嵌套结构

有时候在考虑给定的转换时,可能会忘记给定的转换结构可以是嵌套的。

例如,想象一下,我们想要查找构造函数 ClassMethod Foo ClassDeclaration.

  1. class Foo {
  2. constructor() {
  3. // ...
  4. }
  5. }
  1. const constructorVisitor = {
  2. ClassMethod(path) {
  3. if (path.node.name === 'constructor') {
  4. // ...
  5. }
  6. }
  7. }
  8. const MyVisitor = {
  9. ClassDeclaration(path) {
  10. if (path.node.id.name === 'Foo') {
  11. path.traverse(constructorVisitor);
  12. }
  13. }
  14. }

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

  1. class Foo {
  2. constructor() {
  3. class Bar {
  4. constructor() {
  5. // ...
  6. }
  7. }
  8. }
  9. }

单元测试

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

首先我们需要一个babel插件,我们将把它放在src / index.js中。

  1. <br />module.exports = function testPlugin(babel) {
  2. return {
  3. visitor: {
  4. Identifier(path) {
  5. if (path.node.name === 'foo') {
  6. path.node.name = 'bar';
  7. }
  8. }
  9. }
  10. };
  11. };

快照测试

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

  1. // src/__tests__/index-test.js
  2. const babel = require('babel-core');
  3. const plugin = require('../');
  4. var example = `
  5. var foo = 1;
  6. if (foo) console.log(foo);
  7. `;
  8. it('works', () => {
  9. const {code} = babel.transform(example, {plugins: [plugin]});
  10. expect(code).toMatchSnapshot();
  11. });
  12.  

这给了我们一个快照文件在 src / tests / snapshots / index-test.js.snap

  1. exports[`test works 1`] = `
  2. "
  3. var bar = 1;
  4. if (bar) console.log(bar);"
  5. `;

如果我们在插件中将“bar”更改为“baz”并再次运行,则可以得到以下结果:

  1. 接收到的值与存储的快照1不匹配。
  2. - Snapshot
  3. + Received
  4. @@ -1,3 +1,3 @@
  5. "
  6. -var bar = 1;
  7. -if (bar) console.log(bar);"
  8. +var baz = 1;
  9. +if (baz) console.log(baz);"

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

AST 测试

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

  1. 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</>。 这确保如果我们的插件做了奇怪的操作,如意外删除断言线,但测试仍然失败。

  1. it('foo is an alias to baz', () => {
  2. var input = `
  3. var foo = 1;
  4. // test that foo was renamed to baz
  5. var res = baz;
  6. `;
  7. var {code} = babel.transform(input, {plugins: [plugin]});
  8. var f = new Function(`
  9. ${code};
  10. return res;
  11. `);
  12. var res = f();
  13. assert(res === 1, 'res is 1');
  14. });
  15.  

Babel核心使用类似的方法</>去获取快照和执行测试。

babel-plugin-tester

这个包使测试插件更容易。 如果您熟悉ESLint的 RuleTester您应该对这是熟悉的。 您可以看看the docs去充分了解可能的情况,但这里有一个简单的例子:

  1. import pluginTester from 'babel-plugin-tester';
  2. import identifierReversePlugin from '../identifier-reverse-plugin';
  3. pluginTester({
  4. plugin: identifierReversePlugin,
  5. fixtures: path.join(__dirname, '__fixtures__'),
  6. tests: {
  7. 'does not change code with no identifiers': '"hello";',
  8. 'changes this code': {
  9. code: 'var hello = "hi";',
  10. output: 'var olleh = "hi";',
  11. },
  12. 'using fixtures files': {
  13. fixture: 'changed.js',
  14. outputFixture: 'changed-output.js',
  15. },
  16. 'using jest snapshots': {
  17. code: `
  18. function sayHi(person) {
  19. return 'Hello ' + person + '!'
  20. }
  21. `,
  22. snapshot: true,
  23. },
  24. },
  25. });

对于将来的更新,请跟随 @thejameskyle@babeljs 的Twitter。