Source resolve a babel

We directly create a project, and then execute:

npm install -D @babel/cli

We use the latest version 7.8.0

Create a test1.js test:

/* test.js */
const fn = () => {}
new Promise(() => {})
class Test {}
const c = [1, 2, 3].includes(1)
//测试插件1
var a=10;

Create a babel profile .babelrc (to not write any configuration):

/* .babelrc */
{
 
}

Then we execute:

npx babel test1.js -o test1.babel.js --config-file .babelrc

Finally, look at the results test1.babel.js:

/* test.js */
const fn = () => {};

new Promise(() => {});

class Test {}

const c = [1, 2, 3].includes(1); //测试插件1

var a = 10;

Oh? Why do not change at all? With questions we look at the source code -

In order to better study the source of babel, we go directly to a github clone:

git clone https://github.com/babel/babel.git

Then when we execute:

npx babel test1.js -o test1.babel.js --config-file 

When we directly open packages / babel-cli / bin / babel.js:

#!/usr/bin/env node

require("../lib/babel");

packages/babel-cli/src/babel/index.js:

#!/usr/bin/env node

import parseArgv from "./options";
import dirCommand from "./dir";
import fileCommand from "./file";

const opts = parseArgv(process.argv);

if (opts) {
  const fn = opts.cliOptions.outDir ? dirCommand : fileCommand;
  fn(opts).catch(err => {
    console.error(err);
    process.exitCode = 1;
  });
} else {
  process.exitCode = 2;
}

packages/babel-cli/src/babel/file.js:

export default async function({
  cliOptions,
  babelOptions,
}: CmdOptions): Promise<void> {
	 if (cliOptions.filenames.length) {
    await files(cliOptions.filenames);
  } else {
    await stdin();
  }
}

Then execute files method:

 async function files(filenames: Array<string>): Promise<void> {
    if (!cliOptions.skipInitialBuild) {
      await walk(filenames);
    }

And then do walk method:

async function walk(filenames: Array<string>): Promise<void> {
    
        try {
          return await util.compile(
          );
        } catch (err) {
       
    );
  }

We can see the final implementation of the util compile method (packages / babel-cli / src / babel / util.js):

export function compile(
  filename: string,
  opts: Object | Function,
): Promise<Object> {
  opts = {
    ...opts,
    caller: CALLER,
  };

  return new Promise((resolve, reject) => {
    babel.transformFile(filename, opts, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

It can be seen after the babel-cli, we get the parameters passed in:

  1. Source file test1.js
  2. Enter the file test1.babel.js
  3. babel profile .babelrc
npx babel test1.js -o test1.babel.js --config-file .babelrc

Compiled code is then acquired by the method babel.transformFile babel-core, and finally passed babel-cli The output of the last configuration -o compiled code.

So we focus on what babel.transformFile method

packages/babel-core/src/index.js:

export {
  transformFile
} from "./transform-file";
const transformFileRunner = gensync<[string, ?InputOptions], FileResult | null>(
	//加载配置文件
    const config: ResolvedConfig | null = yield* loadConfig(options);
    if (config === null) return null;
	//加载源文件
    const code = yield* fs.readFile(filename, "utf8");
    //开始编译
    return yield* run(config, code);
  },
);

Let's talk about loadConfig method, remember we passed .babelrc not, after reading the file and then get inside the presets with plugins property, presets are a set of preset with plugin, plugins are a set of plugin, we try to change it our .babelrc configuration file:

/* .babelrc */
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "useBuiltIns": "usage",
      "targets": "ie >= 8"
    }]
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "corejs":false
    }],
    ["./plugins/PluginTest1.js"]
  ]
}

Then execute:

npx babel test1.js -o test1.babel.js --config-file .babelrc

result:

import "core-js/modules/es7.array.includes";
import "core-js/modules/es6.string.includes";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import "core-js/modules/es6.promise";

/* test.js */
var fn = function fn() {};

new Promise(function () {});

var Test = function Test() {
  _classCallCheck(this, Test);
};

var c = [1, 2, 3].includes(1); //测试插件1

var aaa = 10;

About Configuration Files, prestes with plugins we then slowly introduced, we continue to look at the babel-core is how to load our configuration file.
babel-core / src / config / full.js:

