Webpack5 学习笔记

251 阅读16分钟

1. webpack 和 webpack-cli 的关系

webpack 的安装目前分为两个:webpackwebpack-cli
webpack、webpack-cli 的区别
控制台中执行 webpack 指令,会执行 node_modules 下的 .bin 目录下的 webpack 文件,webpack 的执行依赖 webpack-cli,如果没有安装 webpack-cli 就会报错。在 webpack-cli 中代码执行时,才是真正利用 webpack 进行编译和打包的过程。

npm install webpack webpack-cli –g    #全局安装
npm install webpack webpack-cli –D    #局部安装 (项目中使用)

全局安装 和 局部安装 的区别
局部安装:如果只有全局的 webpack,那么打包的时候,用了全局的 webpack,不同电脑的 webpack 版本不同会导致包的安装版本不同。
局部安装:每一个项目都有自己的 webpack 的版本, –D 是开发时依赖,定义了统一的 webpack 版本,打包的时候不会出现包的版本问题。
直接在命令行中执行 webpack 找的是全局的 webpack,如果想用局部的 webpack: 去 node_modules 中的 .bin 中找 webpack:./node_modules/.bin/webpack 执行npx webpack 默认找 node_modules 中的 .bin 下的 webpack 文件 在 package.json 中写脚本 "build": "npx webpack",在使用的时候 npm run build 相当于执行了 webpack 指令。

{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.14.0",
    "webpack-cli": "^4.3.1"
  }
}

webpack 是如何确定入口
运行 webpack 时,webpack 会查找当前目录下的 src/index.js 作为入口,如果没有存在 src/index.js 文件,就会报错。
QQ截图20230828230706.png webpack 是如何对项目进行打包
根据命令或者配置文件找到入口文件,从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等)然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)

2. webpack 对样式的处理

css-loader
webpack 可以处理 js 文件,但是当代码中 import 引入了 css 文件,webpack 不知道如何对其进行加载。

  • css-loader的安装:
npm install css-loader -D
  • css-loader 的使用
module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    // 必须是一个绝对路径
    path: path.resolve(__dirname, "./build")
  },
  module: {
    rules: [
      {
        test: /\.css$/, // 匹配资源
        use: [
          { loader: "css-loader" }
        ]
      }
    ]
  }
}

css-loader 只是负责将 .css 文件进行解析,并不会将解析之后的 css 插入到页面中,需要用 style-loader 完成插入样式的操作。

  • 安装style-loader: npm install style-loader -D
  • 配置 style-loader
      {
        test: /\.css$/, // 匹配资源
        use: [
          { loader: "style-loader" },
          { loader: "css-loader" }
        ]
      }

Less 处理

  1. 可以使用 less 工具来完成它的编译转换
  • 安装:npm install less -D
  • 执行:npx less ./src/css/title.less > title.css
  1. less-loader 处理
  • 安装: npm install less-loader -D
  • 配置 webpack.config.js
      {
        test: /\.less$/,
        use: [
          "style-loader",
          {
            loader: "css-loader"
          }
          "less-loader"
        ]
      }

3.browserslist

属性在浏览器市场占有率可以在Can I use... Support tables for HTML5, CSS3, etc 查看。
browserslist 工具
Browserslist是一个 在不同的前端工具之间 ,共享 目标浏览器和Node.js版本的配置 :

  • Autoprefixer
  • Babel
  • postcss-preset-env
  • eslint-plugin-compat
  • stylelint-no-unsupported-browser-features
  • postcss-normalize
  • obsolete-webpack-plugin
    浏览器查询过程
    可以编写类似于这样的配置:
> 1% 
last 2 versions 
not dead

