细说webpack6 Babel的使用详解


Posted in Javascript onSeptember 26, 2019

在 webpack 中编写 JavaScript 代码,可以使用最新的 ES 语法,而最终打包的时候,webpack 会借助 Babel 将 ES6+语法转换成在目标浏览器可执行 ES5 语法。所以 Babel 是一个重要的知识点需要掌握。

什么是 Babel

Babel 是 JavaScript 的编译器,通过 Babel 可以将我们写的最新 ES 语法的代码轻松转换成任意版本的 JavaScript 语法。随着浏览器逐步支持 ES 标准,我们不需要改变代码,只需要修改 Babel 配置即可以适配新的浏览器。
举例说明,下面是 ES6 箭头函数语法的代码:

[1,2,3].map(n => n**2);

经过 Babel 处理后,可以转换为普通的 ES5 语法:

[1,2,3].map(function(n) {
 return Math.pow(n, 2);
});

Babel 初体验

下面来介绍下 Babel 的安装和功能及其配置文件。

1. 使用 babel-cli 命令行工具

Babel 本身自己带有 CLI(Command-Line Interface,命令行界面)工具,可以单独安装使用。下面我们在项目中安装 @babel/cli 和 @babel/core。

npm i -D @babel/core @babel/cli

然后创建一个 babel.js 文件:

// babel.js
[1,2,3].map(n => n**2);

然后执行npx babel babel.js,则会看到输出的内容,此时可能会看到输出的内容跟源文件内容没有区别,这是因为没有加转换规则,下面安装@babel/preset-env。然后执行 CLI 的时候添加 --presets flag:

// 安装 preset-env
npm i -D @babel/preset-env
// 执行 CLI 添加 --presets
npx babel babel.js --presets=@babel/preset-env

最终输出的代码就是转换为 ES5 的代码了:

‘use strict'
[1,2,3].map(function(n) {
 return Math.pow(n, 2);
});

如果要输出结果到固定文件,可以使用 --out-file 或 -o 参数:

npx babel babel.js -o output.js。

Tips: Babel 7 使用了 @babel 命名空间来区分官方包,因此以前的官方包 babel-xxx 改成了 @babel/xxx。

2.配置文件

除了使用命令行配置 flag 之外,Babel 还支持配置文件,配置文件支持两种:

使用 package.json 的 babel 属性;

在项目根目录单独创建 .babelrc或者 .babelrc.js文件。

示例如下:

// package.json
{
 ‘name': ‘my-package',
 ‘version': ‘1.0.0',
 ‘babel': {
 ‘presets': [‘@babel/preset-env']
 }
}
 
// .babelrc
{
 ‘presets': [‘@babel/preset-env']
}

Babel会在正在被转义的文件当前目录中查找一个 .babelrc 文件。 如果不存在,它会向外层目录遍历目录树,直到找到一个 .babelrc 文件,或一个 package.json 文件中有 "babel": {}。

3.env 选项

如果我们希望在不同的环境中使用不同的 Babel 配置,那么可以在配置文件中添加 env 选项:

{
 ‘env': {
 ‘production': {
  ‘presets': [‘@babel/preset-env']
 }
 }
}

env 选项的值将从 process.env.BABEL_ENV 获取,如果没有的话,则获取 process.env.NODE_ENV 的值,它也无法获取时会设置为 "development"。

Babel 的插件和 Preset

Babel 的语法转换是通过强大的插件系统来支持的。Babel 的插件分为两类:转换插件和语法解析插件。

不同的语法对应着不同的转换插件,比如我们要将箭头函数转换为 ES5 函数写法,那么可以单独安装 @babel/plugin-transform-arrow-functions 插件,转换插件主要职责是进行语法转换的,而解析插件则是扩展语法的,比如我们要解析 jsx 这类 React 设计的特殊语法,则需要对应的 jsx 插件。

如果不想一个个的添加插件,那么可以使用插件组合 preset(插件预设,插件组合更加好理解一些),最常见的 preset 是 @babel/preset-env。之前的 preset 是按照 TC39 提案阶段来分的,比如看到 babel-preset-stage-1 代表,这个插件组合里面是支持 TC39< Stage-1 阶段的转换插件集合。