export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
  inputOpts: mixed,
): Handler<ResolvedConfig | null> {
  const result = yield* loadPrivatePartialConfig(inputOpts);
  if (!result) {
    return null;
  }
  const { options, context } = result;

  const optionDefaults = {};
  const passes = [[]];
  try {
    const { plugins, presets } = options;
}

We can see that we get the plugins with presets property profile and then through all preset preset execution method provided with the plugin with plugin:

import * as context from “…/index”;

const loadDescriptor = makeWeakCache(function*(
  { value, options, dirname, alias }: UnloadedDescriptor,
  cache: CacheConfigurator<SimpleContext>,
): Handler<LoadedDescriptor> {

    try {
     const api = {
      ...context,
      ...makeAPI(cache),
    };
      item = value(api, options, dirname);
    } catch (e) {
    
      throw e;
    }
  }

api that we passed babel-core objects, options that we pass parameters, dirname is our current folder directory babel-test.

Let's advance to write a plug-in PluginTest1.js (the variable var a = 10 became var aaa = 10):

module.exports = function (api, options, dirname) {
    let t = api.types;
    console.log(options)
    console.log(dirname)
    return {
        visitor: {
            VariableDeclarator: {
                enter(path,state) {
                    console.log(path)
                    if(path.node.id.name == 'a'){
                        path.node.id.name="aaa";
                    }
                },
                exit() {
                    console.log("Exited!");
                }
            }
        }
    }
};

We plug again after specific analysis Kazakhstan, we can see that the plug-in parameters corresponding to our offer is api, options, dirname:

module.exports = function (api, options, dirname) {

Well, we finished the configuration file packages / babel-core / src / transform-file.js continue to go down before then:

const transformFileRunner = gensync<[string, ?InputOptions], FileResult | null>(
	//加载配置文件
    const config: ResolvedConfig | null = yield* loadConfig(options);
    if (config === null) return null;
	//加载源文件
    const code = yield* fs.readFile(filename, "utf8");
    //开始编译
    return yield* run(config, code);
  },
);

We can see, after you get a run config direct execution method
packages / babel-core / src / transformation / index.js:

  config: ResolvedConfig,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<FileResult> {
  const file = yield* normalizeFile(
    config.passes,
    normalizeOptions(config),
    code,
    ast,
  );

  const opts = file.opts;
  try {
    yield* transformFile(file, config.passes);
  } catch (e) {
    e.message = `${opts.filename ?? "unknown"}: ${e.message}`;
    if (!e.code) {
      e.code = "BABEL_TRANSFORM_ERROR";
    }
    throw e;
  }

  let outputCode, outputMap;
  try {
    if (opts.code !== false) {
      ({ outputCode, outputMap } = generateCode(config.passes, file));
    }
  } catch (e) {
    e.message = `${opts.filename ?? "unknown"}: ${e.message}`;
    if (!e.code) {
      e.code = "BABEL_GENERATE_ERROR";
    }
    throw e;
  }

  return {
    metadata: file.metadata,
    options: opts,
    ast: opts.ast === true ? file.ast : null,
    code: outputCode === undefined ? null : outputCode,
    map: outputMap === undefined ? null : outputMap,
    sourceType: file.ast.program.sourceType,
  };
}

The code a bit more, do not square! Our step-by-step

First we see the implementation of a normalizeFile method:

export function* run(
  config: ResolvedConfig,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<FileResult> {
  const file = yield* normalizeFile(
    config.passes,
    normalizeOptions(config),
    code,
    ast,
  );

packages/babel-core/src/transformation/normalize-file.js:

export default function* normalizeFile(
  pluginPasses: PluginPasses,
  options: Object,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<File> {
  code = `${code || ""}`;

  if (ast) {
    if (ast.type === "Program") {
      ast = t.file(ast, [], []);
    } else if (ast.type !== "File") {
      throw new Error("AST root must be a Program or File node");
    }
    ast = cloneDeep(ast);
  } else {
    ast = yield* parser(pluginPasses, options, code);
  }

You can see, if we pass not ast, it would by parser way to get a ast (Abstract Syntax Tree) object.

Ast So what is it?

In computer science, AST (Abstract Syntax Tree, AST), or simply syntax tree (Syntax tree), is an abstract representation of the source code syntax structure. It performance programming language syntax structure in the form of a tree, each node of the tree have expressed a structured source code.

Well, a heavyweight debut this babel of parser
parser is a parser Babel. Originally from Acorn project fork out. Acorn is very fast, easy to use, and for non-standard characteristics (as well as those of future standard features) designed a plug-in based architecture

After parse through conversion babel of our code:

/* test.js */
const fn = () => {}
new Promise(() => {})
class Test {}
const c = [1, 2, 3].includes(1)
//测试插件1
var a=10;

It will be converted into:

{
  "type": "Program",
  "start": 0,
  "end": 120,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 14,
      "end": 33,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 20,
          "end": 33,
          "id": {
            "type": "Identifier",
            "start": 20,
            "end": 22,
            "name": "fn"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "start": 25,
            "end": 33,
            "id": null,
            "expression": false,
            "generator": false,
            "async": false,
            "params": [],
            "body": {
              "type": "BlockStatement",
              "start": 31,
              "end": 33,
              "body": []
            }
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "ExpressionStatement",
      "start": 34,
      "end": 55,
      "expression": {
        "type": "NewExpression",
        "start": 34,
        "end": 55,
        "callee": {
          "type": "Identifier",
          "start": 38,
          "end": 45,
          "name": "Promise"
        },
        "arguments": [
          {
            "type": "ArrowFunctionExpression",
            "start": 46,
            "end": 54,
            "id": null,
            "expression": false,
            "generator": false,
            "async": false,
            "params": [],
            "body": {
              "type": "BlockStatement",
              "start": 52,
              "end": 54,
              "body": []
            }
          }
        ]
      }
    },
    {
      "type": "ClassDeclaration",
      "start": 56,
      "end": 69,
      "id": {
        "type": "Identifier",
        "start": 62,
        "end": 66,
        "name": "Test"
      },
      "superClass": null,
      "body": {
        "type": "ClassBody",
        "start": 67,
        "end": 69,
        "body": []
      }
    },
    {
      "type": "VariableDeclaration",
      "start": 70,
      "end": 101,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 76,
          "end": 101,
          "id": {
            "type": "Identifier",
            "start": 76,
            "end": 77,
            "name": "c"
          },
          "init": {
            "type": "CallExpression",
            "start": 80,
            "end": 101,
            "callee": {
              "type": "MemberExpression",
              "start": 80,
              "end": 98,
              "object": {
                "type": "ArrayExpression",
                "start": 80,
                "end": 89,
                "elements": [
                  {
                    "type": "Literal",
                    "start": 81,
                    "end": 82,
                    "value": 1,
                    "raw": "1"
                  },
                  {
                    "type": "Literal",
                    "start": 84,
                    "end": 85,
                    "value": 2,
                    "raw": "2"
                  },
                  {
                    "type": "Literal",
                    "start": 87,
                    "end": 88,
                    "value": 3,
                    "raw": "3"
                  }
                ]
              },
              "property": {
                "type": "Identifier",
                "start": 90,
                "end": 98,
                "name": "includes"
              },
              "computed": false
            },
            "arguments": [
              {
                "type": "Literal",
                "start": 99,
                "end": 100,
                "value": 1,
                "raw": "1"
              }
            ]
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "VariableDeclaration",
      "start": 110,
      "end": 119,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 114,
          "end": 118,
          "id": {
            "type": "Identifier",
            "start": 114,
            "end": 115,
            "name": "a"
          },
          "init": {
            "type": "Literal",
            "start": 116,
            "end": 118,
            "value": 10,
            "raw": "10"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

Small partners can be used directly: the online version of the converter ast

Here Insert Picture Description
Let's look at a simple parse method:

export default function* parser(
  pluginPasses: PluginPasses,
  { parserOpts, highlightCode = true, filename = "unknown" }: Object,
  code: string,
): Handler<ParseResult> {
  try {
    const results = [];
    for (const plugins of pluginPasses) {
      for (const plugin of plugins) {
        const { parserOverride } = plugin;
        if (parserOverride) {
          const ast = parserOverride(code, parserOpts, parse);

          if (ast !== undefined) results.push(ast);
        }
      }
    }

    if (results.length === 0) {
      return parse(code, parserOpts);
    } else if (results.length === 1) {
      yield* []; // If we want to allow async parsers
      if (typeof results[0].then === "function") {
        throw new Error(
          `You appear to be using an async parser plugin, ` +
            `which your current version of Babel does not support. ` +
            `If you're using a published plugin, you may need to upgrade ` +
            `your @babel/core version.`,
        );
      }
      return results[0];
    }
    throw new Error("More than one plugin attempted to override parsing.");
  } catch (err) {
    if (err.code === "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED") {
      err.message +=
        "\nConsider renaming the file to '.mjs', or setting sourceType:module " +
        "or sourceType:unambiguous in your Babel config for this file.";
      // err.code will be changed to BABEL_PARSE_ERROR later.
    }

    const { loc, missingPlugin } = err;
    if (loc) {
      const codeFrame = codeFrameColumns(
        code,
        {
          start: {
            line: loc.line,
            column: loc.column + 1,
          },
        },
        {
          highlightCode,
        },
      );
      if (missingPlugin) {
        err.message =
          `${filename}: ` +
          generateMissingPluginMessage(missingPlugin[0], loc, codeFrame);
      } else {
        err.message = `${filename}: ${err.message}\n\n` + codeFrame;
      }
      err.code = "BABEL_PARSE_ERROR";
    }
    throw err;
  }
}

Code or a lot of, let's mention a related plug-ins with the parse part:

  for (const plugins of pluginPasses) {
      for (const plugin of plugins) {
        const { parserOverride } = plugin;
        if (parserOverride) {
          const ast = parserOverride(code, parserOpts, parse);

          if (ast !== undefined) results.push(ast);
        }
      }

So when we have plug-in provides a method of directly parserOverride go parserOverride plug-in to cover our resolve babel / parser of the.
I do not understand it does not matter Ha! ! parse specific usage we put behind the resolution.

After a simple reading parse, we continue to run method
packages / babel-core / src / transformation / index.js:

export function* run(
  config: ResolvedConfig,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<FileResult> {
	//获取ast对象
  const file = yield* normalizeFile();

  const opts = file.opts;
  try {
  	//执行转换操作
    yield* transformFile(file, config.passes);
  } catch (e) {
  }

We may continue to see a transformFile method, and then we ast objects passed transformFile method:

function* transformFile(file: File, pluginPasses: PluginPasses): Handler<void> {
  for (const pluginPairs of pluginPasses) {
    const passPairs = [];
    const passes = [];
    const visitors = [];

    for (const plugin of pluginPairs.concat([loadBlockHoistPlugin()])) {
      const pass = new PluginPass(file, plugin.key, plugin.options);

      passPairs.push([plugin, pass]);
      passes.push(pass);
      visitors.push(plugin.visitor);
    }

    for (const [plugin, pass] of passPairs) {
      const fn = plugin.pre;
      if (fn) {
        const result = fn.call(pass, file);

        yield* [];
        if (isThenable(result)) {
          throw new Error(
            `You appear to be using an plugin with an async .pre, ` +
              `which your current version of Babel does not support. ` +
              `If you're using a published plugin, you may need to upgrade ` +
              `your @babel/core version.`,
          );
        }
      }
    }

    // merge all plugin visitors into a single visitor
    const visitor = traverse.visitors.merge(
      visitors,
      passes,
      file.opts.wrapPluginVisitorMethod,
    );
    traverse(file.ast, visitor, file.scope);

    for (const [plugin, pass] of passPairs) {
      const fn = plugin.post;
      if (fn) {
        const result = fn.call(pass, file);

        yield* [];
        if (isThenable(result)) {
          throw new Error(
            `You appear to be using an plugin with an async .post, ` +
              `which your current version of Babel does not support. ` +
              `If you're using a published plugin, you may need to upgrade ` +
              `your @babel/core version.`,
          );
        }
      }
    }
  }
}

Or the phrase "Do not be square !! We look step by step," First, we see:

//遍历所有的插件,获取插件的visitor属性,然后传给visitors
 for (const plugin of pluginPairs.concat([loadBlockHoistPlugin()])) {
      const pass = new PluginPass(file, plugin.key, plugin.options);

      passPairs.push([plugin, pass]);
      passes.push(pass);
      visitors.push(plugin.visitor);
    }

So what visitor is it? Before we can see that there is a mention a custom plug-PluginTest1.js:

module.exports = function (api, options, dirname) {
    let t = api.types;
    console.log(options)
    console.log(dirname)
    return {
        visitor: {
            VariableDeclarator: {
                enter(path,state) {
                    console.log(path)
                    if(path.node.id.name == 'a'){
                        path.node.id.name="aaa";
                    }
                },
                exit() {
                    console.log("Exited!");
                }
            }
        }
    }
};

First talk about in advance, visitor after the fact, traverse to enable it to provide access to the abstract syntax tree ast objects

So the next step is a babel of another heavyweight traverse debut
Babel Traverse (traversing) module maintains a state of entire tree, and is responsible for replacement, remove and add nodes.

To return to the run method
packages / babel-core / src / transformation / index.js:

export function* run(
  config: ResolvedConfig,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<FileResult> {
 //获取ast对象(parser)
  const file = yield* normalizeFile();
  try {
  //(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点。
    yield* transformFile(file, config.passes);
  } catch (e) {
   
  }
({ outputCode, outputMap } = generateCode(config.passes, file));
 

可以看到执行了generateCode方法,这时babel的最后一个重量级选手babel-generator登场了

Babel Generator模块是 Babel 的代码生成器,它读取AST并将其转换为代码和源码映射(sourcemaps)。

最后run方法返回generator生成的代码:

return {
    metadata: file.metadata,
    options: opts,
    ast: opts.ast === true ? file.ast : null,
    code: outputCode === undefined ? null : outputCode,
    map: outputMap === undefined ? null : outputMap,
    sourceType: file.ast.program.sourceType,
  };

整个babel-cli到babel-core的过程随着我们的demo跟我们的源码就讲完了。

我们重新整理一下整个过程:

  1. babel-cli开始读取我们的参数(源文件test1.js、输出文件test1.babel.js、配置文件.babelrc)
  2. babel-core根据babel-cli的参数开始编译
  3. Babel Parser 把我们传入的源码解析成ast对象
  4. Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点(也就是结合我们传入的插件把es6转换成es5的一个过程)
  5. Babel Generator模块是 Babel 的代码生成器,它读取AST并将其转换为代码和源码映射(sourcemaps)。

好啦,到此我们算是把babel的整个过程简单的跑了一下,为了加深对每个流程的理解,我们不经过babel-core跟babel-cli单独去用一下parser、traverse、generator。

//我们的es6源码
const code = `
    const result=a*b;
    const result1=()=>{};
`;
const {parse}=require("@babel/parser");
const traverse =require("@babel/traverse").default;
const t = require("babel-types");
const generator = require("@babel/generator").default;

//把es6源码通过parser转换成ast对象
const ats=parse(code,{
    sourceType: "module"
});
//把ast对象通过traverse转换成es5代码
traverse(ats,{
    enter(path) {
        if (t.isIdentifier(path.node, { name: "a" })) {
          path.node.name = "aa";
        }
        if (path.isArrowFunctionExpression()){ //es6转换成es5
            path.arrowFunctionToExpression({
                // While other utils may be fine inserting other arrows to make more transforms possible,
                // the arrow transform itself absolutely cannot insert new arrow functions.
                allowInsertArrow: false,
                specCompliant: false,
              });
        }
    }
});
//通过generator转换ast最后输出es5代码
console.log(generator(ats));

我们运行一下代码:

$ node ./babel-test/demo/demo1.js 

结果输出:

{ 
  code: 'const result = aa * b;\n\nconst result1 = function () {};',
  map: null,
  rawMappings: null 
}

可以看到,最终我们实现了把es6的箭头函数转换成es5的过程。

代码中我们可以看到:

//把ast对象通过traverse转换成es5代码
traverse(ats,{
    enter(path) {
        if (t.isIdentifier(path.node, { name: "a" })) {
          path.node.name = "aa";
        }
        if (path.isArrowFunctionExpression()){ //es6转换成es5
            path.arrowFunctionToExpression({
                // While other utils may be fine inserting other arrows to make more transforms possible,
                // the arrow transform itself absolutely cannot insert new arrow functions.
                allowInsertArrow: false,
                specCompliant: false,
              });
        }
    }
});

我们打开一个官方的插件babel-plugin-transform-arrow-functions:

import { declare } from "@babel/helper-plugin-utils";
import type NodePath from "@babel/traverse";

export default declare((api, options) => {
  api.assertVersion(7);

  const { spec } = options;
  return {
    name: "transform-arrow-functions",

    visitor: {
      ArrowFunctionExpression(
        path: NodePath<BabelNodeArrowFunctionExpression>,
      ) {
        // In some conversion cases, it may have already been converted to a function while this callback
        // was queued up.
        if (!path.isArrowFunctionExpression()) return;

        path.arrowFunctionToExpression({
          // While other utils may be fine inserting other arrows to make more transforms possible,
          // the arrow transform itself absolutely cannot insert new arrow functions.
          allowInsertArrow: false,
          specCompliant: !!spec,
        });
      },
    },
  };
});

哈哈!! 是不是很简单呢? 其实babel也就是把很多的一些插件组合起来最终实现代码的转换,好啦~ 接下来我们就围绕babel的一些常用的插件做解析了

未完待续~~

欢迎志同道合的小伙伴一起学习、一起进步

欢迎入群~~~~~~~

参考:

https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-check-if-a-node-is-a-certain-type

Published 128 original articles · won praise 113 · views 340 000 +

Guess you like

Origin blog.csdn.net/vv_bug/article/details/103823257