Webpack4 配置JS Loader

1.ES6兼容介绍

ECMAScript 6.0 是2015年发布的下一代 JavaScript 语言标准,它引入了新的语法和 API 来提升开发效率。

虽然目前部分浏览器和 Node.js 已经支持 ES6,但由于它们对 ES6 所有的标准支持不全,这导致在开发中不敢全面地使用 ES6。

通常我们需要把采用 ES6 编写的代码转换成目前已经支持良好的 ES5 代码,这包含2件事:

  1. 把新的 ES6 语法用 ES5 实现,例如 ES6 的 class 语法用 ES5 的 prototype 实现,需要用到:@babel/preset-env。
  2. 给新的 API 注入 polyfill ,例如项目使用 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 ....

2.Babel

Babel 是一个JavaScript 编译器

Babel是一个工具链,主要用于将ECMAScript 2015+代码转换为当前和旧版浏览器或环境中的向后兼容版本的JavaScript。以下是Babel可以为您做的主要事情:

  • 转换语法
  • 目标环境中缺少Polyfill功能(通过@ babel / polyfill)
  • 源代码转换(codemods)
  • 和更多!(看看这些视频的灵感)

下面是用 Babel编译器编译 JavaScript 的案例:

// Babel 编译前: ES6 箭头函数
[1, 2, 3].map((n) => n + 1);

// Babel 编译后: 箭头函数转换成es5语法的函数
[1, 2, 3].map(function(n) {
  return n + 1;
});

有关编译器的精彩教程,请查看超级微型编译器,它还解释了Babel本身如何在高级别上工作。

3.配置JS Loader

新建一个项目

|-- 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旧浏览器不兼容。

4.配置 Polyfill

Polyfill : 填充工具,垫片,也可说是补丁

该@babel/polyfill模块,包括核心的JS和一个自定义的再生运行模拟一个完整的ES2015 +环境。

这意味着您可以使用新的API: PromiseWeakMap静态方法,如Array.fromObject.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代码可以兼容旧版本的浏览器。

5.Babel 配置 Plugins

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

6.Babel-Loader参数

第一种写法:

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 去处理。具体处理流程如下:

  1. 通过 bable-loader 去调用Bable处理 JS代码的转换。
  2. @babel/preset-env 会自动把ES6新的语法自动转成ES5的语法。
  3. @babel/transform-runtime 可以使编译时使用的所有辅助代码从这里引用,优化打包的代码
  4. @babel/polyfill 会自动把ES6新的API 添加垫片来兼容旧浏览器。

项目源码地址

你可能感兴趣的:(构建工具)