Giter Site home page Giter Site logo

blog-12's Introduction

关于 Babel、Babel plugins、Babel macros

compiler or transpiler?

(本文叙述的内容所涉及到的相关特性以Babel V7为准)

Babel :原名6to5, 2015年更名为babel,取名灵感来自于BabelFish 是一种虚拟出来的鱼,可以翻译任何物种的语言,,Babel也不负盛名,成了目前最知名的JavaScript语法“编译器”、一种源码转译源码到编译器(source-to-source)。官方说是compiler、也有人称之为transpiler(转译器),stackoverflow也有人提问Is Babel a compiler or transpiler?。总结一下大致意思,编译器是将一种语言转为另一种相对低级一些的语言(比如 Java到字节码。C到二进制)。 转移器是将一种语言转为另一种同等级别的代码(比如JavaScript to python),那么ES6 到 ES5 算同一个level的转换吗??自行理解吧。 对我们来说Babel是JavaScript语法转换工具或是翻译工具,因此不必太纠结compiler还是transpiler 。除了能够转换标准ES以及草案之外,它还支持JSX、typescript、flow。 另外babel方便的插件扩展机制,众多的开发者也相继开发出许多babel插件,让babel不只是作为一个工具 更是一个平台

babel的组成

babel是一个组合套装,拆分为了几十个包,均在@babel命名空间下

核心部件

  • @babel/parser (原名Babylon) 基于 acorn and acorn-jsx。用于语法解析
  • @babel/traverse 遍历AST 调用Plugin转换代码
  • @babel/generator 将AST生成代码
  • @babel/core 转换流程控制器,整合 parser、traverse plugin generator 完成语法转换。
  • 一堆plugin+5个preset

babel的编译流程如下:

  1. Parse 词法分析得到 Tokens,JS代码生成 抽象语法树(AST)
  2. Transform 遍历语法树,通过Babel-plugin操作 AST,完成语法树的改变。
  3. Code Generate 将新的语法树生成代码。

总结 :输入字符串 -> @babel/parser parser -> AST -> traverse ->@babe/plugin--xxxx-> AST -> @babel/generator -> 输出字符串