这些工具会根据我们的配置来获取相关的浏览器信息,以方便决定是否需要进行兼容性的支持,条件查询使用的是 caniuse-lite 的工具,这个工具的数据来自于 caniuse 的网站上。
Browserslist 编写规则

  • defaults:Browserslist的默认浏览器(> 0.5%, last 2 versions, Firefox ESR, not dead)。
  • 5%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=工作过。
  • dead:24个月内没有官方支持或更新的浏览器。现在是IE 10,IE_Mob 11,BlackBerry 10,BlackBerry 7, Samsung 4和OperaMobile 12.1。
  • last 2 versions:每个浏览器的最后2个版本。
  • not ie <= 8:排除先前查询选择的浏览器。
    命令行使用browserslist:npx browserslist ">1%, last 2 version, not dead" QQ截图20230828235740.png
    配置browserslist
  • 在package.json中配置
"browserslist":[
    "last 2 version",
    "not dead",
    "> 0.2%"
]
  • .browserslistrc文件
> 0.5%
last 2 version
not dead

browserslist 包在安装 webpack 的时候自动下载,项目中有 browserslistrc 之后,执行 npx browserslist,可以看到符合当前规则的浏览器列表。
多个条件之间的关系 QQ截图20230829000616.png

4.PostCSS

作用:CSS的转换和适配(自动添加浏览器前缀、css样式的重置)
1. 命令行使用postcss
安装:npm install postcss postcss-cli -D
案例:写一个添加前缀的 css
安装 autoprefixer npm install autoprefixer -D
使用postcss工具,并且制定使用autoprefixer:npx postcss --use autoprefixer -o end.css ./src/css/style.css
转换之后:
QQ截图20230829221534.png
2. postcss-loader
安装npm install postcss-loader -D
配置(postcss需要有对应的插件):
QQ截图20230829222333.png

5.文件加载

file-loader 处理文件资源
file-loader 可以处理 import/require()方式引入的一个文件资源,并且将其放到输出的文件夹中。
安装npm install file-loader -D
配置:

      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: "img/[name].[hash:6].[ext]",
              // 输出文件存放路径
              outputPath: "img"
            }
          }
        ]
      }

处理后的文件名称按照一定的规则进行显示:保留原来的文件名、扩展名,同时为了防止重复,包含一个hash值。
常用的 placeholder

  • [ext]: 处理文件的扩展名。
  • [name]:处理文件的名称。
  • [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制)
  • [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到)
  • [hash:]:截图hash的长度,默认32个字符太长了
  • [path]:文件相对于webpack配置文件的路径

url-loader 处理小文件资源
url-loader 和 file-loader的工作方式相似,但是可以将较小的文件,转成 base64 的 URI
原则:小的图片转换base64(和页面一起请求,减少不必要的请求),大的图片要进行转换,反而会影响页面的请求速度 安装:npm install url-loader -D

      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: "img/[name].[hash:6].[ext]",
              // 小于 100 kb会转换为 uri,大于的不转换
              limit: 100 * 1024,
              outputPath:"img"
            }
          }
        ]
      }

asset module type
webpack5 之前,加载资源需要使用一些loader,比如raw-loader 、url-loader、file-loader,但是在 webpack5之后可以直接使用资源模块类型(asset module type),来替代上面的这些loader。
asset module type 通过添加 4 种新的模块类型,来替换所有这些 loader\

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现
            {
                test: /\.(png|jpe?g|gif|svg)$/i,
                type: "asset/resource"
            }
  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现;
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源 体积限制实现
            {
                test: /\.(png|jpe?g|gif|svg)$/i,
                type: "asset",
                generator: {
                    filename: "img/[name].[hash:6][ext]" // 输出路径和文件名
                },
                parser: {
                    dataUrlCondition: {
                        maxSize: 100 * 1024
                    }
                }
            }

处理字体图标

      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        }
      }

5.Plugin

Loader 是用于特定的模块类型进行转换,Plugin 可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等。 QQ截图20230829232204.png CleanWebpackPlugin
作用:重新打包,自动删除 dist 文件夹
安装:npm install clean-webpack-plugin -D 配置:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
   plugins: [
       new CleanWebpackPlugin()
   ] 
}

HtmlWebpackPlugin
作用:打包之后自动创建对应的入口文件的 index.html 安装npm install html-webpack-plugin -D 配置:

const HtmlWebpackPlugin = require('html-webpack-plugin');
module: {
    plugins: [
        new HtmlWebpackPlugin({
            // 填充语法<% 变量 %>
            title: "webpack项目",
            // 要使用的模块所在的路径
            template: "./public/index.html"
       })
    ]
}

DefinePlugin
可以处理模板中的 <link rel="icon" href="<%= BASE_URL %>favicon.ico">,定义 BASE_URL 常量值

const { DefinePlugin } = require('webpack');
module: {
    plugins: [
        new DefinePlugin({
          BASE_URL: '"./"'
        })
    ]
}

CopyWebpackPlugin
将一些文件放到public的目录 安装:npm install copy-webpack-plugin -D

  • from :设置从哪一个源中开始复制
  • to:复制到的位置,可以省略,会默认复制到打包的目录下
  • globOptions:设置一些额外的选项,其中可以编写需要忽略的文件 QQ截图20230830223535.png

6.webpack 对 common.js的支持

面试时看5
源代码:
工具包 ./js/format.js

const dateFormat = (date) => {
  return "2020-12-12";
}
const priceFormat = (price) => {
  return "100.00";
}
module.exports = {
  dateFormat,
  priceFormat
}

打包入口文件common_index.js

const { dateFormat, priceFormat } = require('./js/format');

console.log(dateFormat());
console.log(priceFormat());

打包配置文件 webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: "development",
  entry: "./src/common_index.js",
  devtool: "source-map",
  output: {
    filename: "js/bundle.js",
    path: path.resolve(__dirname, "./build"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "webpack commonjs"
    })
  ]
}

打包之后的文件

// 定义了一个对象
// 模块的路径(key): 函数(value)
var __webpack_modules__ = {
  "./src/js/format.js":
    (function (module) {
      const dateFormat = (date) => {
        return "2020-12-12";
      }
      const priceFormat = (price) => {
        return "100.00";
      }
      // 将我们要导出的变量, 放入到module对象中的exports对象
      module.exports = {
        dateFormat,
        priceFormat
      }
    })
}
// 定义一个对象, 作为加载模块的缓存
var __webpack_module_cache__ = {};

// 是一个函数, 当我们加载一个模块时, 都会通过这个函数来加载
function __webpack_require__(moduleId) {
  // 1.判断缓存中是否已经加载过
  if (__webpack_module_cache__[moduleId]) {
    return __webpack_module_cache__[moduleId].exports;
  }

  // 2.给module变量和__webpack_module_cache__[moduleId]赋值了同一个对象
  var module = __webpack_module_cache__[moduleId] = { exports: {} };

  // 3.加载执行模块
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  // 4.导出module.exports {dateFormat: function, priceForamt: function}
  return module.exports;
}

// 具体开始执行代码逻辑
!function () {
  // 1.加载./src/js/format.js
  const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js");
  console.log(dateFormat());
  console.log(priceFormat());
}();

7.webpack 对 ESModule 的支持

工具包 ./js/format.js

export const sum = (num1, num2) => {
  return num1 + num2;
}

export const mul = (num1, num2) => {
  return num1 * num2;
}

打包入口文件 es_index.js

import { sum, mul } from "./js/math";

console.log(mul(20, 30));
console.log(sum(20, 30));

webpack 配置同上
打包之后的文件

// 1.定义了一个对象, 对象里面放的是我们的模块映射
var __webpack_modules__ = {
  "./src/es_index.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      // 调用r的目的是记录时一个__esModule -> true
      __webpack_require__.r(__webpack_exports__);

      // _js_math__WEBPACK_IMPORTED_MODULE_0__ == exports
      var _js_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/math.js");

      console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.mul(20, 30));
      console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.sum(20, 30));
    }),
  "./src/js/math.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      __webpack_require__.r(__webpack_exports__);

      // 调用了d函数: 给exports设置了一个代理definition
      // exports对象中本身是没有对应的函数
      __webpack_require__.d(__webpack_exports__, {
        "sum": function () { return sum; },
        "mul": function () { return mul; }
      });

      const sum = (num1, num2) => {
        return num1 + num2;
      }
      const mul = (num1, num2) => {
        return num1 * num2;
      }
    })
};

// 2.模块的缓存
var __webpack_module_cache__ = {};

// 3.require函数的实现(加载模块)
function __webpack_require__(moduleId) {
  if (__webpack_module_cache__[moduleId]) {
    return __webpack_module_cache__[moduleId].exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
}

!function () {
  // __webpack_require__这个函数对象添加了一个属性: d -> 值function
  __webpack_require__.d = function (exports, definition) {
    for (var key in definition) {
      if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
      }
    }
  };
}();


!function () {
  // __webpack_require__这个函数对象添加了一个属性: o -> 值function
  __webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();

!function () {
  // __webpack_require__这个函数对象添加了一个属性: r -> 值function
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };
}();


__webpack_require__("./src/es_index.js");

8.webpack CommonJS 和 ESModule 相互导入的支持

打包入口文件

// es module导出内容, CommonJS导入内容
const { sum, mul } = require("./js/math");

// CommonJS导出内容, es module导入内容
import { dateFormat, priceFormat } from "./js/format";

console.log(sum(20, 30));
console.log(mul(20, 30));

console.log(dateFormat("aaa"));
console.log(priceFormat("bbb"));

打包生成的文件

var __webpack_modules__ = ({
  "./src/index.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      "use strict";
      __webpack_require__.r(__webpack_exports__);
      var _js_format__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/format.js");
      var _js_format__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_js_format__WEBPACK_IMPORTED_MODULE_0__);
      
      // es module导出内容, CommonJS导入内容
      const math = __webpack_require__("./src/js/math.js");

      // CommonJS导出内容, es module导入内容
      console.log(math.sum(20, 30));
      console.log(math.mul(20, 30));
      console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().dateFormat("aaa"));
      console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().priceFormat("bbb"));
    }),
  "./src/js/format.js":
    (function (module) {
      const dateFormat = (date) => {
        return "2020-12-12";
      }
      const priceFormat = (price) => {
        return "100.00";
      }
      module.exports = {
        dateFormat,
        priceFormat
      }
    }),

  "./src/js/math.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
      
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        "sum": function () { return sum; },
        "mul": function () { return mul; }
      });
      const sum = (num1, num2) => {
        return num1 + num2;
      }

      const mul = (num1, num2) => {
        return num1 * num2;
      }
    })
});

var __webpack_module_cache__ = {};

// The require function
function __webpack_require__(moduleId) {
  // Check if module is in cache
  if (__webpack_module_cache__[moduleId]) {
    return __webpack_module_cache__[moduleId].exports;
  }
  // Create a new module (and put it into the cache)
  var module = __webpack_module_cache__[moduleId] = {
    // no module.id needed
    // no module.loaded needed
    exports: {}
  };

  // Execute the module function
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  // Return the exports of the module
  return module.exports;
}

!function () {
  // getDefaultExport function for compatibility with non-harmony modules
  __webpack_require__.n = function (module) {
    var getter = module && module.__esModule ?
      function () { return module['default']; } :
      function () { return module; };
    __webpack_require__.d(getter, { a: getter });
    return getter;
  };
}();

/* webpack/runtime/define property getters */
!function () {
  // define getter functions for harmony exports
  __webpack_require__.d = function (exports, definition) {
    for (var key in definition) {
      if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
      }
    }
  };
}();

/* webpack/runtime/hasOwnProperty shorthand */
!function () {
  __webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();

/* webpack/runtime/make namespace object */
!function () {
  // define __esModule on exports
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };
}();

__webpack_require__("./src/index.js");

9.source-map

作用:从已转换的代码,映射到原始的源文件。使浏览器可以重构原始源并在调试器中显示重建的原始源。
使用方式:

  1. 配置 source-map
