ECMAScript 6.0 是2015年发布的下一代 JavaScript 语言标准,它引入了新的语法和 API 来提升开发效率。
虽然目前部分浏览器和 Node.js 已经支持 ES6,但由于它们对 ES6 所有的标准支持不全,这导致在开发中不敢全面地使用 ES6。
通常我们需要把采用 ES6 编写的代码转换成目前已经支持良好的 ES5 代码,这包含2件事:
class
语法用 ES5 的 prototype
实现,需要用到:@babel/preset-env。fetch
API 时,只有注入对应的 polyfill 后,才能在低版本浏览器中正常运行,需要用到: @babel/polyfill 。ES6新增语法(@babel/preset-env ):
let const 解构数组 解构对象 模板字符串 展开运算符 箭头函数 Class extends 导入模块 …
ES6新增API( @babel/polyfill):
Promise 、 fetch 、 Set 、 Map、 WeakMap Formdata
Array.from 、Array.of 、 Array.prototype.fill 、 Array.prototype.findIndex 、 Array.prototype.copyWithin 、 Array.prototype.entries、 Array.prototype.keys 、 Array.prototype.find 、 Array.prototype.values 、 Array.prototype.includes
String.prototype.includes 、 String.prototype.repeat 、String.prototype.startsWith 、 String.prototype.endsWith
Number.EPSILON 、 Number.isInteger 、 Number.isSafeInteger 、 Number.isFinite 、 Number.isNaN()
Math.acosh 、 Math.hypot 、 Math.imul 、 Math.sign 、 Math.trunc
Object.assign ....
Babel 是一个JavaScript 编译器
Babel是一个工具链,主要用于将ECMAScript 2015+代码转换为当前和旧版浏览器或环境中的向后兼容版本的JavaScript。以下是Babel可以为您做的主要事情:
下面是用 Babel编译器编译 JavaScript 的案例:
// Babel 编译前: ES6 箭头函数
[1, 2, 3].map((n) => n + 1);
// Babel 编译后: 箭头函数转换成es5语法的函数
[1, 2, 3].map(function(n) {
return n + 1;
});
有关编译器的精彩教程,请查看超级微型编译器,它还解释了Babel本身如何在高级别上工作。
新建一个项目
|-- package.json
|-- src
| | `-- common.js
| | `-- index.html
| `-- main.js
`-- webpack.config.js
package.json
{
"name": "testjsloader",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"author": "liujun",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"babel-loader": "^8.0.0-beta.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.6"
},
"dependencies": {
}
}
1.scripts 属性里编写了3个脚本:其中 dev 对应的脚本是 构建测试版;其中 build对应的脚本是 构建发布版
2.使用的开发依赖有:
[email protected] @babel/core @babel/preset-env
webpack webpack-cli
webpack.config.js
const path = require('path');
module.exports = {
entry: ['./src/main.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/, // 1.匹配 .css 结尾的文件,注意test的值不是一个字符串,而是一个正则
exclude: /(node_modules|bower_components)/, // 2.排除这两个文件不需要打包
// 3.使用babel-loader编译js代码
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
],
}
]
}
};
use
接收一个数组
,数组
中可以存放字符串和对象
,并且use的别名是 loaders。
main.js
// 1.通过 CommonJS 规范导入 showHelloWorld 函数
const showHelloWorld = require('./common.js');
// 2.执行 showHelloWorld 函数
showHelloWorld('Webpack的安装和使用');
common.js
// 1.箭头函数 和 const 是es6新的语法。目的是要把es6新的语法转成es5语法
const showHelloWorld =(content) => {
alert(new Person().name)
}
// 2.class 定义类也是es6新的语法。目的是要把es6新的语法转成es5语法
class Person {
constructor(){
this.name = 'person'
}
testSet() {
//3.Set Promise 和 fetch 是es6新增的API。目的是要把es6新的API 添加补丁垫片 polyfill
let set= new Set([1,2,3])
new Promise((resove,reject)=>{
fetch('')
})
}
}
// 4.通过 CommonJS 规范导出 showHelloWorld 函数
module.exports = showHelloWorld;
index.html
<html>
<head>
<meta charset="UTF-8">
head>
<body>
<div id="app">div>
<script src="../dist/bundle.js">script>
body>
html>
本地安装 webpack 和 用到的loader
npm install webpack@4.35.3 --save-dev
npm install webpack-cli@3.3.6 --save-dev
npm install babel-loader@8.0.0-beta.0 --save-dev // 负责把调用babel来进行代码的转换
npm install @babel/core@7.5.5 --save-dev // babel的核心库
npm install @babel/preset-env@7.5.5 --save-dev // 负责把es6语法的代码转换成es5
在项目的根目录,执行脚本构建项目
npm run dev
打包后输出的结果
|-- dist
| |-- bundle.js
|-- node_modules
|-- package-lock.json
|-- package.json
|-- src
| |-- common.js
| |-- index.html
| `-- main.js
`-- webpack.config.js
打包之后的 bundle.js
文件的部分内容:
"./src/common.js":(function(module, exports, __webpack_require__) {
"use strict";
eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n
// 1.箭头函数 和 const 是es6新的语法。目的是要把es6新的语法转成es5语法\nvar showHelloWorld = function showHelloWorld(content) {\n alert(new Person().name);\n};
// 2.class 定义类也是es6新的语法。目的是要把es6新的语法转成es5语法\n\n\nvar Person =\n/*#__PURE__*/\nfunction () {\n function Person() {\n _classCallCheck(this, Person);\n\n this.name = 'person';\n }\n\n _createClass(Person, [{\n key: \"testSet\",\n value: function testSet() {\n
//3.Set Promise 和 fetch 是es6新增的API。目的是要把es6新的API 添加补丁垫片 polyfill\n var set = new Set([1, 2, 3]);\n new Promise(function (resove, reject) {\n fetch('');\n });\n }\n }]);\n\n return Person;\n}(); // 通过 CommonJS 规范导出 showHelloWorld 函数\n\n\nmodule.exports = showHelloWorld;\n\n//# sourceURL=webpack:///./src/common.js?");
/***/ }),
通过上面打包后的结果可以看出:
1.箭头函数 转换成 function函数;2.class定义的类 转换成function构造函数类定义;3.新增加的API还是原样输出。所以说上面的编译后的代码还是不能直接运行在旧浏览器上,因为新增加的API旧浏览器不兼容。
Polyfill : 填充工具,垫片,也可说是补丁
该@babel/polyfill模块,包括核心的JS和一个自定义的再生运行模拟一个完整的ES2015 +环境。
这意味着您可以使用新的API: Promise
或WeakMap
静态方法,如Array.from
或Object.assign
,实例方法一样Array.prototype.includes
,和生成器函数(前提是你使用的再生器插件)。polyfill增加了全局范围以及原生原型,就像String
这样做。
对于 library/tool 来说,这可能太多了。如果您不需要像这样的实例方法Array.prototype.includes
,则可以使用transform runtime插件而不是完全污染全局范围@babel/polyfill
。
更进一步,如果您确切知道需要填充的功能,可以直接从core-js中获取它们。
由于我们正在构建应用程序,我们可以安装@babel/polyfill
:
npm install --save @babel/polyfill
请注意该
--save
选项,而不是--save-dev
因为这是需要在源代码之前运行的polyfill。
项目中集成 @babelPolyfill 的三种方式
When used alongside @babel/preset-env
,
If useBuiltIns: 'usage'
is specified in .babelrc
then do not include @babel/polyfill
in either webpack.config.js
entry array nor source. Note, @babel/polyfill
still needs to be installed.
If useBuiltIns: 'entry'
is specified in .babelrc
then include @babel/polyfill
at the top of the entry point to your application via require
or import
as discussed above.
If useBuiltIns
key is not specified or it is explicitly set with useBuiltIns: false
in your .babelrc, add @babel/polyfill
directly to the entry array in your webpack.config.js
. like :
module.exports = {
entry: ["@babel/polyfill", “./app/js”],
};
因为没有在.babelrc
文件中配置过 useBuiltIns
属性, 所以下面案例将采用第三种,:
webpack.config.js
const path = require('path');
module.exports = {
// 4.采用第三种集成:@babel/Polyfill
entry: ['@babel/polyfill', './src/main.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/, // 1.匹配 .css 结尾的文件,注意test的值不是一个字符串,而是一个正则
exclude: /(node_modules|bower_components)/, // 2.排除这两个文件不需要打包
// 3.使用babel-loader编译js代码
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
],
}
]
}
};
use
接收一个数组
,数组
中可以存放字符串和对象
,并且use的别名是 loaders。
在项目的根目录,执行脚本构建项目
npm run dev
打包后输出的结果
|-- dist
| |-- bundle.js
|-- node_modules
|-- package-lock.json
|-- package.json
|-- src
| |-- common.js
| |-- index.html
| `-- main.js
`-- webpack.config.js
打包之后的 bundle.js
文件的部分内容:
// 1.这里的代码是polyfill垫片添加的部分补丁
"./node_modules/core-js/modules/_microtask.js":(function(module, exports, __webpack_require__) {
eval("var global = __webpack_require__(/*! ./_global */ \"./node_modules/core-js/modules/_global.js\");\nvar macrotask = __webpack_require__(/*! ./_task */ \"./node_modules/core-js/modules/_task.js\").set;\nvar Observer = global.MutationObserver || global.WebKitMutationObserver;\nvar process = global.process;\n
var Promise = global.Promise;\nvar isNode = __webpack_require__(/*! ./_cof */ \"./node_modules/core-js/modules/_cof.js\")(process) == 'process';\n\nmodule.exports = function () {\n var head, last, notify;\n\n var flush = function () {\n var parent, fn;\n if (isNode && (parent = process.domain)) parent.exit();\n while (head) {\n fn = head.fn;\n head = head.next;\n try {\n fn();\n } catch (e) {\n if (head) notify();\n else last = undefined;\n throw e;\n }\n } last = undefined;\n if (parent) parent.enter();\n };\n\n // Node.js\n if (isNode) {\n notify = function () {\n process.nextTick(flush);\n };\n // browsers with MutationObserver, except iOS Safari - https://github.com/zloirock/core-js/issues/339\n } else if (Observer && !(global.navigator && global.navigator.standalone)) {\n var toggle = true;\n var node = document.createTextNode('');\n new Observer(flush).observe(node, { characterData: true ....................?");
/***/ }),
"./node_modules/core-js/modules/_new-promise-capability.js":
/*!*****************************************************************!*\
!*** ./node_modules/core-js/modules/_new-promise-capability.js ***!
\*****************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\n// 25.4.1.5 NewPromiseCapability(C)\nvar aFunction = __webpack_require__(/*! ./_a-function */ \"./node_modules/core-js/modules/_a-function.js\");\n\nfunction PromiseCapability(C) {\n var resolve, reject;\n this.promise = new C(function ($$resolve, $$reject) {\n if (resolve !== undefined || reject !== undefined) throw TypeError('Bad Promise constructor');\n resolve = $$resolve;\n reject = $$reject;\n });\n this.resolve = aFunction(resolve);\n this.reject = aFunction(reject);\n}\n\nmodule.exports.f = function (C) {\n return new PromiseCapability(C);\n};\n\n\n//# sourceURL=webpack:///./node_modules/core-js/modules/_new-promise-capability.js?");
/***/ }),
// 2.这里的代码是bale 转换后的输出
"./src/common.js": (function(module, exports, __webpack_require__) {
"use strict";
eval("
// 1.箭头函数 和 const 是es6新的语法。目的是要把es6新的语法转成es5语法\nvar showHelloWorld = function showHelloWorld(content) {\n alert(new Person().name);\n};
// 2.class 定义类也是es6新的语法。目的是要把es6新的语法转成es5语法\n\n\nvar Person =\n/*#__PURE__*/\nfunction () {\n function Person() {\n _classCallCheck(this, Person);\n\n this.name = 'person';\n }\n\n _createClass(Person, [{\n key: \"testSet\",\n value: function testSet() {\n
//3.Set Promise 和 fetch 是es6新增的API。目的是要把es6新的API 添加补丁垫片 polyfill\n var set = new Set([1, 2, 3]);\n new Promise(function (resove, reject) {\n fetch('');\n });\n }\n }]);\n\n return Person;\n}(); // 通过 CommonJS 规范导出 showHelloWorld 函数\n\n\nmodule.exports = showHelloWorld;\n\n//# sourceURL=webpack:///./src/common.js?");
/***/ }),
通过上面打包后的结果可以看出:
1.箭头函数 转换成 function函数;2.class定义的类 转换成function构造函数类定义;3.ES6新增加的API 垫片polyfill自定代替实现了。所以这个版本编译出来的js代码可以兼容旧版本的浏览器。
https://babeljs.io/docs/en/babel-plugin-transform-runtime
Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend
。 默认情况下会被添加到每一个需要它的文件中
你可以引入 @babel/plugin-transform-runtime 作为一个独立模块( 简称: transform-runtime ),来避免重复引入,可以优化打包后的代码, 因为 @babel/plugin-transform-runtime
可以使编译时使用的所有辅助代码从这里引用,优化打包的代码。
@babel/plugin-transform-runtime
是 Babel 官方提供的一个插件,作用是减少冗余代码。 Babel 在把 ES6 代码转换成 ES5 代码时通常需要一些 ES5 写的辅助函数来完成新语法的实现,例如在转换 class extent
语法时会在转换后的 ES5 代码里注入 _extent
辅助函数用于实现继承:
function _extent(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
}
这会导致每个使用了 class extent
语法的文件都被注入重复的_extent
辅助函数代码,@babel/transform-runtime
的作用在于不把辅助函数内容注入到文件里,而是注入一条导入语句:
var _extent = require('@babel/runtime/helpers/_extent');
这样能减小 Babel 编译出来的代码的文件大小。
同时需要注意的是由于 @babel/plugin-transform-runtime
注入了 require('babel-runtime/helpers/_extent')
语句到编译后的代码里,需要安装 @babel/runtime
依赖到你的项目后,代码才能正常运行。 也就是说 @babel/plugin-transform-runtime
和 @babel/runtime
需要配套使用,使用了 @babel/plugin-transform-runtime
后一定需要 @babel/runtime
。
注意:
bable 7.x 版本以后使用的是: @babel/plugin-transform-runtime 和 @babel/runtime
bable 7.x 版本以前使用的是: babel-plugin-transform-runtime 和 babel-runtime
安装该插件
npm install @babel/plugin-transform-runtime@7.5.5 --save-dev
npm install @babel/runtime@7.5.5 --save
package.json
{
"name": "testjsloader",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"author": "liujun",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"babel-loader": "^8.0.0-beta.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.6"
},
"dependencies": {
"@babel/polyfill": "^7.4.4",
"@babel/runtime": "^7.5.5"
}
}
项目集成 transform-runtime
的插件
webpack.config.js
const path = require('path');
module.exports = {
// 1.使用了垫片: 兼容新增API
entry: ['@babel/polyfill', './src/main.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
// 2.使用bable,新语法转成es5语法
presets: [
'@babel/preset-env'
],
// 3.优化打包的代码
plugins: ['@babel/transform-runtime']
}
}
],
}
]
}
};
use
接收一个数组
,数组
中可以存放字符串和对象
,并且use的别名是 loaders。
在项目的根目录,执行脚本构建项目
npm run dev
打包后输出的结果
|-- dist
| |-- bundle.js
|-- node_modules
|-- package-lock.json
|-- package.json
|-- src
| |-- common.js
| |-- index.html
| `-- main.js
`-- webpack.config.js
第一种写法:
webpack.config.js
const path = require('path');
module.exports = {
entry: ['@babel/polyfill', './src/main.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
// 1.Babel-Loader参数
options: {
presets: [
'@babel/preset-env'
],
plugins: ['@babel/transform-runtime']
}
}
],
}
]
}
};
第二种写法:
webpack.config.js
const path = require('path');
module.exports = {
entry: ['@babel/polyfill', './src/main.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
// 1.Babel-Loader参数
// options: {
// presets: [
// '@babel/preset-env'
// ],
// plugins: ['@babel/transform-runtime']
// }
}
],
}
]
}
};
在项目根目录新建一个:bable.config.js 文件
|-- dist
| |-- bundle.js
|-- node_modules
|-- package-lock.json
|-- package.json
|-- bable.config.js
|-- src
| |-- common.js
| |-- index.html
| `-- main.js
`-- webpack.config.js
bable.config.js
module.exports = function (api) {
api.cache(true);
console.log(api)
const presets = [
'@babel/preset-env'
];
const plugins = [
'@babel/transform-runtime'
];
return {
presets,
plugins
};
}
或者
module.exports = function (api) {
api.cache(true);
console.log(api)
const presets = [
[
'@babel/preset-env',
// 给 @bable/preset-env 添加 option 参数
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: false, // 配置 @babel/polyfill 在entry中引入
}
]
];
const plugins = [
'@babel/transform-runtime'
];
return {
presets,
plugins
};
}
总结:
上面webpack.config.js配置中的use通过正则 /\.js$/
匹配所有以 `.js为后缀的 JS文件,再使用Bable-Loader 去处理。具体处理流程如下:
项目源码地址