babel源碼解析一

我們直接創(chuàng)建一個(gè)工程陌僵,然后執(zhí)行:

npm install -D @babel/cli

我們用的是最新版本7.8.0

創(chuàng)建一個(gè)test1.js測(cè)試:

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

創(chuàng)建一個(gè)babel配置文件.babelrc(先不寫任何配置):

/* .babelrc */
{
 
}

然后我們執(zhí)行:

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

最后看一下結(jié)果test1.babel.js:

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

new Promise(() => {});

class Test {}

const c = [1, 2, 3].includes(1); //測(cè)試插件1

var a = 10;

哦笨蚁?為啥一點(diǎn)變化都沒有呢? 我們帶著疑問研究一下源碼~

為了更好的研究babel的源碼,我們直接去github clone一份:

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

然后當(dāng)我們執(zhí)行:

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

的時(shí)候闰非,我們直接打開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();
  }
}

然后執(zhí)行files方法:

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

然后執(zhí)行walk方法:

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

可以看到最后執(zhí)行了util的compile方法(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);
    });
  });
}

可以看到剩盒,經(jīng)過babel-cli后,獲取我們傳入的參數(shù):

  1. 源文件test1.js
  2. 輸入文件test1.babel.js
  3. babel配置文件.babelrc
npx babel test1.js -o test1.babel.js --config-file .babelrc

然后通過babel-core的babel.transformFile方法后獲取編譯后的代碼台夺,最后babel-cli根據(jù)傳入的-o配置輸出最后編譯完成的代碼径玖。

所以我們重點(diǎn)研究一下babel.transformFile方法

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);
  },
);

我們先說一下loadConfig方法,還記得我們傳入的.babelrc不颤介,讀取這個(gè)文件后然后獲取里面的presets跟plugins屬性梳星,presets是一組preset跟plugin、plugins是一組plugin滚朵,我們?cè)囍囊幌挛覀兊?babelrc配置文件:

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

然后執(zhí)行:

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

結(jié)果:

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); //測(cè)試插件1

var aaa = 10;

關(guān)于配置文件冤灾、prestes跟plugins我們之后慢慢介紹,我們繼續(xù)看一下babel-core是怎么加載我們的配置文件的辕近。
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;
}

可以看到我們獲取了配置文件的plugins跟presets屬性然后遍歷所有的preset跟plugin執(zhí)行preset跟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就是我們傳入的babel-core對(duì)象韵吨、options是我們傳入的參數(shù)、dirname是我們當(dāng)前文件夾目錄babel-test亏推。

我們先提前寫一個(gè)插件PluginTest1.js(把變量var a=10變成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!");
                }
            }
        }
    }
};

插件我們之后再具體解析哈学赛,我們可以看到年堆,我們提供的插件對(duì)應(yīng)的參數(shù)也就是api、options盏浇、dirname:

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

好啦变丧,說完配置文件我們繼續(xù)接著之前packages/babel-core/src/transform-file.js往下走:

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);
  },
);

可以看到,獲取完config后直接執(zhí)行了run方法
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,
  };
}

代碼有點(diǎn)多绢掰,不要方痒蓬!我們一步一步的來

首先我們看到執(zhí)行了一個(gè)normalizeFile方法:

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);
  }

可以看到,如果我們傳入不是ast的話滴劲,就會(huì)通過parser方法去獲取一個(gè)ast(Abstract Syntax Tree)對(duì)象攻晒。

那么ast是什么呢?

在計(jì)算機(jī)科學(xué)中班挖,抽象語法樹(Abstract Syntax Tree鲁捏,AST),或簡(jiǎn)稱語法樹(Syntax tree)萧芙,是源代碼語法結(jié)構(gòu)的一種抽象表示给梅。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)双揪。

好吧动羽,到此babel的一個(gè)重量級(jí)選手parser登場(chǎng)了
parser是 Babel 的解析器。最初是 從Acorn項(xiàng)目fork出來的渔期。Acorn非吃讼牛快,易于使用疯趟,并且針對(duì)非標(biāo)準(zhǔn)特性(以及那些未來的標(biāo)準(zhǔn)特性) 設(shè)計(jì)了一個(gè)基于插件的架構(gòu)

