最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【前端工程化】篇五 未来已来-Babel

    正文概述 掘金(BWrong)   2021-03-18   550

    字数:4627, 阅读时间:12分钟,点击阅读原文

    【前端工程化】系列文章链接:

    • 01 扬帆起航-开发环境
    • 02 白璧微瑕-包管理器
    • 03 席卷八荒-Webpack基础篇
    • 04 席卷八荒-Webpack进阶篇

    示例代码仓库:github.com/BWrong/dev-…

    声明:如按照文中代码执行报错,请检查依赖版本是否和示例代码仓库中一致。

    概述

    作为一个有追求的前端,如果你没有听过Babel的大名,那就真的是out了。不过说实话不太了解Babel的话,其实对日常开发的影响也不大,因为很多脚手架已经配置好了,不用自己折腾。但是如果你想玩转前端工程化这块,这就是绕不过的坎。

    babel官方定义为一款JavaSscript编译器。

    这里的“下一代”是泛指新的、暂时还不被浏览器广泛支持的,而不是局限于某个确定的版本。

    简单来说,浏览器(特别是一些低端浏览器,说你呢IE)对ECMAScript规范的支持是滞后的,新的语法虽然能够给我们带来更好的编程体验,却奈何往往不能不能直接在用户的浏览器运行,所以就需要在浏览器运行这些代码前将其转换成支持的语法,而Babel就是负责这个的,当然它的能力远不止如此,后面我们一一道来。

    Babel主要做的就是这几点:

    • 语法转换:将新的语法转换成兼容性更好的旧语法。
    • 特性垫片:通过Polyfill 实现目标环境中缺少的特性,将环境的特性差异垫平,说白了还是做兼容。
    • more...
    // Babel Input: ES2015 arrow function
    [1, 2, 3].map((n) => n + 1);
    
    // Babel Output: ES5 equivalent
    [1, 2, 3].map(function(n) {
      return n + 1;
    });
    

    运行方式

    Babel的运行过程分为三个阶段:解析 --- 转换 --- 生成

    【前端工程化】篇五 未来已来-Babel

    1. 解析:解析代码,并生成AST抽象语法树
    2. 转换:将上一步的AST转换成目标环境支持的AST
    3. 生成:根据转换后的AST生成目标代码

    不难发现,第二步就是Babel执行的核心操作。但Babel自身是不具备任何转换能力,必须借助一些插件来实现语法的转换。关于插件,后续会详细介绍,这里只需记住,插件赋予了babel强大的能力,没有插件它算个球。

    如果想了解具体的转换过程,请阅读从babel讲到AST。

    Babel7

    作者在写这篇文章的时候,babel的版本是7.10.0babel7带来了很多新的特性,为了避免给大家造成误导,这里先列出babel7的变化,避免和之前版本的配置混淆。

    主要的变化如下:

    • preset:推荐使用 env替代es201x;删除stage-x ,因为这些草案中的特性不稳定,维护这些preset会浪费大量精力,所以官方放弃了。如果需要使用可以自己显式的添加对应的插件。
    • npm包名:将所有babel-*更改为@babel/*,更加符合npm命名规范。
      • babel-cli -> @babel/cli
      • babel-preset-env -> @babel/preset-env,简写为 @babel/env
    • 对低于nodejs6.0不再提供支持。
    • @babel/cli中不再包含@babel/node,如要使用需要单独安装。
    • 提供babel-upgrade帮助开发者从6到7的版本升级。

    使用和配置

    使用方式

    babel的使用场景主要分为两种:命令行和构建工具。

    • 命令行

    babel为命令行提供了@babel/cli工具,支持在命令行直接执行babel命令。

    首先,安装必要的包:

    npm install --save-dev @babel/core @babel/cli
    

    安装好在项目中执行babel命令就可以进行转换了。

    babel src --out-dir lib # 将src下的文件处理后输出到lib
    
    • 构建系统

      在构建工具中使用才是我们使用babel更常用的方式。这里我们以webpack为例。

      babel提供了一个loader:babel-loader,使用这个loader我们就能很轻松实现代码转换。

    module: {
      rules: [
        {
          test: /\.js$/,
          loader: 'babel-loader',
          options: {
          	// 配置项
          }
        }
      ]
    }
    
    
    • 运行时(不推荐)

      babel还提供了运行时环境下的处理,可以在代码运行时,实时进行转换。不过并不推荐这种方式,因为它是在代码运行时实时进行转换,效率较低。

      除了以上方式,还有一些其他的使用方式,详情可以查看babel配置。

    配置文件

    前面的方式我们都没有提供配置,所以输出和输入是一样的,这完全没有意义。所以我们一般会提供一些配置,来告诉babel如何进行转换。

    babel7支持多种方式来进行配置:

    • babel.config.json(项目范围)

    • .babelrc.json(相对文件)

    • package.json中添加babel字段

    {
      "name": "my-package",
      "version": "1.0.0",
      "babel": {
        "presets": [ ... ],
        "plugins": [ ... ],
      }
    }
    

    前面两种配置文件都使用了json格式,其实babel还支持其他格式,如 .js.cjs(Commonjs)和.mjs(ESModule),它们相对于json格式更加灵活,可以在配置中进行编程处理,但是会使配置无法静态分析,失去可缓存性。

    // babel.config.js
    module.exports = {
      presets: [],
      plugins: []
    };
    
    

    所有方式的配置内容都是类似的,我们可以根据自己的使用场景进行灵活选择。

    babel虽然有多种方式来写配置文件,但是配置的内容都是类似的,主要包含pluginspresets两部分,不过这里需要注意几点:

    • 配置格式:pluginspresets配置项均为数组,如果每项不需要配置的话,直接放入数组(值为项的字符串名称);如果要配置的话,则需要把格式改成数组,该数组的第一个元素是该项的字符串名字,第二个元素是该项的配置对象。

      这里填写的plugins和presets,babel会自动检查是否已被安装在node_modules下面,当然,也可以使用相对路径来使用本地文件。

    "plugins": [
        [
          "component",
          {
            "libraryName": "element-ui",
            "styleLibraryName": "theme-chalk"
          }
        ],
        "@babel/plugin-transform-runtime"
     ]
    
    • 执行顺序:plugins会从前到后执行,presets会从后向前执行,且plugins会比presets先执行

      presets逆序执行的目的是为了保证向后兼容,我们在配置的时候按照规范的时间顺序列出即可,如['es2015', 'stage-1'],这样就会先使用stage-1,否则就会出现错误。

    • 短名称:在配置plugin和preset名字的时候,可以省略名字中的babel-plugin-preset-字样,仅使用插件的名字就可以了。

    {
      "plugins": [
        "myPlugin", // 完整名字:"babel-plugin-myPlugin"
        "@org/myPlugin" // 完整名字:"@org/babel-plugin-myPlugin"
      ],
      "presets":[
        "myPreset", // 完整名字:"babel-preset-myPreset"
        "@babel/myPreset" // 完整名字:@babel/preset-myPreset
      ]
    }
    

    关于一些常用pluginspresets的常用配置,我们在下面会有详细介绍。

    核心

    babel的核心除了@babel/core就是pluginspresets了,我们一般使用babel其实就是在折腾这两块内容。下面我们就来一探究竟。

    Plugin

    前面我们说了,babel的转换完全依靠插件,babel拥有的能力完全取决于给它配置了哪些插件。

    babel的插件分为两种:

    • 语法插件:赋予babel解析特定语法的能力,可以理解一个为babel服务的翻译官,仅做翻译解析工作,工作在AST转换阶段。

    • 转译插件:转译插件即把特定的语法转换成指定标准的语法。

      比如将箭头函数 (x) => x 转换成 function (x) {return x},只需要配置箭头函数转译插件即可。

    {
      "plugins": [
        "@babel/plugin-transform-arrow-functions"
      ]
    }
    

    插件的使用大概分为两步:

    1. 将插件添加到配置文件的plugins属性中,可根据插件文档做相应配置。
    2. 安装插件到项目中,使用npm或者yarn均可

    每个插件的功能是单一的,所以一般转换需求要使用的插件数量比较多,这时候可以考虑presets。常用的插件和用法可以查看babel-plugins

    自定义插件

    除了使用社区提供的插件,我们也可以自己开发。

    module.exports = function() {
      return {
        visitor: {
          Identifier(path) {
            const name = path.node.name;
            // reverse the name: JavaScript -> tpircSavaJ
            path.node.name = name
              .split("")
              .reverse()
              .join("");
          },
        },
      };
    }
    

    这里只是一个简单的例子,实际的插件开发远不是如此简单,可以执行查看Babel 插件手册学习。

    Presets

    在实际的开发中,我们如果用ES6来进行开发的话,那需要转换的语法是非常多的,难道要一个一个去配置吗?还要不要配陪妹子了?

    这个时候,就该presets登场了。presets可以理解为一组插件的集合,就像你去德克士直接点一个全家桶,而不必一个一个给服务员说要鸡腿、鸡翅、薯条...(如果想和服务员小姐姐多待一会,也不是不可以)

    presets的来源主要有以下几种途径:

    • 官方:官方为常用的环境提供了preset。

      • @babel/preset-env
      • @babel/preset-flow
      • @babel/preset-react
      • @babel/preset-typescript
      • stage-X (试验性 Presets)
    • 社区:在npm上面可以找到许多社区维护的preset,比如vue和react都有维护了自己的preset。

    • 自定义:在有特殊需求的时候,可以自己创建preset。

    module.exports = () => ({
      presets: [ // 引用其他preset
        require("@babel/preset-env"), 
      ],
      plugins: [ // 要集合的插件
        [require("@babel/plugin-proposal-class-properties"), { loose: true }], 
        require("@babel/plugin-proposal-object-rest-spread"),
      ],
    });
    

    注意:Stage-x presets 中的任何转换都是针对未发布的 Javascript 特性(如 ES6 / ES2015)的更改,都是不稳定的,将来这些提案可能会发生变化,请谨慎使用,特别是第三阶段前的提案。从v7开始,所有针对标准提案阶段的功能所编写的预设(stage preset)都已被弃用,官方已经移除了 @babel/preset-stage-x

    @babel/preset-env

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

    这个是我们最常用的,也是官方现在推荐的preset,它是一个智能预设,我们无需再关心特定环境下所需的转换插件(只需要指定目标环境),就可以使用最新的语法。除了能够减少使用难度,还能够使转换出来的代码体积更小。

    preset-env会根据配置的目标环境中缺少的功能,选择对应的插件进行必要的转换和polyfill,而目标环境支持的特性就不会转换,而不是无脑的一把梭哈。

    如果不进行配置,它将使用默认配置,即 latest(env维护的插件列表),会将最新的JS特性(ES2015,ES2016...,不包含 stage 阶段),将其转换成ES5代码。所以如果使用了提案阶段的语法,则需要自行额外配置。

    如何指定目标环境
    • 配置文件:通过配置文件的targets属性指定目标环境
    {
      "presets": [
        ["env", {
          "targets": {
            "browsers": ["last 2 versions", "safari >= 7"],
            "node": "12.10"
          }
        }]
      ]
    }
    
    • browserslistrc:如果是浏览器或者Electron,官方推荐使用.browserslistrc文件来指定目标环境(可以和autoprefixer、stylelint等其他工具共享配置)。

      不过如果在配置文件中设置了 targetsignoreBrowserslistConfig.browserslistrc中配置的内容将不会生效。

    # .browserslistrc
    last 2 Chrome versions
    

    具体用法请查看browserslist 的更多配置。

    其他配置

    除了targets外,还有一些配置项也比较常用:

    • modules: 用于指定转换后的模块规范,可设置为"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为"auto"

    • useBuiltIns:此选项配置如何@babel/preset-env处理polyfill,在后面介绍polyfill我们会详细介绍。

    • corejs:用于指定corejs的版本,仅当使用了useBuiltIns: usage或者useBuiltIns: entry此配置才有效,使用时一定要配置正确的版本,否则会报错。

      当前默认的版本是2.0,不过建议使用3.0版本,因为2.0已经不会再添加新的特性了,如果使用了一些较新的语法,将会无法转换。

    除了上述配置,还有一些不常用的配置,可以查看官方文档。

    Polyfill

    前面我们的配置其实是不完美的,转换出来的代码直接在浏览器运行是可能会出问题的。这是因为babel默认只转换了语法,而对于一些新的API是并没有处理的,如Generator、Set、Maps、Proxy、Promise 等全局对象及其方法都不会转码,如果我们在代码中使用到了就会报错。

    此时,我们就需要做Polyfill,也就是所谓的'垫片',作用就是把浏览器的差异垫平,人话就是通过模拟为这些浏览器补全缺失的全局对象及API。

    @babel/polyfill(废弃)

    早期的方案就是@babel/polyfill(早期的名字是babel-polyfill,内部集成了 core-jsregenerator),在入口或者webpack配置的entry中引入@babel/polyfill,此工具会模拟完整的ES2015 +环境(不包含第4阶段的提议),会创建一些全局对象,或者在已有全局对象的原型上补全方法,让我们可以使用PromiseWeakMapArray.from等新的API。

    import '@babel/polyfill'; 
    let include = [1,2,3].includes(1);
    new Promise((resolve, reject) => {
        resolve();
    });
    

    现在,我们的代码就可以在低版本的浏览器运行了。不过此方案有一些缺点:

    • 文件体积大:因为@babel/polyfill会把所有API都补全,不管你有没有使用,比如你可能在项目中只使用了一个新的API,结果他给你梭哈了,把所有的API都整进去了。所以最后打出来的包会非常大,非常浪费。
    • 污染全局变量:@babel/polyfill会在全局创建对象或者修改全局对象的原型链,所以会对全局变量进行污染。

    对于第一个缺点,我们可以使用前面@babel/preset-env提供的useBuiltIns配置,当设置为usage时(须要制定corejs版本),babel会检查我们的代码,只引入我们代码中需要的polyfill,这样打包出来的文件就会小很多。

    // babel.config.json
    {
      "presets": [
        [
          "@babel/env",
          {
            "useBuiltIns": "usage",
            "corejs": 3
          }
        ]
      ]
    }
    
    

    例如前面的代码:

    // import '@babel/polyfill';  使用useBuiltIns: 'usage'时,会自动导入需要的垫片,所以不用再手动引入
    let include = [1,2,3].includes(1);
    new Promise((resolve, reject) => {
        resolve();
    });
    

    编译后的代码:

    "use strict";
    require("core-js/modules/es.array.includes");
    require("core-js/modules/es.object.to-string");
    require("core-js/modules/es.promise");
    
    // import '@babel/polyfill'; 
    let include = [1,2,3].includes(1);
    new Promise((resolve, reject) => {
        resolve();
    });
    

    我们可以看到,在文件的头部仅加入了文件中用到的新语法的polyfill,是不是棒棒的了。

    不过这并没有完,试想如果很多文件都使用了这些特性,岂不是每个文件都会引入一次,有没有办法改善一下呢?

    @babel/plugin-transform-runtime

    @babel/plugin-transform-runtime 是一个可重复使用Babel注入的帮助程序的插件,避免重复注入,以节省代码大小。通常还要搭配@babel/runtime一起使用。

    npm install --save-dev @babel/plugin-transform-runtime 
    npm install --save @babel/runtime # 提供缺失的特性,须要在生产环境中安装
    
    // babel.config.json
    {
      "presets": [
        [
          "@babel/preset-env",
          {
            "useBuiltIns": "usage",
            "corejs": 3
          }
        ]
      ],
      "plugins": [["@babel/plugin-transform-runtime"]]
    }
    
    

    如上配置后,一个class转换如下:

    // 源码
    class Foo {
      method() {}
    }
    
    // 转换后
    import _classCallCheck from "babel-runtime/helpers/classCallCheck";
    import _createClass from "babel-runtime/helpers/createClass";
    
    let Foo = function () {
      function Foo() {
        _classCallCheck(this, Foo);
      }
    
      _createClass(Foo, [{
        key: "method",
        value: function method() {}
      }]);
    
      return Foo;
    }();
    

    我们看到,这里并不是采用全局引入的方式,而是引入了局部变量来替换新的语法,提供了一个沙箱机制,这就顺便解决了上面全局作用域污染的问题。而且这里所有模拟的特性都是从@babel/runtime中引入,而这个包我们是安装在生产环境中的,并不是会打包到每个文件中,这就使这些polyfill得到了复用,不会打包多次。

    **注意:**上面的corejs需要为3.0才会完全转换,如果使用2.0只会转换语法,而不会做polyfill,如果要做的话需要再单独引入@babel/runtime-corejs3来进行处理,推荐使用3.0,所以这里不再赘述。

    经历了一波三折,我们终于搞定了。但是,这不是银弹,未来可能还会出现其他的方案,关于Polyfill的历史可以去看云谦大佬的Polyfill 方案的过去、现在和未来。

    生态

    除了上面介绍的东西,babel生态也是非常繁荣,还有很多其他配套的一些工具(名字通常以babel-*@babel/*开头),下面我们大概列举一下:

    • babel-cli:babel提供的命令行工具,提供了babel命令来执行编译任务。
    • babel-register:会在require加载文件前,先用babel进行转码编译,前面说到的运行时编译就是使用此工具。
    • babel-loader:babel为webpack提供的一个loader,也是最常见的使用方式。可以在webpack构建的工程中进行转码编译。
    • babel-polyfill/@babel/polyfill:babel提供的polyfill工具。

    babel的存在,让我们可以打破运行环境语法规范的限制,犹如一台时光机,把我们带到了未来,让我们可以用彼时的语法来编写此时的应用,为我们学习和使用最新的语法扫清了障碍,俨然babel已成为我们工程化中必不可少的一个工具。

    参考文档:Babel中文网、不容错过的 Babel7 知识、一口(很长的)气了解 babel


    起源地下载网 » 【前端工程化】篇五 未来已来-Babel

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元