babel-loader可以用来处理ES6+并将其编译成ES5,它使得我们能够在工程中使用最新的语言特性(甚至还在提案中),同时不必特别关注这些特性在不同平台的兼容问题。

新建项目babel

  1. npm init

package.json

  1. {
  2. "name": "babel-practice",
  3. "version": "1.0.0",
  4. "description": "熟悉babel-loader的配置",
  5. "main": "index.js",
  6. "scripts": {
  7. "test": "echo \"Error: no test specified\" && exit 1"
  8. },
  9. "keywords": [
  10. "babel"
  11. ],
  12. "author": "zsc",
  13. "license": "ISC"
  14. }

本地安装webpack套件

—save-dev选项可以本地安装

  1. npm install --save-dev webpack webpack-cli webpack-dev-server
  2. ···
  3. + webpack-dev-server@4.4.0
  4. + webpack-cli@4.9.1
  5. + webpack@5.60.0
  6. added 326 packages from 274 contributors in 29.269s

查看套件版本

npx webpack -v 或 npx webpack-cli -v

webpack: 5.60.0
webpack-cli: 4.9.1
webpack-dev-server 4.4.0

创建webpack.config.js,初步配置

const path = require('path');

module.exports = {
    entry:'./src/index.js',
    output:{
        filename:'[name]@[chunkhash].js',
        path:path.join(__dirname,"dist")
    },
    devtool:"inline-source-map",
    mode:'development'
}

配置npm script

package.json
json格式最后一组键值对不加逗号,且字符串键和值都要用双引号。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"webpack"
  },

初步打包

//shell
npm run build

assets by status 1.43 KiB [cached] 1 asset
./src/index.js 235 bytes [built] [code generated]
webpack 5.60.0 compiled successfully in 98 ms

配置webpack-dev-server

在scripts下添加一个指令dev。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "dev":"webpack-dev-server"
  },

可以根据需要选择其他开发环境:https://webpack.docschina.org/guides/development/

配置devServer

//webpack.config.js
const path = require('path');

module.exports = {
    entry:'./src/index.js',
    output:{
        filename:'[name]@[chunkhash].js',
        path:path.join(__dirname,"dist")
    },
    devServer:{
        static:'/dist/',
        port:8888,
    },
      devtool:"inline-source-map",
    mode:'development'
}

devServer的更详细配置:https://webpack.docschina.org/configuration/dev-server/

试运行webpack-dev-server

webpack-dev-server默认只监视js文件的变化。

npm run dev

配置对HTML文件也进行监视。

devServer:{
  static:'/dist/',
  port:8888,
  watchFiles:['src/*.html'],
},

监听src目录下的HTML文件。相对于webpack.config.js的目录。

安装babel-loader

本地安装套件

npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save-dev babel-loader @babel/plugin-transform-runtime

@babel/cli可以不安装

实验代码

let p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        console.log("babel");
        resolve(new Date());
    },2000);
}).then((date)=>{
    console.log(date);
}).finally(()=>{
    console.log('finish');
});


let a = b => b;


const double = [1, 2, 3].map(num => num * 2);
console.log(double); // [2,4,6]


let bob = {
  _name: "Bob",
  _friends: ["Sally", "Tom"],
  printFriends() {
    this._friends.forEach(f => console.log(this._name + " knows " + f));
  },
};
console.log(bob.printFriends());

全局安装core-js和@babel/runtime

npm install --save core-js @babel/runtime

core-js包含的是polyfill。

配置babel-loader

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: "babel-loader",
        options: {
          presets: [
            ["@babel/preset-env",
             {
               targets: {
                 "chrome": "58",
                 "ie": "9"
               },
               useBuiltIns: "usage",
               corejs: "3.19.0"
             }
            ],
          ],
          plugins: ["@babel/plugin-transform-runtime"]
        }
      }
    },
  ],
}

切记,写loader是从module键开始的。

在这里写的corejs的值是安装的版本值,但是只是写corejs:"3",结果也一样。