module.exports = {
  mode: "development",
  devtool: "source-map"
}
  1. 在打包后的代码,最后添加一个注释,它指向 sourcemap //# sourceMappingURL=common.bundle.js.map\
  2. 在Chrome中,可以按照如下的方式打开source-map QQ截图20230902135842.png source-map 文件
{
    "version": 3,
    "sources": [
        "webpack:./src/index.js",
        "webpack:./src/js/format.js",
        "webpack:./src/js/math.js",
        "webpack:/webpack/bootstrap",
        "webpack:k/webpack/runtime/compat get default export",
        "webpack:/webpack/runtime/define property getters",
        "webpack:/webpack/runtime/hasOwnProperty shorthand",
        "webpack:/webpack/runtime/make namespace object",
        "webpack:/webpack/startup"
    ],
    "names": [
        "require",
        "sum",
        "mul",
        "console",
        "log",
        "dateFormat",
        "priceFormat",
        "abc",
        "date",
        "price",
        "module",
        "exports",
        "num1",
        "num2"
    ],
    "mappings": ";;;;;;;;;;;;;AAAA;eACqBA,mBAAO,CAAC,mCAAD,C;IAApBC,G,YAAAA,G;IAAKC,G,YAAAA,G,EAEb;;;AACA;AAEAC,OAAO,CAACC,GAAR,CAAYH,GAAG,CAAC,EAAD,EAAK,EAAL,CAAf;AACAE,OAAO,CAACC,GAAR,CAAYF,GAAG,CAAC,EAAD,EAAK,EAAL,CAAf;AAEAC,OAAO,CAACC,GAAR,CAAYC,sDAAU,CAAC,KAAD,CAAtB;AACAF,OAAO,CAACC,GAAR,CAAYE,uDAAW,CAAC,KAAD,CAAvB;AAEAH,OAAO,CAACC,GAAR,CAAYG,GAAZ,E;;;;;;;;;;ACZA,IAAMF,UAAU,GAAG,SAAbA,UAAa,CAACG,IAAD,EAAU;AAC3B,SAAO,YAAP;AACD,CAFD;;AAIA,IAAMF,WAAW,GAAG,SAAdA,WAAc,CAACG,KAAD,EAAW;AAC7B,SAAO,QAAP;AACD,CAFD,C,CAIA;;;AAEAC,MAAM,CAACC,OAAP,GAAiB;AACfN,YAAU,EAAVA,UADe;AAEfC,aAAW,EAAXA;AAFe,CAAjB,C;;;;;;;;;;;;;;;;ACVO,IAAML,GAAG,GAAG,SAANA,GAAM,CAACW,IAAD,EAAOC,IAAP,EAAgB;AACjC,SAAOD,IAAI,GAAGC,IAAd;AACD,CAFM;AAIA,IAAMX,GAAG,GAAG,SAANA,GAAM,CAACU,IAAD,EAAOC,IAAP,EAAgB;AACjC,SAAOD,IAAI,GAAGC,IAAd;AACD,CAFM,C;;;;;;UCJP;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCrBA;WACA;WACA;WACA,cAAc,0BAA0B,EAAE;WAC1C,cAAc,eAAe;WAC7B,gCAAgC,YAAY;WAC5C;WACA,E;;;;;WCPA;WACA;WACA;WACA;WACA,wCAAwC,yCAAyC;WACjF;WACA;WACA,E;;;;;WCPA,6CAA6C,wDAAwD,E;;;;;WCArG;WACA;WACA;WACA,sDAAsD,kBAAkB;WACxE;WACA,+CAA+C,cAAc;WAC7D,E;;;;UCNA;UACA;UACA;UACA",
    "file": "js/bundle.js",
    "sourceRoot": ""
}

