HMR(模块热替换)

配置

  1. const webpack = require("webpack");
  2. plugins: [
  3. new webpack.HotModuleReplacementPlugin() // 模块热替换插件
  4. ]
  5. // 启动 HMR
  6. devServer: {
  7. contentBase: "./dist",
  8. open: true,
  9. hot:true,
  10. // 即便HMR不⽣效,浏览器也不⾃动刷新,就开启hotOnly
  11. hotOnly:true
  12. }

css 模块HMR

  1. var btn = document.createElement("button");
  2. btn.innerHTML = "新增";
  3. document.body.appendChild(btn);
  4. btn.onclick = function() {
  5. var div = document.createElement("div");
  6. div.innerHTML = "item";
  7. document.body.appendChild(div);
  8. };

注意:启动HMR后,不⽀持hash,chunkhash,contenthash 且不能使用 MiniCssExtractPlugin.loader 抽离css ,因为 HMR 加载样式是借助于 **style-loader**,此 loader 使用了 **module.hot.accept**,在 CSS 依赖模块更新之后,会将其 patch 到 **<style>** 标签中。

js 模块的 HMR

测试时需要使⽤ module.hot.accept 来观察模块是否更新,实际开发中,不需要手动观察模块是否更新,在Vue项目中,Vue loader 支持 vue 组件的HMR。

  1. // counter.js
  2. function counter() {
  3. var div = document.createElement("div");
  4. div.setAttribute("id", "counter");
  5. div.innerHTML = 1;
  6. div.onclick = function() {
  7. div.innerHTML = parseInt(div.innerHTML, 10) + 1;
  8. };
  9. document.body.appendChild(div);
  10. }
  11. export default counter;
  12. // number.js
  13. function number() {
  14. var div = document.createElement("div");
  15. div.setAttribute("id", "number");
  16. div.innerHTML = 13000;
  17. document.body.appendChild(div);
  18. }
  19. export default number;
  20. // index.js
  21. import number from './number'
  22. import counter from './counter'
  23. number()
  24. counter()
  25. if (module.hot) {
  26. module.hot.accept("./number", function() {
  27. document.body.removeChild(document.getElementById("number"))
  28. number()
  29. })
  30. }

Babel 处理 ES6

开发过程中,我们会用到很多 ES6 的新特性,但是部分浏览器对这些新特性的支持并不是很好,所以我们需要使用Babel 对代码进行转换,使得我们的项目可以兼容更多的浏览器版本。

Babel 默认只转换新的 JavaScript 语法,而不转换新的 API,比如IteratorGeneratorSetMapProxyReflectSymbolPromise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转换。

举例来说,ES6 在Array对象上新增了Array.from方法。Babel 就不会转换这个方法。如果想让这个方法运行,必须使用@babel-polyfill,为当前环境提供一个垫片。

babel-polyfill主要包含了core-jsregenerator-runtime两部分。

  • core.js:提供了如ES5、ES6、ES7等规范中新定义的各种对象、方法的模拟实现。
  • regenerator-runtime:提供 generator 支持,如果应用代码中用到 generator、async 函数的话需要用到。

**babel-polyfill**实现的 Promise 对象扩展了 finally 方法,引用后不需要再使用 es6-promise 扩展**Promise.finally**(在 ES2018 中 finally 方法已经成为了标准)。

在 7.x 的版本中,babel 的依赖都添加了@前缀

安装

  1. yarn add babel-loader @babel/core @babel/preset-env -D
  2. yarn add @babel/polyfill

@babel/core 的作用是把 js 代码转换成 AST(抽象语法树),方便各个插件分析语法进行相应的处理

配置

  1. // 可以把 options 抽离到 .babelrc 文件中
  2. module: {
  3. rules: [
  4. {
  5. test: /\.js$/,
  6. exclude: /node_modules/,
  7. loader: "babel-loader",
  8. options: {
  9. presets: [ "@babel/preset-env" ]
  10. }
  11. }
  12. ]
  13. }
  14. // index.js 引入 @babel/polyfill
  15. import "@babel/polyfill";

经过上面的配置,我们的项目就可以在大部分的浏览器上运行,但是我们会发现打包之后文件的体积大了很多,因为 polyfill 默认会把所有的 ES6+ 的特性注入进来,如果想要按需注入,可以设置 useBuiltIns 参数。

useBuiltIns 选项是 babel 7 的新功能,这个选项告诉 babel 如何配置 @babel/polyfill 。

它有三个参数可以使⽤:

  • entry:需要在 webpack 的⼊⼝⽂件⾥手动 import “@babel/polyfill” 。 babel 会根据你的使⽤情况导⼊垫⽚,没有使⽤的功能不会被导⼊相应的垫⽚。
  • usage::不需要 import ,全⾃动检测,但是要安装 @babel/polyfill 。(试验阶段)
  • false::如果你 import “@babel/polyfill” ,它不会排除掉没有使⽤的垫⽚,文件的体积会很大。 (不推荐)
  1. // .babelrc
  2. {
  3. "presets": [
  4. [
  5. "@babel/preset-env",
  6. {
  7. "targets": {
  8. "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
  9. },
  10. "corejs": {
  11. "version": 2,
  12. "proposals": true
  13. },
  14. "useBuiltIns": "usage" // 按需注⼊
  15. }
  16. ]
  17. ]
  18. }

@babel/preset-env 在升级到 7.4.0 以上的版本以后,既支持core-js@2,也支持core-js@3。所以增加了corejs的配置,来指定所需的版本。如果没有配置,默认使用 core-js@2, 并且会提示我们指定一个版本。

core-js@3