转译后的JavaScript代码

//···

(() => {
"use strict";
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.object.to-string.js */ "./node_modules/core-js/modules/es.object.to-string.js");
/* harmony import */ var core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_object_to_string_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es.promise.js */ "./node_modules/core-js/modules/es.promise.js");
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var core_js_modules_es_promise_finally_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/es.promise.finally.js */ "./node_modules/core-js/modules/es.promise.finally.js");

//···一大堆polyfill···

//转译结果

var p = new Promise(function (resolve, reject) {
  setTimeout(function () {
    console.log("babel");
    resolve(new Date());
  }, 2000);
}).then(function (date) {
  console.log(date);
}).finally(function () {
  console.log('finish');
});

var a = function a(b) {
  return b;
};

var double = [1, 2, 3].map(function (num) {
  return num * 2;
});
console.log(double); // [2,4,6]

var bob = {
  _name: "Bob",
  _friends: ["Sally", "Tom"],
  printFriends: function printFriends() {
    var _this = this;

    this._friends.forEach(function (f) {
      return console.log(_this._name + " knows " + f);
    });
  }
};
console.log(bob.printFriends());
})();

//···
//控制台回显信息
assets by status 291 KiB [cached] 1 asset
asset index.html 993 bytes [compared for emit]
runtime modules 1.13 KiB 5 modules
modules by path ./node_modules/core-js/internals/*.js 58.4 KiB 104 modules
modules by path ./node_modules/core-js/modules/*.js 18.7 KiB
  ./node_modules/core-js/modules/es.object.to-string.js 380 bytes [built] [code generated]
  ./node_modules/core-js/modules/es.promise.js 14 KiB [built] [code generated]
  ./node_modules/core-js/modules/es.promise.finally.js 1.66 KiB [built] [code generated]
  ./node_modules/core-js/modules/web.timers.js 1.23 KiB [built] [code generated]
  ./node_modules/core-js/modules/es.array.map.js 598 bytes [built] [code generated]
  ./node_modules/core-js/modules/web.dom-collections.for-each.js 887 bytes [built] [code generated]
./src/index.js 923 bytes [built] [code generated]
webpack 5.60.0 compiled successfully in 1333 ms

通过查看转译后的代码和执行npm run build后的回显信息可以看到有哪些polyfill被自动加载了。

package.json依赖

  "devDependencies": {
    "@babel/cli": "^7.15.7",
    "@babel/core": "^7.15.8",
    "@babel/plugin-transform-runtime": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "babel-loader": "^8.2.3",
    "css-loader": "^6.5.0",
    "html-webpack-plugin": "^5.5.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.60.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.4.0"
  },
  "dependencies": {
    "@babel/runtime": "^7.15.4",
    "core-js": "^3.19.0"
  }

useBuiltIns

该字段有三种值:false | usage | entry
上面展示的是值为usage时的配置。

**useBuiltIns:"usage"**
表示会根据配置的浏览器兼容,以及代码中用到的 API 来进行 polyfill,实现了按需添加。
须同时指定corejs的值,这里应该填3。因为版本2的core-js已弃用,而且会缺少一些polyfill,比如core-js/modules/web.dom-collections.for-each.jscore-js/modules/web.timers.js(试着改用2就可以发现少了这些polyfill)。

**useBuiltIns:"false"**
此时不对 polyfill 做操作。如果引入 @babel/polyfill,则无视配置的浏览器兼容,引入所有的 polyfill。

**useBuiltIns:"entry"**
根据配置的浏览器兼容,引入浏览器不兼容的 polyfill。需要在入口文件手动添加 import ‘@babel/polyfill’,会自动根据 browserslist 替换成浏览器不兼容的所有 polyfill。
这里需要指定 core-js 的版本, 如果 “corejs”: 3, 则 import ‘@babel/polyfill’ 需要改成

import 'core-js/stable';
import 'regenerator-runtime/runtime';

插件@babel/plugin-transform-runtime

这个插件是用来复用辅助函数的。
外部化对帮助程序和内置函数的引用,自动填充您的代码而不会污染全局变量
项目地址:https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-runtime

@babel/runtime是什么

@babel/runtime is a library that contains Babel modular runtime helpers and a version of regenerator-runtime.

这是以模块化方式包含函数实现的包。里面包含babel模块运行helper和某个版本的regenerator-runtime。作为@babel/plugin-transform-runtime运行时的依赖。
如node_modules/@babel/runtime/helper/classCallCheck.js:

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

module.exports = _classCallCheck;
module.exports["default"] = module.exports, module.exports.__esModule = true;

这个函数用来检验实例的构造函数,如果不匹配则抛出错误。
Babel可能为输出文件注入辅助函数代码,这时就需要搭配@babel/plugin-transform-runtime来实现复用——只需通过require引入,而不用直接注入代码。

全局污染问题,是怎么样的

polyfill方案,目前主要有以下两种:

  • 方案一是全局入口文件头部引入import "@babel/polyfill";或者在webpack入口entry: ["@babel/polyfill", "./src/index.js"]
  • 方案二是@babel/plugin-transform-runtime@babel/runtime

作者:Kevin_St
webpack5+babel7爬坑记

babel-loader - 图1

转译方案

@babel/plugin-transform-runtime 和@babel/runtime

{
  test:/\.js$/,
    exclude:/node_modules/,
      use:{
        loader:"babel-loader",
          options: {
            presets:[
              [
                "@babel/preset-env",
                {
                  targets:{
                    'ie':9
                  },
                  useBuiltIns:"usage",
                  corejs:"3"
                },
              ],
            ],
              plugins:[
                "@babel/plugin-transform-runtime",
              ]
          },
      },
},

可以将部分配置放入项目根目录下新建的的.babelrcbabel.config.json文件中去,只需写options的值。

package.json

  "devDependencies": {
    "@babel/core": "^7.15.8",
    "@babel/plugin-transform-runtime": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.60.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.4.0"
  },
  "dependencies": {
    "@babel/runtime": "^7.15.4",
    "core-js": "^3.19.0"
  }

补充

什么是polyfill

又称垫片。用户使用各家各种版本的浏览器,这些浏览器在一些API的实现上会存在差异,比如有的浏览器有这种函数,有的没有。polyfill就是非官方的功能补全。比如,IE5 之前的版本中没有 document.getElementById() 这个 DOM 方法,但可以通过document.all 属性实现同样的功能。为此,可以进行如下能力检测:

function getElement(id) {
  if (document.getElementById) {
      return document.getElementById(id);
  } else if (document.all) {
      return document.all[id];
  } else {
      throw new Error("No way to retrieve element!");
  }
}

这里的getElement(id)就属于polyfill。

这涉及到浏览器检测内容

@babel/preset-env解决什么问题

@babel/preset-env解决的是将高版本写法转化成低版本写法的问题,因为不同环境下低版本的写法有可能不同。根据你的约束(targets)进行语法转换。

core-js库是什么

JavaScript 的模块化标准库。包括ECMAScript 到 2021 年的 polyfills:promises、symbols、collections、iterators、typed arrays、许多其他特性、ECMAScript 提案、一些跨平台的 WHATWG/W3C 特性和提案,如URL. 您可以仅加载所需的功能或在没有全局命名空间污染的情况下使用它。
项目地址:https://github.com/zloirock/core-js

**cacheDirectory**

默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。如果设置了一个空值(loader: 'babel-loader?cacheDirectory') 或者 true (loader: 'babel-loader?cacheDirectory=true'),loader 将使用默认的缓存目录 node_modules/.cache/babel-loader,如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。
通过使用 cacheDirectory 选项,将 babel-loader 提速至少两倍。这会将转译的结果缓存到文件系统中。

相关文献

聊一聊Babel7.x+Webpack(babel7.4+的使用感受
webpack5+babel7爬坑记
《Webpack实战:入门,进阶与调优》居玉皓 著