通過babel的parse轉(zhuǎn)換后我們的代碼:

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

就會(huì)被轉(zhuǎn)換成:

{
  "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"
}

小伙伴可以直接使用:在線版的ast轉(zhuǎn)換器

在這里插入圖片描述

我們先簡(jiǎn)單的看一下parse方法:

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;
  }
}

代碼還是很多拘哨,我們先提一下一個(gè)跟parse的插件有關(guān)的部分:

  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);
        }
      }

所以當(dāng)我們插件中有提供parserOverride方法的時(shí)候就直接走我們插件的parserOverride去覆蓋babel/parser的解析了。
不懂也沒關(guān)系哈Q赴臁宅静!parse具體用法我們放到后面解析章蚣。

簡(jiǎn)單看完parse后站欺,我們繼續(xù)run方法
packages/babel-core/src/transformation/index.js:

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

  const opts = file.opts;
  try {
    //執(zhí)行轉(zhuǎn)換操作
    yield* transformFile(file, config.passes);
  } catch (e) {
  }

可以看到繼續(xù)執(zhí)行了transformFile方法,然后把我們ast對(duì)象傳給了transformFile方法:

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.`,
          );
        }
      }
    }
  }
}

還是那句話 “不要方O舜埂矾策!我們一步一步來看”,首先我們看到:

//遍歷所有的插件峭沦,獲取插件的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);
    }

那么visitor是什么呢? 可以看到我們之前有提到一個(gè)自定義一個(gè)插件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!");
                }
            }
        }
    }
};

先提前說一下吼鱼,visitor其實(shí)就是提供給之后的traverse使它能夠去訪問抽象語法樹ast對(duì)象

所以接下來就是babel的又一個(gè)重量級(jí)選手traverse登場(chǎng)了
Babel Traverse(遍歷)模塊維護(hù)了整棵樹的狀態(tài)蓬豁,并且負(fù)責(zé)替換绰咽、移除和添加節(jié)點(diǎn)。

再次回到run方法
packages/babel-core/src/transformation/index.js:

export function* run(
  config: ResolvedConfig,
  code: string,
  ast: ?(BabelNodeFile | BabelNodeProgram),
): Handler<FileResult> {
 //獲取ast對(duì)象(parser)
  const file = yield* normalizeFile();
  try {
  //(遍歷)模塊維護(hù)了整棵樹的狀態(tài)地粪,并且負(fù)責(zé)替換取募、移除和添加節(jié)點(diǎn)。
    yield* transformFile(file, config.passes);
  } catch (e) {
   
  }
({ outputCode, outputMap } = generateCode(config.passes, file));
 

可以看到執(zhí)行了generateCode方法蟆技,這時(shí)babel的最后一個(gè)重量級(jí)選手babel-generator登場(chǎng)了

Babel Generator模塊是 Babel 的代碼生成器玩敏,它讀取AST并將其轉(zhuǎn)換為代碼和源碼映射(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,
  };

整個(gè)babel-cli到babel-core的過程隨著我們的demo跟我們的源碼就講完了质礼。

我們重新整理一下整個(gè)過程:

  1. babel-cli開始讀取我們的參數(shù)(源文件test1.js旺聚、輸出文件test1.babel.js、配置文件.babelrc)
  2. babel-core根據(jù)babel-cli的參數(shù)開始編譯
  3. Babel Parser 把我們傳入的源碼解析成ast對(duì)象
  4. Babel Traverse(遍歷)模塊維護(hù)了整棵樹的狀態(tài)眶蕉,并且負(fù)責(zé)替換砰粹、移除和添加節(jié)點(diǎn)(也就是結(jié)合我們傳入的插件把es6轉(zhuǎn)換成es5的一個(gè)過程)
  5. Babel Generator模塊是 Babel 的代碼生成器,它讀取AST并將其轉(zhuǎn)換為代碼和源碼映射(sourcemaps)造挽。

好啦伸眶,到此我們算是把babel的整個(gè)過程簡(jiǎn)單的跑了一下,為了加深對(duì)每個(gè)流程的理解刽宪,我們不經(jīng)過babel-core跟babel-cli單獨(dú)去用一下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轉(zhuǎn)換成ast對(duì)象
const ats=parse(code,{
    sourceType: "module"
});
//把a(bǔ)st對(duì)象通過traverse轉(zhuǎn)換成es5代碼
traverse(ats,{
    enter(path) {
        if (t.isIdentifier(path.node, { name: "a" })) {
          path.node.name = "aa";
        }
        if (path.isArrowFunctionExpression()){ //es6轉(zhuǎn)換成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轉(zhuǎn)換ast最后輸出es5代碼
console.log(generator(ats));

我們運(yùn)行一下代碼:

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

結(jié)果輸出:

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

可以看到嘴秸,最終我們實(shí)現(xiàn)了把es6的箭頭函數(shù)轉(zhuǎn)換成es5的過程。

代碼中我們可以看到:

//把a(bǔ)st對(duì)象通過traverse轉(zhuǎn)換成es5代碼
traverse(ats,{
    enter(path) {
        if (t.isIdentifier(path.node, { name: "a" })) {
          path.node.name = "aa";
        }
        if (path.isArrowFunctionExpression()){ //es6轉(zhuǎn)換成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,
              });
        }
    }
});

我們打開一個(gè)官方的插件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,
        });
      },
    },
  };
});

哈哈1幼弧岳掐! 是不是很簡(jiǎn)單呢? 其實(shí)babel也就是把很多的一些插件組合起來最終實(shí)現(xiàn)代碼的轉(zhuǎn)換饭耳,好啦~ 接下來我們就圍繞babel的一些常用的插件做解析了

未完待續(xù)~~

歡迎志同道合的小伙伴一起學(xué)習(xí)串述、一起進(jìn)步

歡迎入群~~~~~~~

參考:

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寞肖,隨后出現(xiàn)的幾起案子纲酗,更是在濱河造成了極大的恐慌,老刑警劉巖新蟆,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件觅赊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡琼稻,警方通過查閱死者的電腦和手機(jī)吮螺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸠补,你說我怎么就攤上這事萝风。” “怎么了紫岩?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵闹丐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我被因,道長(zhǎng)卿拴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任梨与,我火速辦了婚禮堕花,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粥鞋。我一直安慰自己缘挽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布呻粹。 她就那樣靜靜地躺著壕曼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪等浊。 梳的紋絲不亂的頭發(fā)上腮郊,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音筹燕,去河邊找鬼轧飞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撒踪,可吹牛的內(nèi)容都是我干的过咬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼制妄,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼掸绞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起耕捞,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤衔掸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后砸脊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體具篇,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年凌埂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诗芜。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞳抓,死狀恐怖埃疫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情孩哑,我是刑警寧澤栓霜,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站横蜒,受9級(jí)特大地震影響胳蛮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丛晌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一仅炊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧澎蛛,春花似錦抚垄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毁兆,卻和暖如春浙滤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背气堕。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工瓷叫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人送巡。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓摹菠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親骗爆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子次氨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • babel官網(wǎng) babel 介紹 Babel 是一個(gè)通用的多用途 JavaScript 編譯器。通過 Babel ...
    鋒享前端閱讀 1,817評(píng)論 0 10
  • 前言 半年前也寫過一篇babel的簡(jiǎn)單使用文章摘投,當(dāng)時(shí)看了下babel的文檔煮寡,但是很多地方還不理解,所以文章里沒有怎...
    mercurygear閱讀 46,021評(píng)論 9 100
  • babel官網(wǎng) babel 介紹 Babel 是一個(gè)通用的多用途 JavaScript 編譯器犀呼。通過 Babel ...
    不得不愛XIN閱讀 1,127評(píng)論 0 9
  • Babel是一個(gè)廣泛使用的轉(zhuǎn)碼器幸撕,可以將ES6代碼轉(zhuǎn)為ES5代碼,從而在現(xiàn)有環(huán)境執(zhí)行外臂。 這意味著坐儿,你可以現(xiàn)在就用 ...
    yichen_china閱讀 1,312評(píng)論 0 3
  • 作者:小 boy (滬江前端開發(fā)工程師)本文原創(chuàng)貌矿,轉(zhuǎn)載請(qǐng)注明作者及出處炭菌。原文地址:https://www.smas...
    iKcamp閱讀 2,757評(píng)論 0 18