@babel/preset-env 是 Babel 官方推出的插件预设,它可以根据开发者的配置按需加载对应的插件,通过 @babel/preset-env 我们可以根据代码执行平台环境和具体浏览器的版本来产出对应的 JavaScript 代码,例如可以设置代码执行在 Node.js 8.9 或者 iOS 12 版本。

Babel polyfill

Babel 只负责进行语法转换,即将 ES6 语法转换成 ES5 语法,但是如果在 ES5 中,有些对象、方法实际在浏览器中可能是不支持的,例如:Promise、 Array.prototype.includes,这时候就需要用 @babel/polyfill 来做模拟处理。@babel/polyfill 使用方法是先安装依赖,然后在对应的文件内显性的引入:

// 安装,注意因为我们代码中引入了 polyfill,所以不再是开发依赖(--save-dev,-D)
npm i @babel/polyfill

在文件内直接 import 或者 require 进来:

// polyfill
import ‘@babel/polyfill'
console.log([1,2,3].includes(1));

Bable runtime

@babel/polyfill 虽然可以解决模拟浏览器不存在对象方法的事情,但是有以下两个问题:

直接修改内置的原型,造成全局污染;

无法按需引入,Webpack 打包时,会把所有的 Polyfill 都加载进来,导致产出文件过大。

为了解决这个问题,Babel 社区又提出了 @babel/runtime 的方案,@babel/runtime 不再修改原型,而是采用替换的方式,比如我们用 Promise,使用 @babel/polyfill 会产生一个 window.Promise 对象,而 @babel/runtime 则会生成一个 _Promise (示例而已)来替换掉我们代码中用到的 Promise。另外 @babel/runtime 还支持按需引入。下面以转换 Object.assign 为例,来看下 @babel/runtime 怎么使用。

  • 安装依赖 @babel/runtime:npm i @babel/runtime;
  • 安装 npm i -D @babel/plugin-transform-runtime 作为 Babel 插件;
  • 安装需要转换 Object.assign 的插件:
npm i -D @babel/plugin-transform-object-assign

编写一个 runtime.js 文件,内容如下:

Object.assign({}, {a:1});

执行 npx babel runtime.js --plugins @babel/plugin-transform-runtime,@babel/plugin-transform-object-assign,最终的输出结果是:

import _extends from ‘@babel/runtime/helpers/extends';
_extends(
 {},
 {
  a:1
 }
);

代码中自动引入了 @babel/runtime/helpers/extends 这个模块(所以要添加 @babel/runtime 依赖啊)。
@babel/runtime也不是完美的解决方案,由于 @babel/runtime 不修改原型,所以类似[].includes() 这类使用直接使用原型方法的语法是不能被转换的。

Tips:'@babel/polyfill'实际是 core-js和
regenerator-runtime的合集,所以如果要按需引入'@babel/polyfill'的某个模块,可以直接引入对应的
core-js 模块,但是手动引入的方式还是太费劲。

@babel/preset-env

铺垫了这么多,我们继续来讲 @babel/preset-env,前面介绍了@babel/preset-env 可以零配置的转化 ES6 代码,我们如果要精细化的使用 @babel/preset-env ,就需要配置对应的选项了,在 @babel/preset-env 的选项中,useBuiltIns 和 target 是最重要的两个, useBuiltIns 用来设置浏览器 polyfill,target 是为了目标浏览器或者对应的环境(browser/node)。

preset-env 的 useBuiltIns

前面介绍了 @babel/polyfill 和 @babel/runtime 两种方式来实现浏览器 polyfill,两种方式都比较繁琐,而且不够智能,我们可以使用 @babel/preset-env 的 useBuildIn 选项做 polyfill,这种方式简单而且智能。
useBuiltIns 默认为 false,可以使用的值有 usage 和 entry:

{
 ‘presets': [
 ‘@babel/preset-env', {
  ‘useBuiltnls': ‘usage|entry|false'
 }
 ]
}

usage 表示明确使用到的 Polyfill 引用。在一些 ES2015+ 语法不支持的环境下,每个需要用到 Polyfill 的引用时,会自动加上,例如:

const p = new Promise();
[1,2].includes(1);
‘foobar'.includes(‘foo');

使用 useBuiltIns='usage' 编译之后,上面代码变成,真正的做到了按需加载,而且类似 [].includes() 这类直接使用原型方法的语法是能被转换的:

‘use strict'
require(‘core-js/modules/es.array.includes');
require(‘core-js/modules/es.object.to-string');
require(‘core-js/modules/es.promise');
require(‘core-js/modules/es.string.includes');
var p = new Promise();
[1,2].includes(1);
‘foobar'.includes(‘foo');

entry 表示替换 import "@babel/polyfill";(新版本的 Babel,会提示直接引入 core-js或者regenerator-runtime/runtime来代替 @babel/polyfill)的全局声明,然后根据 targets 中浏览器版本的支持,将 polyfill 拆分引入,仅引入有浏览器不支持的 polyfill,所以 entry 相对 usage 使用起来相对麻烦一些,首先需要手动显性的引入 @babel/polyfill ,而且根据配置 targets 来确定输出,这样会导致代码实际用不到的 polyfill 也会被打包到输出文件,导致文件比较大。

一般情况下,个人建议直接使用 usage 就满足日常开发了。

需要提一下的是,polyfill 用到的 core-js 是可以指定版本的,比如使用 core-js@3,则首先安装依赖 npm i -S core-js@3,然后在 Babel 配置文件 .babelrc 中写上版本。

// .babelrc
{
 ‘presets': [
 [
  ‘@babel/preset-env',
  {
   ‘useBuiltlns': ‘useage',
   ‘corejs': 3
  }
 ]
 ]
}

preset-env 的 target

假设希望代码中使用 ES6 的模板字面量`语法,但是实际执行代码的宿主浏览器是 IE 10 却不支持,那么我们可以使用</code>target指定目标浏览器了。

{
 ‘presets': [
  [
  ‘@babel/preset-env',
  {
   ‘targets': {
    ‘browsers': ‘IE 10'
   }
  }
  ]
 ]
}

如果我们代码是在 Node.js 环境执行的,则可以指定 Node.js 的版本号:

{
 ‘presets': [
 [
  ‘env',
  {
  ‘@babel/preset-env': {
   ‘node: ‘8.9.3'
  }
  }
 ]
 ] 
}

targets.browsers 需要使用 browserslist 的配置方法,但是其设置会被 targets.[chrome, opera, edge, firefox, safari, ie, ios, android, node, electron] 覆盖;
targets.node 设置为 true 或 "current" 可以根据当前 Node.js 版本进行动态转换。也可以设置为具体的数字表示需要支持的最低 Node.js 版本;
targets.esmodules 设置使用 ES Modules 语法,最新浏览器支持,这个在后面 Webpack 插件章节会详细介绍如何实现 Modern Mode。

在 Webpack 中使用 Babel

通过上面的内容,我们已经掌握了 Babel 的基本用法,下面在 webpack 中使用 Babel 就变得很简单了,首先安装 npm 依赖,然后修改 webpack.config.js。

安装依赖包:

// 安装开发依赖
npm i webpack babel-loader webpack-cli @babel/core @babel/preset-env @babel/plugin-transform-runtime -D

// 将 runtime 作为依赖
npm i @babel/runtime -S

第二步创建 webpack.config.js 文件,内容如下:

// webpack.config.js
module.exports = {
 entry: ‘./babel.js',
 mode: ‘development',
 devtool: false,
 module: {
 rules: [
  {
  test: /\.js$/,
  use: [
   {
   loader: ‘babel-loader',
   options: {
    presets: [
     [
     ‘@babel/preset-env',
     {
      useBuiltlns: ‘usage'
     }
     ]
    ]
   }
   }
  ]
  }
 ]
 }
}

上面的 webpack.config.js 文件直接将 Babel 的配置写到了 options 中,还可以在项目根目录下创建 .babelrc 或者使用 package.json 的 babel 字段。

小结

在本篇中,我们学习了 Webpack 怎么配置 Babel,希望对大家有所帮助,如果喜欢萝卜的文章,请大家持续关注,下一篇我将给大家介绍 webpack-dev-server 这个超好用的工具。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
json对象转字符串如何实现
Dec 02 Javascript
JavaScript window.location对象
Nov 14 Javascript
jQuery实现的经典竖向伸缩菜单效果代码
Sep 24 Javascript
js 右侧浮动层效果实现代码(跟随滚动)
Nov 22 Javascript
详解JavaScript对象和数组
Dec 03 Javascript
Svg.js实例教程及使用手册详解(一)
May 16 Javascript
AngularJS中使用ngModal模态框实例
May 27 Javascript
基于Vue实例对象的数据选项
Aug 09 Javascript
JavaScript实现的DOM树遍历方法详解【二叉DOM树、多叉DOM树】
May 07 Javascript
JavaScript 作用域实例分析
Oct 02 Javascript
JavaScript鼠标悬停事件用法解析
May 15 Javascript
在vue中对数组值变化的监听与重新响应渲染操作
Jul 17 Javascript
微信小程序实现拖拽功能
Sep 26 #Javascript
vue用BMap百度地图实现即时搜索功能
Sep 26 #Javascript
layui.tree组件的使用以及搜索节点功能的实现
Sep 26 #Javascript
微信小程序点击列表跳转到对应详情页过程解析
Sep 26 #Javascript
vue+element tabs选项卡分页效果
Jun 29 #Javascript
layui实现根据table数据判断按钮显示情况的方法
Sep 26 #Javascript
vue+element表格导出为Excel文件
Sep 26 #Javascript
You might like
PHP+javascript模拟Matrix画面
2006/10/09 PHP
PHP入门学习笔记之一
2010/10/12 PHP
php下利用curl判断远程文件是否存在的实现代码
2011/10/08 PHP
PHP限制页面只能在微信自带浏览器访问的代码
2014/01/15 PHP
php创建sprite
2014/02/11 PHP
一款简单实用的php操作mysql数据库类
2014/12/08 PHP
PHP+Mysql+jQuery文件下载次数统计实例讲解
2015/10/10 PHP
PHP封装的字符串加密解密函数
2015/12/18 PHP
PHP中如何防止外部恶意提交调用ajax接口
2016/04/11 PHP
thinkPHP多域名情况下使用memcache方式共享session数据的实现方法
2016/07/21 PHP
Yaf框架封装的MySQL数据库操作示例
2019/03/06 PHP
PHP的重载使用魔术方法代码实例详解
2021/02/26 PHP
实用javaScript技术-屏蔽类
2006/08/15 Javascript
根据分辨率不同,调用不同的css文件
2006/08/25 Javascript
基于JQuery实现滚动到页面底端时自动加载更多信息
2014/01/31 Javascript
Jquery Uploadify上传带进度条的简单实例
2014/02/12 Javascript
现代 JavaScript 开发编程风格Idiomatic.js指南中文版
2014/05/28 Javascript
JavaScript修改作用域外变量的方法
2016/03/25 Javascript
jQuery过滤选择器用法示例
2016/09/12 Javascript
JS控制HTML元素的显示和隐藏的两种方法
2016/09/27 Javascript
防止重复发送 Ajax 请求
2017/02/15 Javascript
vue 插槽简介及使用示例
2020/11/19 Vue.js
python中subprocess批量执行linux命令
2018/04/27 Python
Python 在字符串中加入变量的实例讲解
2018/05/02 Python
在python中获取div的文本内容并和想定结果进行对比详解
2019/01/02 Python
基于python2.7实现图形密码生成器的实例代码
2019/11/05 Python
python实现宿舍管理系统
2019/11/22 Python
详解CSS3弹性伸缩盒
2020/09/21 HTML / CSS
html5利用canvas绘画二级树形结构图的示例
2017/09/27 HTML / CSS
美国迪克体育用品商店:DICK’S Sporting Goods
2018/07/24 全球购物
高性能装备提升营地:Kammok
2019/02/27 全球购物
校长岗位职责
2013/11/26 职场文书
优秀村官事迹材料
2014/01/10 职场文书
《燕子专列》教学反思
2014/02/21 职场文书
美术社团活动总结
2014/06/27 职场文书
详解Java分布式事务的 6 种解决方案
2021/06/26 Java/Android