最近在学习webpack, 发现了webpack中一个重要的功能点babel-loader
, 因而就想着学习了解一波Babel.前端
咱们在作一件事, 学习一个知识点的时候, 都应该是抱有一个目的去作的.node
在你花了大把时间大把精力去学习这个知识的时候, 它能带给你什么 🤔️ ? 能帮助到你什么🤔️ ?webpack
就像我学习Babel同样, 以前一直只知道它是一个JS
编译器, 大概功能是能帮咱们在旧的浏览器环境中将ES6+代码转换成向后兼容版本的JS
代码, 可是其中重要的转换功能是靠什么实现, 以及里面到底有个什么学问是我没深刻了解的, 它对我学习webpack有什么帮助?es6
在这一篇文章中我并无介绍过于深刻的内容, 可是若是把它当成一个入门Babel的教材来看那我相信它对你是有必定帮助的. 不信若是你读完了它以后再去看看官方的文档, 必定以为均可以看懂了. 否则的话请评论区留下你的地址, 看我给不给你寄口罩...web
不拐弯抹角了, 嘻嘻 😁, 让咱们看看经过这一章节的阅读你能学习到什么:chrome
学习一个新的知识, 我仍是偏向于用案例的的方式来打开讲解它.npm
因此在正式开始阅读以前, 让咱们先来准备一个这样的案例项目:json
mkdir babel-basic && cd babel-basic
npm init -y
mkdir src && cd src
touch index.js
复制代码
一顿操做以后, 咱们新建的项目目录为:promise
/babel-basic
|- /src
|- index.js
|- package.json
复制代码
如今package.json
是最原始的配置, 而index.js
暂时没有写内容.浏览器
package.json:
{ "name": "babel-basic", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": {} } 复制代码
下面我都将围绕这个babel-basic
项目来进行讲解, 我但愿你也能在本地准备一个这样的项目案例, 以便你更好的理解我接下来要说的内容.
咱们学习Babel, 首先要了解一个叫@babel/core
的东西, 它是Babel的核心模块.
固然要使用它, 咱们得先安装:
$ npm i --save-dev @babel/core
复制代码
安装成功以后就能够在咱们的代码中使用了, 你能够采用CommonJS
的引用方式:
const babel = require('@babel/core');
babel.transform("code", options);
复制代码
这里的知识点有不少, 不过你不用急于的掌握它, 只须要知道它是Babel的核心, 让咱们接着往下看.
再而后就是@babel/cli
, 它是一个终端运行工具, 内置的插件,运行你从终端使用babel的工具.
一样, 它也须要先安装:
$ npm i --save-dev @babel/cli @babel/core
复制代码
让咱们安装@babel/cli
的同时再来安装一下@babel/core
,
如今, 让我先在src/index.js
中写上一段简单的代码, 并来看看它的基本用法.
src/index.js:
const fn = () => 1; // 箭头函数, 返回值为1 console.log(fn()); 复制代码
用法一: 命令行的形式(在项目根目录执行语句):
$ ./node_modules/.bin/babel src --out-dir lib
复制代码
这段语句的意思是: 它使用咱们设置的解析方式来解析src
目录下的全部JS
文件, 并将转换后的每一个文件都输出到lib
目录下.
可是注意了, 因为咱们如今没有设置任何的解析方式, 因此你在执行了这段语句以后, 能看到项目中多了一个lib
目录, 并且里面的JS
代码和src
中的是同样的. 至于我说的解析方式, 就是后面我要介绍的plugins和presets.
另外, 若是你是npm@5.2.0
附带的npm
包运行器的话, 就能够用npx babel
来代替./node_modules/.bin/babel
:
$ npx babel src --out-dir lib
复制代码
用法二: 给package.json
中配置一段脚本命令:
{ "name": "babel-basic", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { + "build": "babel src -d lib" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { + "@babel/cli": "^7.8.4", + "@babel/core": "^7.8.4" } } 复制代码
如今运行npm run build
效果也是同样的, -d
是--out-dir
的缩写...
(咱们使用上面的 --out-dir
选项。你能够经过使用 --help
运行它来查看 cli 工具接受的其他选项。但对咱们来讲最重要的是 --plugins
和 --presets
。)
$ npx babel --help
复制代码
知道了Babel的基本用法以后, 让咱们来看看具体的代码转换.
如今要介绍的是插件plugins, 它的本质就是一个JS
程序, 指示着Babel如何对代码进行转换.
因此你也能够编写本身的插件来应用你想要的任何代码转换.
可是首先让咱们来学习一些基本的插件.
若是你是要将ES6+转成ES5, 能够依赖官方插件, 例如:
@babel/plugin-transform-arrow-functions
:
$ cnpm i --save-dev @babel/plugin-transform-arrow-functions
$ npx babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions
复制代码
这个插件的做用是将箭头函数转换为ES5兼容的函数:
还记得咱们以前的src/index.js
吗:
const fn = () => 1; // 箭头函数, 返回值为1 console.log(fn()); 复制代码
如今编译以后, 你再打开lib/index.js
来看看.
它是否是被转换为ES5的代码了呢? 😁
const fn = function () { return 1; }; // 箭头函数, 返回值为1 console.log(fn()); 复制代码
捣鼓了这么久, 终于看到了一点实际的效果, 此时有点小兴奋啊😄
表情包开心
虽然咱们已经实现了箭头函数转换的功能, 可是ES6+
其它的语法(比求幂运算符**
)却并不能转换, 这是由于咱们只使用了@babel/plugin-transform-arrow-functions
这个功能插件, 没有使用其它的了.
若是想要转换ES6+的其它代码为ES5, 咱们可使用"preset"来代替预先设定的一组插件, 而不是逐一添加咱们想要的全部插件.
这里能够理解为一个preset就是一组插件的集合.
presets和plugins同样, 也能够建立本身的preset, 分享你须要的任何插件组合.
例如, 咱们使用env
preset:
cnpm i --save-dev @babel/preset-env
复制代码
env
preset这个preset包括支持现代JavaScript(ES6+)的全部插件.
因此也就是说你安装使用了env
preset以后, 就能够看到其它ES6+语法的转换了.
如今让咱们来用用ES7中的求幂运算符和函数参数支持尾部逗号这两个功能吧:
src/index.js:
const fn = () => 1; // ES6箭头函数, 返回值为1 let num = 3 ** 2; // ES7求幂运算符 let foo = function(a, b, c, ) { // ES7参数支持尾部逗号 console.log('a:', a) console.log('b:', b) console.log('c:', c) } foo(1, 3, 4) console.log(fn()); console.log(num); 复制代码
而后在命令行里使用这个preset
:
npx babel src --out-dir lib --presets=@babel/preset-env
复制代码
如今打开lib/src
看看:
"use strict"; var fn = function fn() { return 1; }; // 箭头函数, 返回值为1 var num = Math.pow(3, 2); var foo = function foo(a, b, c) { console.log('a:', a); console.log('b:', b); console.log('c:', c); }; foo(1, 3, 4); console.log(fn()); console.log(num); 复制代码
求幂运算符被转换为成Math.pow()
函数参数的最后一个逗号也被去掉了.
截止到如今, 看完了@babel/core
、@babel/cli
、plugins
、presets
, 相信你对Babel的功能有必定了解了吧, 可是真正使用起来咱们不可能都是靠命令行的形式吧, 没错, 接下来我要将这些功能作成配置项.
上面👆介绍的都是一些终端传入CLI的方式, 在实际使用上, 咱们更加偏向于配置文件.
例如咱们在项目的根目录下建立一个babel.config.js
文件:
const presets = [ [ "@babel/env", { targets: { edge: "17", chrome: "64", firefox: "60", safari: "11.1" } } ] ] module.exports = { presets }; 复制代码
加上这个配置的做用是:
env
preset这个presetenv
preset只会为目标浏览器中没有的功能加载转换插件如今你要使用这个配置就很简单了, 直接用咱们前面package.json配置的命令行语句:
{ "scripts": { "build": "babel src -d lib" } } 复制代码
执行npm run build
就能够了.
这个命令行语句看起来并无修改, 那是由于它默认会去寻找跟根目录下的一个名为babel.config.js
的文件(或者babelrc.js
也能够, 这个在以后的使用babel的几种方式
中会说到), 因此其实就至关于如下这个配置:
{ "scripts": { "build": "babel src -d lib --config-file ./babel.config.js" } } 复制代码
所以若是你的Babel配置文件是babel.config.js
的话, 这两种效果是同样的.
(--config-file
指令就相似于webpack中的--config
, 用于指定以哪一个配置文件构建)
这里我重点要说一下只会为目标浏览器中没有的功能加载转换插件这句话的意思.
例如我这里配置的其中一项是edge: "17"
, 那就表示它转换以后的代码支持到edge17
.
因此你会发现, 若是你用了我上面babel.config.js
的配置以后生成的lib
文件夹下的代码好像并无发生什么改变, 也就是它并无被转换成ES5
的代码:
src/index.js:
const fn = () => 1; // ES6箭头函数, 返回值为1 let num = 3 ** 2; // ES7求幂运算符 let foo = function(a, b, c, ) { // ES7参数支持尾部逗号 console.log('a:', a) console.log('b:', b) console.log('c:', c) } foo(1, 3, 4) console.log(fn()); console.log(num); 复制代码
使用babel.config.js
配置以后构建的lib/index.js
:
"use strict"; const fn = () => 1; // ES6箭头函数, 返回值为1 let num = 3 ** 2; // ES7求幂运算符 let foo = function foo(a, b, c) { // ES7参数支持尾部逗号 console.log('a:', a); console.log('b:', b); console.log('c:', c); }; foo(1, 3, 4); console.log(fn()); console.log(num); 复制代码
箭头函数依旧是箭头函数, 求幂运算符依旧是求幂运算符.
这是由于在Edge17浏览器中支持ES7的这些功能, 因此它就没有必要将其转换了, 它只会为目标浏览器中没有的功能加载转换插件!!!
若是咱们将edge17
改为edge10
看看 🤔️?
babel.config.js:
const presets = [ [ "@babel/env", { targets: { - edge: "17", + edge: "10", firefox: "60", chrome: "67", safari: "11.1", }, }, ], ]; module.exports = { presets }; 复制代码
保存从新运行npm run build
, 你就会发现lib/index.js
如今有所改变了:
"use strict"; var fn = function fn() { return 1; }; // ES6箭头函数, 返回值为1 var num = Math.pow(3, 2); // ES7求幂运算符 var foo = function foo(a, b, c) { // ES7参数支持尾部逗号 console.log('a:', a); console.log('b:', b); console.log('c:', c); }; foo(1, 3, 4); console.log(fn()); console.log(num); 复制代码
Plugins是提供的插件, 例如箭头函数转普通函数@babel/plugin-transform-arrow-functions
Presets是一组Plugins的集合.
而Polyfill是对执行环境或者其它功能的一个补充.
什么意思呢 🤔️?
就像如今你想在edge10
浏览器中使用ES7中的方法includes()
, 可是咱们知道这个版本的浏览器环境是不支持你使用这个方法的, 因此若是你强行使用并不能达到预期的效果.
而polyfill
的做用正是如此, 知道你的环境不容许, 那就帮你引用一个这个环境, 也就是说此时编译后的代码就会变成这样:
// 原来的代码 var hasTwo = [1, 2, 3].includes(2); // 加了polyfill以后的代码 require("core-js/modules/es7.array.includes"); require("core-js/modules/es6.string.includes"); var hasTwo = [1, 2, 3].includes(2); 复制代码
这样说你应该就能看懂它的做用了吧 😁
表情包装逼
如今就让咱们来学习一个重要的polyfill
, 它就是@babel/polyfill
.
@babel/polyfill
用来模拟完成ES6+环境:
Promise
或者WeakMap
这样的新内置函数Array.from
或者Object.assign
这样的静态方法Array.prototype.includes
这样的实例方法generator
函数为了实现这一点, Polyfill增长了全局范围以及像String这样的原生原型.
而@babel/polyfill
模块包括了core-js
和自定义regenerator runtime
对于库/工具来讲, 若是你不须要像Array.prototype.includes
这样的实例方法, 可使用transform runtime
插件, 而不是使用污染全局的@babel/polyfill
.
对于应用程序, 咱们建议安装使用@babel/polyfill
cnpm i --save @babel/polyfill
复制代码
(注意 --save
选项而不是 --save-dev
,由于这是一个须要在源代码以前运行的 polyfill。)
可是因为咱们使用的是env
preset, 这里个配置中有一个叫作 "useBuiltIns"
的选项
若是将这个选择设置为"usage"
, 就只包括你须要的polyfill
此时的babel.config.js
调整为:
const presets = [ [ "@babel/env", { targets: { edge: "17", chrome: "64", firefox: "67", safari: '11.1' }, + useBuiltIns: "usage" } ] ] module.exports = { presets } 复制代码
安装配置了@babel/polyfill
, Babel将检查你的全部代码, 而后查找目标环境中缺乏的功能, 并引入仅包含所需的polyfill
(若是咱们没有将 env
preset 的 "useBuiltIns"
选项的设置为 "usage"
,就必须在其余代码以前 require 一次完整的 polyfill。)
仍是上面👆的那个例子, 咱们来改造一下, 使用Edge17
中没有的Promise.prototype.finally
:
src/index.js:
const fn = () => 1; // ES6箭头函数, 返回值为1 let num = 3 ** 2; // ES7求幂运算符 let hasTwo = [1, 2, 3].includes(2) let foo = function(a, b, c, ) { // ES7参数支持尾部逗号 console.log('a:', a) console.log('b:', b) console.log('c:', c) } foo(1, 3, 4) Promise.resolve().finally(); console.log(fn()); console.log(num); console.log(hasTwo); 复制代码
如今执行npm run build
以后生成的lib/index.js
变成了:
"use strict"; require("core-js/modules/es7.promise.finally"); const fn = () => 1; // ES6箭头函数, 返回值为1 let num = 3 ** 2; // ES7求幂运算符 let hasTwo = [1, 2, 3].includes(2); let foo = function foo(a, b, c) { // ES7参数支持尾部逗号 console.log('a:', a); console.log('b:', b); console.log('c:', c); }; foo(1, 3, 4); Promise.resolve().finally(); console.log(fn()); console.log(num); console.log(hasTwo); 复制代码
@babel/polyfill
帮咱们引入了Edge17
环境中没有的promise.finally()
上面我介绍了一种名为@babel/polyfill
的polypill, 其实它在Babel7.4.0以上已经不被推荐使用了.
而是推荐使用core-js@3
+@babel/preset-env
而后设置@babel/preset-env
的corejs
选项为3
.
所以若是你按着我文章中讲方式使用@babel/polyfill
, 是能够实现的, 不过控制台中会抛出一个警告⚠️:
WARNING: We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.
You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:
npm install --save core-js@2 npm install --save core-js@3
yarn add core-js@2 yarn add core-js@3
复制代码
解决办法是卸载掉@babel/polyfill
, 而后从新安装core-js@版本号
, 而后从新配置一些babel.config.js文件.
core-js@3
:cnpm i --save core-js@3
复制代码
corejs
选项:const presets = [ [ "@babel/env", { targets: { edge: "17", chrome: "64", firefox: "67", safari: '11.1' }, useBuiltIns: "usage", + corejs: 3 } ] ] module.exports = { presets } 复制代码
(useBuiltIns
选项仍是不能去掉)
如今从新npm run build
以后就不会有这个警告了, 并且生成的lib
也是正确的.
(感谢掘友 KsRyY 的细心提醒😁 )
babel/cli
容许咱们从终端运行Babelenv
preset 只包含咱们使用的功能的转换,实现咱们的目标浏览器中缺乏的功能@babel/polyfill
实现全部新的JS
功能, 为目标浏览器引入缺乏的环境(可是Babel7.4.0以上不推荐使用)哈哈😄, 很差意思开头骗了你们...寄口罩不存在的 😂 我本身也是被关在家里不敢出门...
看我为了能让你们老实呆家学习多费心啊 😂 (不要脸了一波)
最后...
喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai
或者扫一扫下面的二维码👇👇👇.
我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉
你的鼓励就是我持续创做的主要动力 😊.
相关推荐: