学习笔记
mode in webpack config
使用:
1 2 3 module .exports = { mode : "development" , };
简单看下 webpack 官网对mode的描述:
option
description
development
Sets process.env.NODE_ENV on DefinePlugin to value development. Enables useful names for modules and chunks.
production
Sets process.env.NODE_ENV on DefinePlugin to value production. Enables deterministic mangled names for modules and chunks, FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin and TerserPlugin.
none
Opts out of any default optimization options
可以看到mode可以设置为development, production, none, 并且development和production 存在 webpack 预设的一些默认配置. 后面例子为特殊说明mode都为development
webpack 模块化原理
之前提到浏览器目前只能解析 ESM 模块(某些浏览器甚至无法解析模块), 需要添加<script type="module">, 但是通过 webpack 打包的代码, 允许 使用各种各样的模块化, AMD, CMD, CommonJS, ESModule 等, 它是如何帮助我们实现代码中支持模块化的呢? 这里以最常用的 CommonJS 和 ESModule 举例, 依次介绍 webpack 中:
CommonJS 模块化实现原理
ES Module 模块化实现原理
CommonJS 加载 ES Module 原理
ES Module 加载 CommonJS 原理
每种情况都会以简单案例讲解, 开始之前webpack.config.js先配置devtool: "source-map", 因为development模式下打包默认使用eval, 不方便阅读打包产物代码, 具体含义后续介绍.
IIFE 立即执行函数
产物中会出现大量立即执行函数, 存在多种写法, 可以先阅读JavaScript 中的立即执行函数
CommonJS 模块化实现原理
入口文件index.js:
1 2 3 4 const { dateFormat, prizeFormat } = require ("./util/format" );console .log (dateFormat (new Date ()));console .log (prizeFormat (100 ));
引入模块文件:
1 2 3 4 5 6 7 8 9 10 11 12 function dateFormat (date ) { return "2022-09-14" ; }function prizeFormat (prize ) { return "100.00" ; }module .exports = { dateFormat, prizeFormat, };
打包后的文件bundle.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 (() => { var __webpack_modules__ = { "./util/format.js" : (module ) => { function dateFormat (date ) { return "2022-09-14" ; } function prizeFormat (prize ) { return "100.00" ; } module .exports = { dateFormat, prizeFormat, }; }, }; var __webpack_module_cache__ = {}; function __webpack_require__ (moduleId ) { var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule !== undefined ) { return cachedModule.exports ; } var module = (__webpack_module_cache__[moduleId] = { exports : {}, }); __webpack_modules__[moduleId](module , module .exports , __webpack_require__); return module .exports ; } var __webpack_exports__ = {}; (() => { const { dateFormat, prizeFormat } = __webpack_require__ ("./util/format.js" ); console .log (dateFormat (new Date ())); console .log (prizeFormat (100 )); })(); })();
打包后的所有逻辑都有对应的注释, 可以看到 webpack 通过__webpack_modules__对象存储模块路径和内容, __webpack_module_cache__对象缓存所有module.exports, __webpack_require__函数简单实现了 CommonJS 中require
ESM 模块化实现原理
入口文件esm_index.js:
1 2 3 4 import math from "./util/math" ;console .log (math.sum (10 , 20 ));console .log (math.mul (10 , 20 ));
引入模块文件:
1 2 3 4 5 6 7 8 9 10 11 12 function sum (a, b ) { return a + b; }function mul (a, b ) { return a * b; }export default { sum, mul, };
打包产物:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 (() => { "use strict" ; var __webpack_modules__ = { "./util/math.js" : ( __unused_webpack_module, __webpack_exports__, __webpack_require__ ) => { __webpack_require__.r (__webpack_exports__); __webpack_require__.d (__webpack_exports__, { default : () => __WEBPACK_DEFAULT_EXPORT__, }); function sum (a, b ) { return a + b; } function mul (a, b ) { return a * b; } const __WEBPACK_DEFAULT_EXPORT__ = { sum, mul, }; }, }; var __webpack_module_cache__ = {}; function __webpack_require__ (moduleId ) { var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule !== undefined ) { return cachedModule.exports ; } var module = (__webpack_module_cache__[moduleId] = { exports : {}, }); __webpack_modules__[moduleId](module , module .exports , __webpack_require__); return module .exports ; } (() => { __webpack_require__.d = (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_require__.o = (obj, prop ) => Object .prototype .hasOwnProperty .call (obj, prop); })(); (() => { __webpack_require__.r = (exports ) => { if (typeof Symbol !== "undefined" && Symbol .toStringTag ) { Object .defineProperty (exports , Symbol .toStringTag , { value : "Module" }); } Object .defineProperty (exports , "__esModule" , { value : true }); }; })(); var __webpack_exports__ = {}; (() => { __webpack_require__.r (__webpack_exports__); var _util_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__ ("./util/math.js" ); console .log (_util_math__WEBPACK_IMPORTED_MODULE_0__["default" ].sum (10 , 20 )); console .log (_util_math__WEBPACK_IMPORTED_MODULE_0__["default" ].mul (10 , 20 )); })(); })();
可以看到其实和 commonjs 最大的区别是
增加了 esmodule 属性标识
通过Object.defineProperty代理 module.exports 属性代替直接赋值
多了 default
混用 CommonJs 和 ESModule
同时在项目中使用 CommonJS 和 ESModule 打包结果和上述大同小异, 可以直接示例代码中 esModule 文件夹下的打包产物, 这里不过多赘述.
配置 devtool 能够帮助我们在程序报错更好地定位问题, webpack 中提供了 26 种 devtool 的值, 下面详细介绍.
mode 为development, devtool 默认值为eval;
mode 为production, devtool 默认值为(nond), 这里的 none 不是字符串, 表示没有该项配置;
source-map
devtool 设置为source-map后, 打包产物会多出来一个bundle.js.map文件
并且在bundle.js最后会添加 source-map 链接
以下是 mode 为production, devtool 设置source-map生成的bundle.js.map内容:
1 2 3 4 5 6 7 8 9 10 11 { "version" : 3 , "file" : "bundle.js" , "mappings" : "AASgB,IAAIA,SAAQ,CAACC,EAASC,KAAV,IAH1BC,QAAQC,IAFM,eAShBD,QAAQC,IAAIC" , "sources" : [ "webpack:///./src/index.js" ] , "sourcesContent" : [ ] , "names" : [ "Promise" , "resolve" , "reject" , "console" , "log" , "abc" ] , "sourceRoot" : "" }
每个字段含义如下:
version:Source map 的版本,目前为 3。
file:转换后的文件名。
sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。有时也会包含 webpack 库中的文件
names:转换前的所有变量名和属性名。mode 为development时代码并没有进行混淆, 变量名和属性名都不变, names 为空数组
mappings:记录位置信息的字符串,记录原文件到打包文件的所有映射, 详情可以查阅阮一峰博客
默认浏览器会开启 source-map, 配置如图:
在浏览器中可以看到 source-map 映射的源文件
eval
JavaScript 原生支持的 eval 函数可以在结尾添加类似 source-map 的 sourceURL 用于打包前的源文件, 从而实现映射, 这也是mode: "development"的默认 devtool 配置, 但是这样的代码不方便阅读
inline-source-map
不会再 source-map 文件, 而是以 base64 编码 inline 在打包产物 js 中
eval-source-map
顾名思义, 会生成 eval 链接和 sourcep-map 链接
1 //# sourceMappingURL=data:application/json;charset=utf-8;base64 ,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvaW5kZXguanMuanMiLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLG1EQUFtRDs7QUFFbkQ7O0FBRUEiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaW5kZXguanM/YjYzNSJdLCJzb3VyY2VzQ29udGVudCI6WyIvKiogbmVjZXNzYXJ5IHdoaWxlIHVzZUJ1aWx0SW5zOiAnZW50cnknICovXG4vLyBpbXBvcnQgJ2NvcmUtanMvc3RhYmxlJztcbi8vIGltcG9ydCAncmVnZW5lcmF0b3ItcnVudGltZS9ydW50aW1lJztcblxuY29uc3QgbWVzc2FnZSA9ICdIZWxsbyB3b3JsZCc7XG5jb25zdCBmb28gPSAobmFtZSkgPT4ge1xuICBjb25zb2xlLmxvZyhuYW1lKTtcbn07XG5cbmNvbnN0IHByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7fSk7XG5cbmZvbyhtZXNzYWdlKTtcblxuY29uc29sZS5sb2coYWJjKTsiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//
cheap-source-map
source-map 对于报错信息会详细到某行某列, cheap-source-map 只会精细到行**(网上是这么说的, 虽然我验证的时候设置 source-map 或 cheap-source-map 好像没什么区别)**
cheap-module-source-map
如果源码经过 loader 例如 babel-loader 处理后, 使用 cheap-source-map 会链接到 loader 处理过的文件, 和源码还是有点区别的, 使用 cheap-module-source-map 就会正常指向源文件了.
这也是 react 官方脚手架本地环境下默认的 devtool 配置.
cheap-source-map:
cheap-module-source-map:
hidden-source-map
会生成 source-map 文件, 但不会在 javascript 文件中链接, 一般用于前端错误信息上报, 后端通过错误中的行列信息还原出源文件的报错位置.
nosources-source-map
使用 nosources 关键字生成的 source-map 文件中不包含 sourcesContent 内容, 因此调试时只能看到源文件的行列错误信息, 无法看到源码
上面介绍了比较典型的几个 devtool 配置, 理解了每个关键字的含义也就知道如何配置了. 所有的 devtool 配置都是以下几个关键字的排列组合
[inline-|hidden-|eval][nosources-][cheap-[module-]][source-map]
下面给出不同环境下的最佳配置:
开发/测试环境: source-map or cheap-module-source-map
线上环境: false or 根据上报需求使用 hidden, nosources
示例代码
https://github.com/Mariana-Yui/fe-learn-code/tree/main/learn-webpack/day5
reference
JavaScript 中的立即执行函数
一文搞懂 SourceMap 以及 webpack devtool