source-map 文件分析

  • version:当前使用的版本,也就是最新的第三版
  • sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件)
  • names:转换前的变量和属性名称(因为我目前使用的是development模式,所以不需要保留转换前的名称
  • mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriablelength quantity可变长度值)编码;
  • file:打包后的文件(浏览器加载的文件);
  • sourceContent:转换前的具体代码信息(和sources是对应的关系);
  • sourceRoot:所有的sources相对的根目录;
    生成 source-map 可选项
  1. 下面几个值不会生成 source-map 文件
  • false:不使用source-map,也就是没有任何和source-map相关的内容。
  • none:production模式下的默认值,不生成source-map。
  • eval:development模式下的默认值,不生成source-map,但是它会在eval执行的代码中,添加 //# sourceURL=; 被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码,如下: QQ截图20230902143603.png 可以大概的看到源文件的报错位置 QQ截图20230902143711.png eval-source-map
    会生成 sourcemap,但是 source-map 是以 DataUrl 添加到 eval 函数的后面。 QQ截图20230902144437.png inline-source-map
    作用:生成 sourcemap,但是 source-map 是以 DataUrl 添加到 bundle 文件的后面。 QQ截图20230902144640.png cheap-source-map
    作用:生成sourcemap,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping) QQ截图20230902145901.png cheap-module-source-map
    生成sourcemap,类似于cheap-source-map,但是对源自loader的sourcemap处理会更好(例如 ts-loader将 ts 文件转换为 js 文件,babel会把箭头函数等转换为js)
    cheap-source-map和cheap-module-source-map的区别?
    QQ截图20230902151514.png hidden-source-map
    生成 sourcemap,但是不会对 source-map 文件进行引用,相当于删除了打包文件中对sourcemap的引用注释
// 被删除掉的 
//# sourceMappingURL=bundle.js.map

手动添加进来,那么 sourcemap 就会生效了。
nosources-source-map
生成sourcemap,但是生成的sourcemap只有错误信息的提示,不会生成源代码文件;
点击错误提示,无法查看源码:
QQ截图20230902152233.png 多个值的组合
webpack 提供给我们的26个值,是可以进行多组合的。
组合的规则如下:

  • inline-|hidden-|eval:三个值时三选一
  • nosources:可选值
  • cheap可选值,并且可以跟随module的值
    [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
    source-map 最佳实践
  • 开发阶段:推荐使用 source-map 或者 cheap-module-source-map ,这分别是vue和react使用的值,可以获取调试信息,方便快速开发。
  • 测试阶段:推荐使用 source-map或者cheap-module-source-map ü 测试阶段我们也希望在浏览器下看到正确的错误提示。
  • 发布阶段:false、缺省值(不写)

10.Babel

作用:Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript 2015+代码转换为向后兼容版本的 JavaScript,包括:语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等。
Babel命令行使用
安装:npm install @babel/cli @babel/core
@babel/core:babel的核心代码,必须安装,@babel/cli:可以让我们在命令行使用babel
执行npx babel src --out-dir dist
例:转换箭头函数
使用 @babel/plugin-transform-arrow-functions 插件
安装:npm install @babel/plugin-transform-arrow-functions -D
使用npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
Babel的预设preset @babel/preset-env 中包含了一些常用的预设 babel 插件,不用单独引入插件了。
安装npm install @babel/preset-env -D
执行npx babel src --out-dir dist --presets=@babel/preset-env
webpack 中配置 babel
安装npm install babel-loader @babel/core
单个使用插件的方式

  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
              plugins: [
                "@babel/plugin-transform-arrow-functions",
                "@babel/plugin-transform-block-scoping"
              ]
           }
        }
      }
    ]
  },

使用 preset-env 预设
安装npm install @babel/preset-env

  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
              presets: [
                  ["@babel/preset-env"]
              ]
           }
        }
      }
    ]
  }

babel 会按照 browserslist 的配置将源代码编译成 目标浏览器 可以识别的语法。也可以在 targets 中进行配置浏览器的范围

  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
              presets: [
                  ["@babel/preset-env",{
                      target:"last 2 version"
                  }]
              ]
           }
        }
      }
    ]
  }

如果 browserslist 和 targets 都设置了,targets属性会覆盖browserslist。

