Babel了解

作者: 平安喜乐698 | 来源:发表于2020-03-13 18:55 被阅读0次
    目录
    
    

    本文为本人略读官网文档后的大略笔记,实在不适合他人阅读

    前言

    Babel是一个JavaScript编译器(用于转换浏览器不支持的最新版本js代码)。
    Babel工作过程(3阶段):解析=>转换=>生成。

    Babel支持source map(方便调试编译代码)。
    Babel可转换ReactJSX和删除类型注释。
    Babel可集成到构建工具(Webpack、Browserify、Grunt、Gulp等)。

    1. 插件:
      babel的转换功能是通过插件来实现。
    
    2. preset:
      语法糖(一系列插件的组合)
    
    3. polyfill:
      抹平不同浏览器之间的差异。
    

    1. 插件

    不配置任何插件时,经过babel的代码和输入是相同的。
    因为:babel本身不具有任何转化功能,它把转化的功能都分解到一个个plugin中。

    1. plugin插件分为2种
    1. 语法插件
    使babel能够正确解析语法
    
    例
    
    babel不能识别callFoo(param1, param2,) 
    需要添加babel-plugin-syntax-trailing-function-commas 来正确解析。
    
    1. 转译插件
    将ES2015+转换浏览器可识别的js代码
    
    例
    
    箭头函数 (a) => a 转化为 function (a) {return a}
    需要添加 babel-plugin-transform-es2015-arrow-functions 来完成转换
    
    1. 配置方式

    方式1 package.json文件中

    { 
      "name": "helloworld", 
      "version": "1.0.0", 
      "babel": { 
        "presets": [ ... ], 
        "plugins": [ ... ], 
      } 
    } 
    

    方式2 .babelrc独立文件

    {
        "presets": […],
        "plugins": […]
    }
    

    方式3 使用webpack工具时 (具体参考webpack了解篇3.1)

    webpack.config.js文件
    
    module.exports = {
      devtool: 'eval-source-map',
      entry:  __dirname + "/src/main.js", // 入口文件(唯一)
      output: {
        path: __dirname + "/build",     // 输出文件路径(打包后的文件存放的地方)
        filename: "bundle.js",           // 输出文件名(打包后输出文件的文件名)
      },
      module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [     
                            "env", "react"
                        ]
                    }
                },
                exclude: /node_modules/
            }
        ]
      }
    }
    

    具体配置

    "presets": [
        // 情况1 : 不带配置项,直接写名字
        "stage-2"
    
        // 情况2 : 带了配置项,则变成数组
        [
            // 第一个元素依然是名字
            "env",
            // 第二个元素是对象,列出配置项
            {
              "module": false
            }
        ],
    ]
    
    说明:
      1. env、stage-2 (见preset)
      2.modules
          可取值 : amd, umd, systemjs, commonjs 和 false。
          让babel以特定的模块化格式来输出代码 , 如果选择 false 就不进行模块化处理。
    
    1. Plugin 会运行在 Preset 之前。
    2. Plugin 会从前到后顺序执行。
    3. Preset 刚好相反(会从后向前)。
    
    因此编写Preset要按照规范的时间顺序列出。如['es2015', 'stage-0'],反过来则会报错
    
    1. 安装plugin
    npm install babel-plugin-xxx
    

    2. preset

    光es2015就需要包含近20个转译插件。
    如果每次都一个个添加并安装,会导致配置文件很长,npm install的时间也会很长。
    为了解决这个问题,babel提供了一组插件的集合。

    1. preset分为
    1. 官方内容(目前包括 env, react, flow, minify等)
    2. stage-x(当年最新规范草案,每年更新)
        Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
        Stage 1 - 提案: 初步尝试。
        Stage 2 - 初稿: 完成初步规范。
        Stage 3 - 候选: 完成规范和浏览器初步实现。
        Stage 4 - 完成: 将被添加到下一年度发布。
        低一级的 stage 会包含所有高级 stage 的内容。
    3. es201x, latest
        已经纳入到标准规范的语法。例如 es2015 包含 arrow-functions,es2017 包含 syntax-trailing-function-commas。latest 是 env 的雏形,它是一个每年更新的 preset,目的是包含所有 es201x。
        但因为 env 的出现,使得 es2016、es2017、latest 都已经废弃。
    
    1. env (最常用、最重要)
    env的目的是通过配置得知目标环境的特点,然后只做必要的转换。
    例如目标浏览器支持 es2015,则不需要es2015这个preset ,缩短了代码和构建时间。
    
    如果不写任何配置项,env等价于latest,也等价于es2015+es2016+es2017 三个相加(不包含 stage-x 中的插件)。
    

    例1

    {
      "presets": [
        ["env", {
          "targets": {
            "browsers": ["last 2 versions", "safari >= 7"]
          }
        }]
      ]
    }
    
    如上配置将考虑所有浏览器的最新2个版本(safari大于等于7.0的版本)的特性,将必要的代码进行转换。
    而这些版本已有的功能就不进行转化了。
    

    例2

    {
      "presets": [
        ["env", {
          "targets": {
            "node": "6.10"
          }
        }]
      ]
    }
    
    如上配置将目标设置为 nodejs,并且支持 6.10 及以上的版本。
    也可以使用 node: 'current' 来支持最新稳定版本。
    例如箭头函数在 nodejs 6 及以上将不被转化,但如果是 nodejs 0.12 就会被转化了。
    

    3. 常用模块

    1. babel-cli

    项目不大时使用

    cli 即命令行工具,允许开发者在命令行中使用 babel 命令来编译文件。
    
    # 本地安装(建议)
    # 不同项目会依赖不同版本的 Babel
    $ npm install --save-dev babel-cli
    
    # 全局安装(可直接在终端使用)
    $ npm install --global babel-cli
    

    使用

    # 将编译后的结果直接输出至终端
    $ babel example.js
    
    # 将编译后的结果写入到指定的文件
    $ babel ES6.js --out-file ES5.js
    $ babel ES6.js -o ES5.js
    
    # 编译整个目录
    $ babel ES5 --out-dir ES6
    $ babel ES5 -d ES6
    
    1. babel-node

    babel-node 是 babel-cli 的一部分,不需要单独安装

    作用是在node环境中,直接运行es2015代码,而不需要进行转码。
    
    babel-node = babel-polyfill + babel-register
    

    使用

    babel-node es2015.js
    
    1. babel-register
    改写require命令:
        为它加上一个钩子,每当使用require加载 .js、.jsx、.es 和 .es6 后缀名的文件,就会先用 babel 进行转码。
    
    使用时,必须首先加载 require('babel-register')
    
    只会对require命令加载的文件转码,而不会对当前文件转码。
    由于它是实时转码,所以只适合在开发环境使用
    
    1. babel-polyfill
    babel是通过语法转换来转换不属于ES5的语法,如:
        for-of
        箭头函数
        结构
        块级作用域
        let/const
    
    下面情况则不作处理:
        1. 全局对象(如:Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise、WeakMap)
        2. 定义在全局对象上的方法(如:Array.from、Array.includes、Object.is、Object.assign)
        3. generactor 函数
    
    方式1:babel-polyfill 可用来解决上述情况
        会检测运行环境是否支持Promise等,如果不支持,会创建全局的Promise等对象或者给Array等新增方法。
    
    方式2:也可使用babel-runtime来解决上述情况
          与babel-polyfill作用一样。不同的是它不会污染全局作用域。它会提供一个module, 当使用Promise时,它会把这个module的某个对象导出给使用的地方。
    
    2个缺点:
        1. 会导致打出来的包非常大,因为 babel-polyfill 是一个整体,把所有方法都加到原型链上。比如我们只使用了 Array.from,但它把 Object.defineProperty 也给加上了,这就是一种浪费了。这个问题可以通过单独使用 core-js 的某个类库来解决,core-js 都是分开的。
        2. 会污染全局变量,给很多类的原型链上都作了修改,如果我们开发的也是一个类库供其他开发者使用,这种情况就会变得非常不可控。
    
    因此通常使用babel-plugin-transform-runtime 
    
    使用时
      在所有代码运行之前增加 require('babel-polyfill')。
      或
      在 webpack.config.js 中将 babel-polyfill 作为第一个 entry(更常用)。
    
    因此必须把 babel-polyfill 作为 dependencies 而不是 devDependencies
    
    1. babel-runtime 和 babel-plugin-transform-runtime

    babel-plugin-transform-runtime 依赖 babel-runtime

    babel-plugin-transform-runtime 不支持 实例方法 (例如 [1,2,3].includes(1))
    
    ~~~~~未使用babel-plugin-transform-runtime~~~~~
    // 每一个使用_asyncToGenerator的地方都会加,导致重复定义
    // babel 添加一个方法,把 async 转化为 generator
    function _asyncToGenerator(fn) { return function () {....}} // 很长很长一段
    
    // 具体使用处
    var _ref = _asyncToGenerator(function* (arg1, arg2) {
      yield (0, something)(arg1, arg2);
    });
    ~~~~~使用babel-plugin-transform-runtime~~~~~
    // 从直接定义改为引用,这样就不会重复定义了。
    var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
    var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
    
    // 具体使用处是一样的
    var _ref = _asyncToGenerator3(function* (arg1, arg2) {
      yield (0, something)(arg1, arg2);
    });
    
    

    babel-runtime内部集成了

    1. core-js
        转换一些内置类 (Promise, Symbols等等) 和静态方法 (Array.from 等)。
        绝大部分转换是这里做的。
        自动引入。
    2. regenerator
        作为 core-js 的拾遗补漏,主要是 generator/yield 和 async/await 两组的支持。
        当代码中有使用 generators/async 时自动引入。
    3. helpers
        如上面的 asyncToGenerator 就是其中之一,其他还有如 jsx, classCallCheck 等等。
        在代码中有内置的 helpers 使用时(如上面的第一段代码)移除定义,并插入引用。
    
    1. babel-loader

    用于大型项目。
    webpack的loader,用于转换ES6等浏览器不识别的js代码(详情见webpack篇)。

    4. Babel 7.x

    preset的变更

    1. 淘汰(即不推荐使用) es201x
    2. 删除 stage-x
      依然可以显式地声明这些插件来获得等价的效果
      babel-upgrade会检测 babel 配置中的 stage-x 并且替换成对应的 plugins
    3. 强推 env (重点)
    

    npm package 名称的变化 (重点)

    把所有 babel-* 重命名为 @babel/*
    
    1. babel-cli 变成了 @babel/cli。
    2. babel-preset-env 变成了 @babel/preset-env。
        简写为 @babel/env。
    3. babel-plugin-transform-arrow-functions 变成了 @babel/plugin-transform-arrow-functions。
        简写为 @babel/transform-arrow-functions。
    4. 如果插件名称中包含了规范名称 (-es2015-, -es3- 之类的),一律删除。
        例如 babel-plugin-transform-es2015-classes 变成了 @babel/plugin-transform-classes。
    
    {
      "presets": [
    -   "env"
    +   "@babel/preset-env"
      ]
    }
    

    不再支持低版本 node

    babel 7.0 开始不再支持 nodejs 0.10, 0.12, 4, 5 这四个版本,相当于要求 nodejs >= 6
    
    不再支持指的是:在这些低版本 node 环境中不能使用 babel 转译代码,但 babel 转译后的代码依然能在这些环境上运行。
    

    only 和 ignore 匹配规则的变化

    在 babel 6 时,ignore 选项如果包含 *.foo.js,实际上的含义 (转化为 glob) 是 ./**/*.foo.js,也就是当前目录 包括子目录 的所有 foo.js 结尾的文件。这可能和开发者常规的认识有悖。
    
    于是在 babel 7,相同的表达式 *.foo.js 只作用于当前目录,不作用于子目录。如果依然想作用于子目录的,就要按照 glob 的完整规范书写为 ./**/*.foo.js 才可以。only 也是相同。
    
    这个规则变化只作用于通配符,不作用于路径。所以 node_modules 依然包含所有它的子目录,而不单单只有一层。(否则全世界开发者都要爆炸)
    

    @babel/node 从 @babel/cli 中独立了

    和 babel 6 不同,如果要使用 `@babel/node`,就必须单独安装,并添加到依赖中。
    

    babel-upgrade

    在提到删除 stage-x 时候提过这个,它的目的是帮助用户自动化地从 babel 6 升级到 7。
    
    功能:
      package.json
      1.  把依赖(和开发依赖)中所有的 `babel-*` 替换为 `@babel/*`
      2.  把这些 `@babel/*` 依赖的版本更新为最新版 (例如 `^7.0.0`)
      3.  如果 `scripts` 中有使用 `babel-node`,自动添加 `@babel/node` 为开发依赖
      4.  如果有 `babel` 配置项,检查其中的 `plugins` 和 `presets`,把短名 (`env`) 替换为完整的名字 (`@babel/preset-env`)
      .babelrc
      1.  检查其中的 `plugins` 和 `presets`,把短名 (`env`) 替换为完整的名字 (`@babel/preset-env`)
      2.  检查是否包含 `preset-stage-x`,如有替换为对应的插件并添加到 `plugins`
      等等
    
    使用:
    
    # 不安装到本地而是直接运行命令,npm 的新功能
    npx babel-upgrade --write
    
    # 或者常规方式
    npm i babel-upgrade -g
    babel-upgrade --write
    

    相关文章

      网友评论

        本文标题:Babel了解

        本文链接:https://www.haomeiwen.com/subject/cvmhvctx.html