当 core-js 升级到 3 .0 的版本后,我们就不需要再使用 @babel/polyfill,因为它只包含 core-js 2.0 的版本。所以在 @babel/prest-env 升级到 7.4.0 及以上版本并且使用 core-js@3,需要做如下的替换工作:

  1. // 安装 core-js@3.0 和 regenerator-runtime
  2. yarn add core-js@3 regenerator-runtime
  3. // .babelrc
  4. presets: [
  5. ["@babel/preset-env", {
  6. useBuiltIns: "entry", // or "usage"
  7. corejs: 3,
  8. }]
  9. ]
  10. // 入口文件index.js
  11. // 如果配置了 "useBuiltIns": "usage",下面的操作就不需要了
  12. // before
  13. import "@babel/polyfill";
  14. // after
  15. import "core-js/stable";
  16. import "regenerator-runtime/runtime";

@babel/plugin-transform-runtime

当我们开发的是组件库、⼯具库的时候, @babel-polyfill 就不适合了,因为 ployfill 模拟的对象和方法是注⼊到全局变量 window 对象下的,会污染全局环境。所以推荐闭包方式:@babel/plugin-transform-runtime 。

  1. // 安装
  2. yarn add @babel/plugin-transform-runtime @babel/runtime-corejs3 -D
  3. // 配置
  4. "plugins": [
  5. [
  6. "@babel/plugin-transform-runtime",
  7. {
  8. "absoluteRuntime": false,
  9. "corejs": 3,
  10. "helpers": true,
  11. "regenerator": true,
  12. "useESModules": false
  13. }
  14. ]
  15. ]

注意:在之前的版本中,@babel/runtime-corejs2 最大的问题就是无法模拟实例上的方法,比如数组的 includes 方法就无法被 polyfill。但是在 core-js@3 的版本中,所有的实例方法都可以被polyfill了。

配置 React 打包环境

  1. // 安装依赖
  2. yarn add @babel/preset-react -D
  3. yarn add react react-dom
  4. // 配置
  5. {
  6. "presets": [
  7. [
  8. "@babel/preset-env"
  9. {
  10. "targets": {
  11. "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
  12. },
  13. "corejs": 3,
  14. "useBuiltIns": "usage" // 按需注⼊
  15. }
  16. ]
  17. "@babel/preset-react"
  18. ]
  19. }
  20. // react 代码
  21. import React, { Component } from "react";
  22. import ReactDom from "react-dom";
  23. class App extends Component {
  24. render() {
  25. return <div>hello world</div>;
  26. }
  27. }
  28. ReactDom.render(<App />, document.getElementById("app"));

tree shaking

开启 tree shaking 之后,生产环境下 webpack 会帮助我们把没有用到的的代码去掉 (只⽀持 ES Module 方式引入的模块 )

  1. // webpack 配置
  2. optimization: {
  3. // 开启 tree shanking
  4. usedExports: true,
  5. }
  6. // 开启 tree shaking 之后会产生一些副作用,需要在 package.json 中配置 sideEffects 选项来清除副作用
  7. "sideEffects": [
  8. "*.css",
  9. "*.less",
  10. "@babel/polyfill"
  11. ]

code splitting

假如我们引⼊⼀个第三⽅的⼯具库,体积为1mb,⽽我们的业务逻辑代码也有1mb,那么打包出来的体积⼤⼩会是2mb ,会影响代码加载速度和首页渲染速度。我们可以使用 code splitting 将第三方工具库代码单独打包。其实code Splitting 概念 与 webpack 并没有直接的关系,只不过webpack中提供了⼀种更加⽅便的⽅法供我们实现代码分割 ,基于 https://webpack.js.org/plugins/split-chunks-plugin

  1. optimization:{
  2. // ⾃动做代码分割
  3. splitChunks:{
  4. chunks:"all"
  5. }
  6. }
  7. // 详细配置
  8. optimization: {
  9. splitChunks: {
  10. chunks: 'async', // 对同步 initial,异步 async,所有的模块有效 all
  11. minSize: 30000, // 最⼩尺⼨,当模块⼤于30kb
  12. maxSize: 0, // 对模块进⾏⼆次分割时使⽤,不推荐使⽤
  13. minChunks: 1, // 打包⽣成的chunk⽂件最少有⼏个chunk引⽤了这个模块
  14. maxAsyncRequests: 5, // 最⼤异步请求数,默认5
  15. maxInitialRequests: 3, // 最⼤初始化请求书,⼊⼝⽂件同步请求,默认3
  16. automaticNameDelimiter: '~', // 打包分割符号
  17. name: true, // 打包后的名称,除了布尔值,还可以接收⼀个函数function
  18. cacheGroups: { //缓存组
  19. vendors: {
  20. test: /[\\/]node_modules[\\/]/,
  21. name:"vendor", // 要缓存的 分隔出来的 chunk 名称
  22. priority: -10 // 缓存组优先级 数字越⼤,优先级越⾼
  23. },
  24. other:{
  25. chunks: "initial", // 必须三选⼀: "initial" | "all" | "async"(默认就是async)
  26. test: /react|lodash/, // 正则规则验证,如果符合就提取
  27. chunk,
  28. name:"other",
  29. minSize: 30000,
  30. minChunks: 1,
  31. },
  32. commons:{
  33. test:/(react|react-dom)/,
  34. name:"react_vendors",
  35. chunks:"all"
  36. },
  37. default: {
  38. minChunks: 2,
  39. priority: -20,
  40. reuseExistingChunk: true//可设置是否重⽤该chunk
  41. }
  42. }
  43. }
  44. }

参考资源