11.polyfill

作用:项目中使用了一些语法特性(例如:Promise, Generator, Symbol等以及实例方法例如 Array.prototype.includes等),打包之后,某些浏览器压根不认识这些特性,必然会报错,使用 polyfill 来填充或者打补丁,那么就会包含该特性了。
使用方式:

  • babel7.4.0之前,可以使用 @babel/polyfill的包,但是该包现在已经不推荐使用了 QQ截图20230902212205.png
  • babel7.4.0之后,可以通过单独引入core-js和regenerator-runtime来完成polyfill的使用:
    安装npm install core-js regenerator-runtime --save
    配置:
module.exports = {
  presets: [
    ["@babel/preset-env", {
      // false: 不用任何的polyfill相关的代码
      // usage: 代码中需要哪些polyfill, 就引用相关的api
      // entry: 手动在入口文件中导入 core-js/regenerator-runtime, 根据目标浏览器引入所有对应的polyfill
      useBuiltIns: "usage",
      corejs: 3
    }],
    ["@babel/preset-react"]
  ]
}
  • useBuiltIns:设置以什么样的方式来使用 polyfill; useBuiltIns属性有三个常见的值
  1. false 打包后的文件不使用polyfill来进行适配,不需要设置corejs属性的。
  2. usage 根据源代码中出现的语言特性,自动检测所需要的polyfill,确保最终包里的polyfill数量的最小化,打包的包相对会小一些
  3. entry 如果我们依赖的某一个库本身使用了某些polyfill的特性,但是因为我们使用的是usage,所以之后用户浏览器 可能会报错,可以在入口文件中添加
import 'core-js/stable';
import 'regenerator-runtime/runtime';

这样做会根据 browserslist 目标导入所有的polyfill,但是对应的包也会变大。

  • corejs:设置 corejs 的版本,目前使用较多的是3.x的版本,比如我使用的是3.8.x的版本,另外 corejs 可以设置是否对提议阶段的特性进行支持,设置 proposals 属性为 true 即可。

12.webpack 对 TypeScript的编译

  1. 使用ts-loader
    ts-loader 的原理是用了 tsc 编译 ts 文件
    安装:npm install ts-loader -D
    配置:
{
    test: /\.ts$/,
    exclude: /node_modules/,
    use:[
        "ts-loader"
    ]
}
  1. 使用babel-loader
    原理:babel 可以主动编译 ts 文件,不依赖 tsc
    Babel是有对TypeScript进行支持,可以使用 ts @babel/preset-typescript
    安装npm install @babel/preset-typescript -D
    QQ截图20230903114945.png ts-loader和babel-loader选择
  • 使用ts-loader(TypeScript Compiler),来直接编译TypeScript,那么只能将ts转换成js,如果我们还希望在这个过程中添加对应的polyfill,那么ts-loader是无能为力的,需要借助于babel来完成 polyfill 的填充功能。
  • 使用babel-loader(Babel) 直接编译TypeScript,也可以将ts转换成js,并且可以实现 polyfill 的功能,但是babel-loader在编译的过程中,不会对类型错误进行检测。 编译TypeScript最佳实践 使用Babel来完成代码的转换,使用tsc来进行类型的检查。
    package.json\
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "type-check": "tsc --noEmit",
    "type-check-watch": "tsc --noEmit --watch"
  }

npm run type-check可以对ts代码的类型进行检测,npm run type-check-watch可以实时的检测类型错误。

13.ESLint

ESLint是一个静态代码分析工具(Static program analysis,在没有任何程序执行的情况下,对代码进行分析) 项目中使用 ESLint 建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性。
安装npm install eslint -D
创建ESLint的配置文件npx eslint --init
选择想要使用的ESLint: QQ截图20230912221450.png 执行检测命令npx eslint ./src/main.js
ESLint的文件解析