辅助包

  • @babel/types@babel/template@babel/helpers@babel/code-frames` 方便操作语法树、提供的工具类,写插件的时候会用到
  • @babel/polyfill。ES标准中包括两部分: 新增的语法+新增的API。 Babel编译流程只提供了语法的转换(比如const、let、async、await、箭头函数等), babel/polyfill是ES标准新增的原生对象以及API的模拟实现(比如PromiseMapSetObject.assignArray.fromObject.assig等),其实@babel/polyfill上仅仅是core-js和regenerator-runtime两个包的简单封装,之前版本中我们都是在文件的收口处手动的引入babel polyfill。整个PolyFill文件较大、没必要全部导入、也没必要手动导入、在babel V7之后推荐新的使用方法。通过配置 env选项的时候添加 "useBuiltIns":"usage",会自动分析代码根据需求来按需针对性的导入polyfill。另外polyfill是全局导入的,像Array.prototype.includes还会修改原生函数的原型。polyfill只包含了超过stage4以上的规范。如果要使用更高级草案标准的API 需要自己手动去引入core-js里面的函数。举个例子 .babel配置文件如下
        {
            "presets": [["@babel/env", {
                "useBuiltIns": "usage"
            }]]
        }

源码

            var str = '   foo  ';
            str.trimLeft();
            str.padStart(10);

babel 编译输出结果

        require("core-js/modules/es7.string.pad-start");
        var str = '   foo  ';
        str.trimLeft();
        str.padStart(10);

String.prototype.padStart 是 ES(7) 正式版的规范,因此会自动引入pad-start的polyfill、而 trimLeft截止目前还在 Stage 3。需要自己手动引入core-js(-pure)/features/string/trim-left。(每一项新特性,要最终纳入ECMAScript规范中,TC39拟定了一个处理过程,称为TC39 process、其**包含5个阶段,Stage 0 ~ Stage 4 stage0开始有初级的想法,不断升级到stage4基本才算确定是进入正式标准了的规范)

既然说到polyfill了 还得说一个函数 :regeneratorruntime, 这个函数也被算在了babel-polyfill里面了,它不是ES规范新增的API,只是babel在做async/await 语法转换的时候,转换后的结果代码调用到了这个函数,转换结果并没有提供这个函数的定义,所以要让代码能够正常运行,必须要引入regeneratorruntime这个函数,如果配置babel的时候 env里面设置 "useBuiltIns": "usage"属性,业务里面如果用到async/await 会自动引入regeneratorruntime这个polyfill

  • @babel/runtime:功能类似babel-polyfill,提供了一些帮助函数(regeneratorruntime 等一些代码正常执行需要的辅助函数)以及非实例方法(Array.from,Object.assign、Promise、Map等)的shim,一般用于library或plugin中,最大好处减少工具代码重复引用、且不会污染全局作用域,配合babel/plugin-transform-runtime插件使用 更多信息可以参考babel-polyfill 和 runtime的区别

其他工具

  • @babel/cli babel命令行工具
  • @babel/node nodeJS环境下使用babel的功能,由于性能问题不得在生产环境下使用
  • @babel/register 通过绑定node.js的require来自动转译require引用的js代码文件

Babel Plugin

上面已经介绍过babel语法转换流程主要包含三个步骤:词法分析语法分析生成AST->Plugin操作AST->生成代码

那么什么是AST?AST 即抽象语法树 它是源代码语法结构的一种抽象表示(类似浏览器页面用抽象为DOM树来表示)、方便对编程语言进行语法分析、语法检查、代码风格检查、语法转换、代 码优化等,UglifyJS、ESLint、JSDoc、Babel等常用的工具,这背后的就是对AST的应用,另外像目前的mpvue、taro 也都用到AST转换的**。babel通过语法分析,把代码解析为AST,这是基于ESTree稍微改造的结构(EStree可以认为是一些权威大佬联合制定的标准)。JavaScript代码的语法树可以通过astexplorer中查看。可以把AST类比DOM Tree,那么Babel相当于操作AST的jQuery

Babel语法转换的本质是:将源代码解析为AST后、对AST进行遍历(先序深度优先遍历),并对节点进行操作(增、删、改,最后将AST生成代码的过程,这个操作过程采用的是Visitors 模式对节点进行访问,每一个visitor在babel里面有babel-plugin承担。babel-core负责解析语法,每一个语法的转换需要一个单独的plugin来变更AST,转换后的AST生成代码也由babel自动完成。babel官方包内置了一些丰富的plugin来完成语法的转译(比如最新的ES标准/草案、JSX、typescript、flow),一个插件只完成一个特定的功能,启用该功能需要在babel的配置文件里面添加即可,为了方便分享,也方便集中配置,把插件的列表封装为preset,preset即是一堆插件的组合合。babel的插件启用需要单独配置,支持多种配置方式 使用 babel.config.js .babelrc .babelrc.js 或者package.json中添加 babel的属性.官方也有详细的介绍

Plugin配置

官网写的很清楚,感觉没必要多写)简单提一下,Preset是plugin组合,翻译过来叫"预设",官网的提供的一些预设(env、flow、react、typescript)我们也能很容易的创造自己的预设,比如create-react-app ,react-native都是自定义了preset,preset可以有预设和plugin组成。自定义预设也跟配置 babel.config.js类似。另外babel在执行的时候

        module.exports = () => ({
            presets: [
                require("@babel/preset-env"),
            ],
            plugins: [
                [require("@babel/plugin-proposal-class-properties"), { loose: true }],
                require("@babel/plugin-proposal-object-rest-spread"),
            ],
        });

babel7之后推荐使用@babel/preset-env来转换正式版ES语法,目前preset-env===ES3+ES5+ES2015+ES2016+ES2017,还有三个比较常用的preset( typescript flow react),之前的stage0~stage3 在Babel 7中已经被干掉了。对于目前处于草案的语法需要手动添加相关plugin。具体到特定的语法是否为草案还是正式标准可以去babel的插件列表看一下,不要被这么多插件吓到,其实也没多少,不必熟知,尽量都要清楚每个插件的作用。另外babel-preset-env 还支持根据指定代码的环境,来过滤语法转换,用来表示来支持到什么程度,比如浏览器版本,Node版本,通过这样等减少一些不必要的转换,降低冗余代码

关于plugin和preset执行顺序,Babel遍历到每个AST节点的时候,按规则来执行plugin和preset。执行规则就是 :先执顺序行完所有Plugin,再逆序执行Preset。这个配置的时候可能注意。有时候出错的话,可能跟这个执行顺序有关。仔细想一下,那么多节点,都要被每个插件轮流执行一遍。这个对性能影响也是很大的。所以尽量用具体的babel plugin来配置,干掉stage的preset从一方面避免了这个问题。如果不配置插件任何插件及preset、babel对代码不会做做任何转换 将会输出最初的代码。关于具体的配置,简单介绍下对async/await以及decorators配置方式。

关于 async/await

  • async/await 在ES7的正式版发布了,目前属于ES的正式标准,理论上来说配置下env即可以使用了,但是上面也有提到 babel的plugin只做语法转换,通过env的配置可以将async,await语法转换为旧式的语法。但是转换后的的代码里面使用了Promise 和 regeneratorRuntime 这两个API。

如果配置为

    {
        "presets": [["@babel/env"]]
    }

源码

    async function name(params) {
        await 1
    }

转化后的代码为 很明显这个代码是执行的话 报错 regeneratorRuntime,对于不支持Promise的浏览器也会 报错。在配置中添加 "useBuiltIns": "usage"

    {
        "presets": [["@babel/env", {
            "useBuiltIns": "usage"
        }]]
    }

编译之后的代码为 发现顶部多了如下两行代码 引入了 如上两个方法的polyfill

    require("regenerator-runtime/runtime");
    require("core-js/modules/es6.promise");

刚才说过了,还可以使用 @babel/plugin-transform-runtime结合@babel/runtime 来实现polyfill的功能。 需要安装两个依赖包

    npm install --save @babel/runtime
    npm install --svae-dev @babel/plugin-transform-runtime

修改babel的配置

{
    "presets": [["@babel/env"]],
    "plugins": [
    ["@babel/plugin-transform-runtime", {
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "useESModules": false
    }]]
}

运行babel之后编译的代码为 ,可以看到 _regenerator(即regeneratorRuntime)以局部变量的形式被引入了,非上面的全局作用域。另外 asyncToGenerator也作为一个工具函数被提取至@babel/runtime,通过导入包,以局部变量的形式在代码里面呈现。另外由于编译后的代码在执行的时候用到了 @babel/runtime 包里面的代码,因此安装依赖的包的时候,根据原则将安装到dependencies里面(--save) 。那么还有个问题,asyncToGenerator用了Promise,对于不支持Promise的浏览器依然会报错。

关于 decrotors 语法支持

“Decorators”从好三年前就开始炒的特性,这个特性在Typescriptangularmobx中广为使用,然而经过多年的努力,Decorators的是目前仍然处于Stage2,babel官方从babel 5 就有支持 Decorators的plugin,因为草案不稳定的原因,在babel6中从内置插件中移除了对“装饰器”语法转换的支持,之前我们都是使用 “民间”的第三方插件(babel-plugin-transform-decorators-legacy)来转换装饰器语法,babel7中把这个插件纳入了babel的内置插件列表中,名字也改为 @babel/plugin-proposal-decorators 。配置起来比较简单

{
    "presets": [["@babel/env"]],
    "plugins": [["@babel/plugin-proposal-decorators", {
        "legacy": true
    }]]
}

一些第三方Babel Plugin

官方提供的plugin 一般是用来对目前成型的标准或者草案进行通用的转换,那么我们是否可以根据自己的需求,来定制自己的转换规则?受益于babel提供的插件扩展机制,我们可以完成对语法树进行操作,从而对代码的转换,只需要关注语法的transform这个关键的步骤,自定义Visitor,利用babel提供的API可以方便的操作语法树的节点,其他的工作比如语法树解析,遍历算法、代码生成等由 Babel帮我们自动完成这些步骤。如果对编写babel插件有兴趣,可以去参考babel插件手册。(这篇文章写很详细,熟悉之后写插件没啥问题了, 如果第一次看,不用害怕,细心的看下去,多看一遍,可能一些陌生的词汇有些唬人, 利用Babel API操作AST ,相当于使用jQuery来操作DOM树)。一定要善于利用显示AST的神器astexplorer 或者 http://esprima.org/demo/parse.html 或者 使用JAVASCRIPT AST VISUALIZER可视化查看语法树结构

社区中有一些为了满足特定需求的plugin,对实际项目开发中很有用处,这里举例介绍两个,我们通过读这些插件的源码也能有助于我们掌握babel的插件开发

  • babel-plugin-import 在import阶段进行转换,只导入需要的文件,将导入整个库的代码转为只导入单个的组件文件、避免导入整个库。适用于 antd, antd-mobile, lodash, material-ui
  • babel-plugin-preval 在nodeJS环境下执行代码返回函数执行的结果。
  • babel-plugin-codegen 在nodeJS环境下执行代码,返回的结果字符串,作为JavaScript语句表达式插入到代码中 比如楼上功能更强
  • [babel-plugin-transform-remove-console] 功能如其名

Babel macros

这个时候就有一种需求:不改变babel配置的情况下,在应用的代码里面动态为特定的代码应用指定的语法转换,babel macros就是满足为了这个需求,babel macros 的想法的来源于create-react-app的一个issue,目前macros 已经在这个项目中使用了。宏的功能与babel plugin功能上差不多,babel plugin的引入需要在babel的配置文件中添加配置,只是宏是让我们可以对手动指定的代码进行语法转换的。babel 有许多plugin,也有许多的macro可用,不同的宏功能不同。macro的出现让我们在代码中显示的使用babel的转换功能。

如果是使用macros 需要先安装babel-plugin-macros,启用macros的能力 npm install --save-dev babel-plugin-macros 。babel-plugin-macros 不是一个具体的macro,只是给macro提供了一个运行的平台,不具有转换特定代码的功能,这里可以查看具体可用macros列表 根据需求安装对应的macros 在代码里面使用即可。说了这么多还是不直观,下面介绍下具体如何使用宏。

  • penv.macro 它能用来在一个代码文件中统一管理你的环境变量, 并且只保留与当前环境变量匹配的值。与当前环境无关的代码被移除,确保不会将与指定环境不相干的代码发布到对应的环境上.
  1. npm install --save-dev babel-plugin-macros 修改babel配置文件在plugins中 添加 macros (使用宏功能必须有这一步,提供一个运行宏的环境)
  2. npm install penv.macro --save-dev
  3. 编写源码
import env from 'penv.macro'

const BASE_URL = env({
  development: 'https://development.example.com',
  staging: 'https://staging.example.com',
  production: (() => 'https://production.example.com')(),
})

假设编译的时候 env 为 process.env.NODE_ENV,编译后的结果为,功能上与webpack中的DefinePlugin功能类似,然而使用起来要感觉舒服很多。统一管理,不依赖webpack,方便就近维护

    const BASE_URL = (() => 'https://production.example.com')()

宏为我们提供了解决问题的另一种思路,所有的宏都以/macro为后缀,在代码中显示引用,在需要的地方调用具体的 宏,也能方便对功能的理解(使用插件,语法被转换了,可能不清楚是什么原因造成的),增加新的宏也不需要修改babel的配置,同时也能避免插件顺序配置问题导致的冲突。这里有一些可用的宏列表 另外把插件转换为宏也很容易,比如 上面介绍的 preval、和codegen插件就有对应的 preval.macro 和 codegen.macro。也有人基于codegen.macro来实现国际化方案。另外更好的国际化方案可以使用 @lingui/macro

总结

通过本篇 希望能对babel有一些了解,更多的是介绍介绍一些思路以及学习方向,很多东西要写,受限于笔墨,不敢写太多,里面涉及到的知识点,都可以深入来研究,另外文章中涉及链接文章都有很好的指导意义。

学习资料 这些好好看完就成babel高手了

blog-12's People

Contributors

kunkun12 avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.