module.exports = {
  // env:运行的环境,比如是浏览器,并且我们会使用es2021(对应的ecmaVersion是12)的语法
  env: {
    browser: true,
    commonjs: true,
    es2021: true,
  },
  // extends:可以扩展当前的配置,让其继承自其他的配置信息,可以跟字符串或者数组(多个)
  extends: ['plugin:vue/essential', 'airbnb-base'],
  // 指定ESMAScript的版本、sourceType的类型
  parserOptions: {
    ecmaVersion: 12,
    // 编译TypeScript解释器
    parser: '@typescript-eslint/parser',
  },
  // 用到的插件
  plugins: ['vue', '@typescript-eslint'],
  // 自定义规则
  rules: {
    // 0 => off
    // 1 => warn
    // 2 => error
    'no-unused-vars': 0,
    quotes: ['warn', 'single'],
    'no-console': 0,
    'import/no-extraneous-dependencies': 0,
  },
};

VSCode的ESLint插件
如果每次校验时,都需要执行一次npm run eslint就有点麻烦了,所以我们可以使用一个VSCode的插件: ESLint QQ截图20230912222308.png ESLint-Loader
编译代码的时候,也希望进行代码的eslint检测,就可以使用 eslint-loader 来完成

  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: ['babel-loader', 'eslint-loader'],
      },
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        // 本质上是依赖于typescript(typescript compiler)
        use: 'babel-loader',
      }
    ]
  }

14.本地服务器 devServer

自动编译有以下三种方式
1. Webpack watch
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译
开启 watch
在 package.json 的 scripts 中添加一个 watch 的脚本 QQ截图20230912223306.png
2. webpack-dev-server
安装:npm install --save-dev webpack-dev-server
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中。

  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch",
    "serve": "webpack serve"
  },

15.模块热替换(HMR)

模块热替换:应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面。
HMR通过如下几种方式,来提高开发的速度

  1. 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
  2. 只更新需要变化的内容,节省开发的时间
  3. 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式

开启HMR
修改webpack的配置:

  devServer: {
    hot: true
  }

浏览器可以看到如下效果: QQ截图20230912234501.png 修改了某一个模块的代码时,依然是刷新的整个页面: 需要去指定哪些模块发生更新时,进行HMR

if (module.hot) {
  module.hot.accept("./math.js", () => {
    console.log("math模块发生了更新~");
  });
}

框架的HMR
1. React的HMR
在之前,React是借助于React Hot Loader来实现的HMR,目前已经改成使用react-refresh来实现了
安装:npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
修改webpack.config.js和babel.config.js文件 QQ截图20230912235753.png 2.Vue的HMR
vue-loader 加载的组件默认进行 HMR 的处理
安装:npm install vue-loader vue-template-compiler -D
配置webpack.config.js:
QQ截图20230912235950.png 3.HMR的原理
webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket),express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析),HMR Socket Server,是一个socket的长连接,当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk),通过长连接,可以直接将这两个文件主动发送给客户端(浏览器),浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新。 QQ截图20230913000211.png

16.webpack的路径处理

output 中的 path:静态资源js、css等输出目录,常见设置为dist、build文件夹等。\

  output: {
    filename: "bundle.js",
    // 打包后的文件的输出目录
    path: path.resolve(__dirname, "./build")
  }

output 中的 publicPath:指定index.html文件打包引用的一个基本路径,默认值是一个空字符串,所以我们打包后引入js文件时,路径是 bundle.js。vue项目如果希望本地直接打开html文件来运行,会将其设置为 ./,路径时 ./bundle.js,可以根据相对路径去 查找资源。

  output: {
    filename: "bundle.js",
    // 打包后的文件的输出目录
    path: path.resolve(__dirname, "./build"),
    publicPath: "./"
  }

devServer 的 publicPath
devServer 中的 publicPath 属性,指定本地服务所在的文件夹:
默认值是 /,直接访问端口即可访问其中的资源 http://localhost:8080 如果设置为了 /abc,需要通过 http://localhost:8080/abc 才能访问到对应的打包后的资源。建议 devServer.publicPath 与 output.publicPath相同。\

17.webpack的环境分离