第一章 Webpack简介
何为Webpack?
Webpack是一个开源的JS模块化打包工具,其核心的功能是解决模块之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并为一个js文件。
为什么需要Webpack?
- 模块化有利于开发和迭代,但无法直接实际使用
- 大多数
npm模块是CommonJS的形式,浏览器不支持 - 个别浏览器/平台的兼容性问题
- 无法使用
code splitting(按需加载) 和tree shaking(去除无用代码)来提升代码优化速度
为什么 js 需要模块化机制(而不是通过 script 标签来引入我们的分块开发的 js 文件)
需要手动维护
js的加载顺序。(多文件相互依赖的时候往往很难确认依赖的顺序和加载的顺序)每一个
script标签都意味着需要向服务器多发出一次静态资源请求 (性能问题)全局作用域污染(每个
script中,顶层作用域即全局作用域)
Webpack优势
-
支持多种模块标准,可处理好使用多种模块标准的工程中的各种依赖关系
-
完备的代码分割(
code splitting)解决方案 -
可以通过各种
loader处理各种类型的资源 -
庞大的社区支持
-
初次使用
npx webpack --entry=./index.js --output-filename=bundle.js --mode=development // 根据entry进行项目依赖查找// output-filename是输出资源名// 三种打包模式:development、production、none;
-
npx-
npx想要解决的主要问题,就是调用项目内部安装的模块。$ node-modules/.bin/mocha --version$ npx mocha --version -
npx的原理很简单,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。由于npx会检查环境变量$PATH,所以系统命令也可以调用。# 等同于 ls$ npx ls
-
-
webpack配置-
默认源码入口
src/index.js; -
参数太多,后期难以维护,可以把这些参数改为对象的形式专门放在一个配置文件里,在
webpack每次打包的时候读取该配置文件即可; -
webpack默认配置文件为webpack.config.jsmodule.exports = { entry: './src/index.js', output: { filename: 'bundle.js', }, mode: 'development',}
-
-
webpack-dev-server- 令
webpack进行模块打包,并处理打包结果的资源请求; - 作为普通的
web servere,处理静态资源文件请求; live-reloading(自动刷新);
- 令
注意:直接使用
webpack开发和使用webpack-dev-server有一个很大的区别,前者每次都会生成bundle.js,而webpack-dev-server只是将打包结果放在内存中,并不会写入实际的bundle.js,在每次webpack-dev-server接收到请求时都只是将内存中的打包结果返回给浏览器。
Webpack 基础配置重点
Webpack 的默认配置文件为
webpack.config.js
output.path需要用绝对地址。
webpack-dev-server: 需要关注 HMR 的底层实现。
dev-server工作:浏览器发出资源请求 → url 校验 → 资源服务地址(配置的publickPath):输出打包结果资源。反之,读取硬盘中的源文件返回。
dev-server和直接打包的区别:dev-server的打包结果写在内存中,不会写入实际的bundle.js,每次接收到请求后返回的是内存中的打包结果。
dev-server工作内容:
- 模块打包。
web server分发资源。
第二章 模块打包
CommonJs (NodeJs [require, module.export])
CommonJs规定每一个文件是一个模块(模块会形成自已的作用域,这样就不会造成变量污染了。内部会有一个
module对象用于存放当前模块的信息。
require一个模块时会有两种情况:
- 第一次被加载的时候会首先执行该模块,然后导出内容。
- 如果已经被加载过,不会再次执行,而是直接导出上次执行后得到的结果。
- 内部拥有一个
loaded去记录是否被加载过注意:在使用
exports时不要直接给exports赋值,会导致exports指向了新对象,module.exports仍旧是空对象;并且不要把module.exports与exports混用,可能会发生属性丢失的情况。
ES6 module (export default / import)
import export属于关键字,而module不属于关键字。
ES6 module会自动采用严格模式。
export方式:
- 命名导出
export const name = 'xxx' / const name = 'xxx', export { name }(可以使用 as 作重命名)- 默认导出
export default xxx(输出入了一个名为 default 的变量)import 方式:
- 使用命名导出的变量:
import {name} from 'xxx'(可以使用 as 作重命名),导入的都是只读属性,不能修改。导入多个变量可以用import * as xxx from 'xxx'代替- 对于默认导出的变量,
import xxx from 'xxx'(xxx 可以自由指定)- 组合写法
import React, { Component } from 'React'复合写法:导入后立即导出(只适用于命名导出)
export name from 'xxx'
CommonJS、ES6 Module区别
- 模块加载
CommonJS:“动态”=>模块依赖关系的建立发生在代码运行阶段,模块被执行前,并没有办法确定明确的依赖关系ES6 Module:“静态”=>模块依赖关系的建立发生在代码编译阶段,导入路径不支持表达式,导入导出必须在顶层作用域
- 死代码检测和排除,通过静态分析可以在打包时去掉这些未曾使用过的模块,以减小打包资源体积;
- 模块变量类型检查,
import时就能判断出是否正确;- 编译器优化。
CommonJS等动态模块系统中,本质上导入的是一个对象,ESM支持直接导入变量,减少了引用层级。- 导出值
CommonJS:值拷贝,允许对导入值进行修改,对其余导入位置无影响- ES6
Module:原有值的动态映射,并且只读。对内部变量进行修改其余导入位置也随之变化- 循环依赖
CommonJS:无法解决,按照顺序执行,产生循环依赖时直接取出默认module.exports,也就是{}ES6 Module:利用导出值是动态映射的特性,保证导入的值被使用时已经设置好正确的导出值,即可支持循环依赖
|
**CommonJS**
|
**ES6 Module**
| |
|---|---|---|
| 模块加载 |
动态:
|
静态
优点:
|
| 导出值 | 值拷贝:修改导入值对其他位置无影响 | 动态映射:修改导入值其他位置也随之变化 |
| 循环依赖 | 无法解决 | 本身也不支持,利用循环特性可支持循环依赖 |
加载其他类型模块(使用场景已经不多)
- 非模块化文件:需特别注意隐式声明的全局变量,
Webpack在打包时会为每一个文件包装一层函数作用域来避免全局污染,无法挂在全局AMD:异步模块定义,通过声明回调函数异步加载模块,将其他模块以依赖注入的方式加入进当前模块
define('name', [...dependencies], callback)require([...modules], function(...modules) {})- 优势:模块加载是非阻塞性的,当执行到
require函数时并不会停下去执行被记载的模块,而是继续执行require后面的代码,这使得模块加载操作并不会阻塞浏览器- 缺点:导入导出代码冗长,容易造成回调地域
UMD统一多模块标准管理方案的解决
- 目的:让任何标准的模块可以运行在各种环境(
AMD或CommonJs或非模块环境)下- 实现原理:
- 先判断是否为
AMD环境(是否有define函数) ,按AMD导出- 再判断是否为
CommonJs环境,以CommonJs形式导出npm模块
模块打包原理
webpack打包原理:模块缓存—实现require(__webpack_require__函数)—实现入口模块的加载(以key-value的形式存储所有被打包的模块)
最外层立即执行匿名函数—包裹整个
bundle,并构成自身的作用域;
installModules对象—每个模块只在第一次被加载的时候执行,之后其导出值就被存储到这个对象里面,当再次被加载的时候直接从里面取值,而不会重新执行;
__webpack_require__函数:对模块加载的实现,在浏览器中可以通过该函数来完成模块导入;
Module对象:以key-value的形式存放工程中所有产生依赖的模块
key:表示模块id—数字/hash字符串;
value:匿名函数包裹的模块实体,匿名函数的参数赋予了每个模块的导入导出的能力;
/******/ (function(modules) { // webpackBootstrap/******/ // The module cache/******/ var installedModules = {};/******//******/ // The require function/******/ function __webpack_require__(moduleId) {/******//******/ // Check if module is in cache/******/ if(installedModules[moduleId]) {/******/ return installedModules[moduleId].exports;/******/ }/******/ // Create a new module (and put it into the cache)/******/ var module = installedModules[moduleId] = {/******/ i: moduleId,/******/ l: false,/******/ exports: {}/******/ };/******//******/ // Execute the module function/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);/******//******/ // Flag the module as loaded/******/ module.l = true;/******//******/ // Return the exports of the module/******/ return module.exports;/******/ }/******//******/ //....省略一部分.../******/ // Load entry module and return exports/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js");/******/ })/************************************************************************//******/ ({/***/ "./node_modules/moment/locale sync recursive ^\\.\\/.*$":/*!**************************************************!*\!*** ./node_modules/moment/locale sync ^\.\/.*$ ***!\**************************************************//*! no static exports found *//***/ (function(module, exports, __webpack_require__) {//....省略一部分.../***/ "./src/add-content.js":/*!****************************!*\!*** ./src/add-content.js ***!\****************************//*! exports provided: default *//***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(moment__WEBPACK_IMPORTED_MODULE_0__);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (function () {\n document.write('Hello cs! dev-server hahahha<br />')\n document.write(moment__WEBPACK_IMPORTED_MODULE_0___default()(new Date()).format('YYYYMMDD'))\n});\n\n\n//# sourceURL=webpack:///./src/add-content.js?");/***/ }),/***/ "./src/index.js":/*!**********************!*\!*** ./src/index.js ***!\**********************//*! no exports provided *//***/ (function(module, __webpack_exports__, __webpack_require__) {"use strict";eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _add_content__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./add-content */ \"./src/add-content.js\");\n\n\ndocument.write('My first Webpack app.<br />');\nObject(_add_content__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n\n\n//# sourceURL=webpack:///./src/index.js?");/***/ })/******/ });
bundle在浏览器中执行步骤:代码执行顺序和模块加载的顺序完全一致;
最外层的匿名函数中会初始化浏览器的执行环境;
加载入口模块—每个
bundle有且只有一个入口模块;执行模块代码
执行到
module.exports则记录下模块的导出值;若遇到
require()则会暂时交出执行权,进入__webpack_require__函数体内进行加载其他模块的逻辑;在
__webpack_require__中会判断即将加载的模块是否存在于installedModules中
若存在,直接取值;
若不存在,执行该模块代码获取导出值;
所有依赖模块都执行完毕,最后执行权又回到入口模块,当入口模块的代码执行到结尾,也意味着整个
bundle运行结束;
要点: 3.4 步为一个递归过程,Webpack在本质上不会修改代码的执行逻辑,代码的加载顺序和模块的加载顺序完全一致(同步)。
第三章 资源输入输出
资源处理流程
Webpack 从入口文件开始检索,将有依赖关系的模块生成一颗依赖树,最终得到一个个 chunk。(chunk:代码块,呗抽象和包装过后的模块。根据配置不同,一个工程可能会产生一个或多个 chunk,chunk 得到的产物就是 bundle。)工程科定义多个入口,每个入口产生一个结果资源,也就是说 entry 与 bundle 有一定的对应关系。
特殊情况下,一个入口也可能产生多个
chunk并最终生成多个bundle
配置资源入口
Webpack 通过 context 和 entry 这两个配置项来共同决定入口文件的路径。
- 确定入口模块位置;
- 定义
chunk name。一个入口,默认其chunk name为“main”;如有多个入口,需定义每个入口的chunk name,来作为该chunk的唯一标识。
- context:资源入口路径前缀,必须是绝对路径的字符串,可省略,默认值为工程根目录。
- entry:多种形式配置
- 字符串:无法更
改chunk name
entry: './src/index.js'
- 数组:将多个资源预先合并,最后一个元素为实际入口,相当于在入口文件中
import资源。无法更改chunk name
entry: ['babel-polyfill', './src/index.js']
- 对象:定义多入口,
key => chunk name,value =>入口路径。对象的属性值也可以为字符串或数组。
entry: { index: ['babel-polyfill', './src/index.js'], chunk1: './src/chunk1.js',}
- 函数:返回值为上面任意一种形式。可动态获取入口,支持异步操作
entry: () => './src/index.js'
实例
-
SPA。一般定义单一入口,各个模块都有此入口引用,关系清晰但会导致体积过大,降低渲染速度。Webpack在bundle大于250KB时会发出警告 -
提取
vendor,添加一个新chunk,放置第三方模块,不会经常更改,加快后续页面渲染速度。entry: { index: './src/index.js', vendor: ['react', 'redux'],} -
多页应用,每个页面独立
bundle,加载各自必要的逻辑,同时也可以提取vendor。entry: { a: './src/a.js', b: './src/b.js', vendor: ['react', 'redux'],}
配置资源出口
-
filename:字符串,输出文件名或相对路径,目录不存在会创建该目录。
多入口场景下,Webpack支持为需要为每个bundle指定不同名字。下例中name会被替换成chunk nameentry: { a: './src/a.js', b: './src/b.js', vendor: ['react', 'redux'],}output: { filename: '[name]@[chunkhash].js'}模板变量:当有多个
chunk存在时对不同的chunk进行区分;控制客户端缓存。模板变量 功能 其他 `[hash]` `Webpack` 此次打包所有资源生成的 `hash` 表中的 [`hash`] 和 [`chunkhash`] 都与 `chunk` 内容直接相关,在 `filename` 中使用了这些变量后,当 `chunk` 的内容改变时,可以同时引起资源文件名的更改,从而使用户在下一次请求资源文件时会立即下载新的版本而不会使用本地缓存。[`query`]也可以起到类似的效果,只不过它与 `chunk` 内容无关,要由开发者手动指定。如果要控制客户端缓存,最好还要加上 [`chunkhash`],因为每个 `chunk` 所产生的[`chunkhash`]只与自身内容有关,单个 `chunk` 内容的改变不会影响其他资源,可以最精确地让客户端缓存得到更新。 `[chunkhash]` 当前 `chunk` 内容的 `hash` `[id]` 当前 `chunk` 的 `id` `[query]` `filename` 配置项中的 `query`
更新缓存一般只用在生产环境的配置下,在开发环境中可以不必配置[
chunkhash],后续介绍
-
path:指定资源输出的位置,要求值必须为绝对路径。 -
publicPath:指定资源请求的位置。
输出位置:打包完成后资源产生的目录,一般将其指定为工程中的dist目录。
请求位置:由JS或CSS所请求的间接资源路径。-
HTML相关:可以将publicPath指定为HTML的相对路径,在请求这些资源时会以当前页面 HTML 所在路径加上相对路径,构成实际请求的URL。 -
Host相关:若publicPath的值以“/”开始,则代表此时publicPath是以当前页面的host name为基础路径的。 -
CDN相关:绝对路径,当publicPath以协议头或相对协议的形式开始时,代表当前路径是CDN相关// HTML地址:http://localhost:8080/app/index.html// 异步加载资源名为:a.chunk.jspublicPath: "", // http://localhost:8080/app/a.chunk.jspublicPath: "./js", // http://localhost:8080/app/js/a.chunk.jspublicPath: "/", // http://localhost:8080/a.chunk.jspublicPath: "/js", // http://localhost:8080/js/a.chunk.jspublicPath: "https://cdn.com/", // https://cdn.com/a.chunk.jspublicPath: "//cdn.com/assets", // //cdn.com/assets/a.chunk.js
-
注:
webpack-dev-server中publicPath是指定静态资源服务路径,可以将webpack-dev-server的publicPath与Webpack中的output.path保持一致,这样在任何环境下资源输出的目录都是相同的。
第四章 预处理器(loader)
一切皆模块
loader - JS 外的其他类型资源都需要 loader 进行转义
每个loader本质上都是一个函数。对接收到的内容进行转换,返回转换后的结果。output = loader(input)
input 和 output 可以是字符串或上一个 loader 转化后的结果,包括字符串、source map、AST 对象。
// babel-loader 转化ES6+ ES5 = babel-loader(ES6+)// 链式 output = loaderA(loaderB(loaderC(input)))// 编译 SCSS Style标签 = style-loader(css-loader(sass-loader(SCSS)))
-
loader引入:install=>module.rules中配置- test:正则表达式 / 正则表达式数组,匹配模块
- use:该规则使用
loader的数组 / 字符串(只有一个loader时使用) - 链式
loader:Webpack打包时按照数组从后往前的顺序将资源交给loader处理,要把最后生效的放在前面。 loader options
-
exclude:所有被正则匹配到的模块都排除在该规则之外;include:规则只对正则匹配到的模块生效;
exclude和include同时存在时,exclude的优先级更高,可以对include中的子目录进行排除 -
resource、issuer
在Webpack中,我们认为被加载模块是resource,而加载者是issuer。 -
enforce:指定一个loader的种类,只接收“pre”或“post”两种字符串类型的值。
loader执行顺序分为四种:pre、inline(官方已不推荐)、normal、post
pre:在所有正常loader之前执行,保证代码未被其他loader更改过
post:与pre相反,在所有loader之后执行
常用 loader
**babel-loader**: 处理ES6+并将其编译为ES5;exclude中要添加node_modulescacheDirectory缓存机制,防止未改变模块重复编译,可以是指定的路径,为true时缓存目录指向node_modules/.cache/babel-loader- 将
@babel/preset-env的modules配置项设置为false会禁用模块语句的转化,防止转化成CommonJS,tree-shaking特性失效 babel-loader支持从.babelrc文件读取Babel配置,因此可以将presets 和 plugins 从Webpack配置文件中提取出来,也能达到相同的效果
**ts-loader**:Typescript本身的配置并不在ts-loader中,而是必须要放在工程目录下的tsconfig.json中;**html-loader**:将HTML文件转化为字符串并进行格式化;**handlebars-loader**:处理handlebars模板,得到一个函数,可以接收一个变量,最终返回字符串。在安装时要额外安装handlebars;file-loader:打包文件类型的资源,并返回其publicPath;- 不指定
output.publicPath,返回文件hash值,指定后返回output.publicPath + hashfile-loader也支持配置文件名以及publicPath,会覆盖output.publicPath,通过loader的options传入
- 不指定
url-loader:用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader一样返回publicPath,而小于该阈值时则返回文件base64形式编码。
**vue-loader**:处理 vue 组件,可以将组件的模板、JS及样式进行拆分
module.exports = { module: { rules: [{ test: /\.css$/, // use: ['style-loader', { // loader: 'css-loader', // options: { // modules: true, // camelCase: true // } // }], use: ExtractTextPlugin.extract({ fallback: 'style-loader', // 用于指定当插件无法提取样式时所采用的loader use: 'css-loader', // 指定在提取样式之前采用哪些loader来预先进行处理 }), exclude: /node_modules/, // 或 include: /src/ issuer: { test: /\.js$/, include: /src/, // 当前目录的js文件引用css,才会生效 } },{ test: /\.js|jsx$/, include: /src/, use: [{ loader: 'babel-loader', options: { cacheDirectory: path.resolve('.cache') } }], },{ test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, use: { loader: 'url-loader', options: { limit: 8192, name: 'images/[hash:8].[ext]', } }, }] }, plugins: [ new ExtractTextPlugin("[name].css"), new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[id].css' }) ]}
自定义 loader
第五章 样式处理
plugins
接收一个插件数组,可以在打包的各个环节中添加一些额外任务
分离样式文件
extract-text-webpack-plugin,多样式文件使用[name].css来动态生成CSS为文件名mini-css-extract-plugin,支持按需加载,设置publicPath,不需设置fallback
样式预处理
Sass、SCSS(sass-loader)、Less(less-loader),配置sourceMap可在浏览器中查看源码
PostCss
接收样式源代码并交由编译插件处理,最后输出CSS
CSS Modules
把CSS模块化
- 每个
CSS文件中的样式都拥有单独的作用域,不会和外界发生命名冲突。 - 对
CSS进行依赖管理,可以通过相对路径引入CSS文件。 - 可以通过
composes轻松复用其他CSS模块。
使用CSS Modules不需要额外安装模块,只要开启css-loader中的modules配置项即可。localIdentName配置项,它用于指明CSS代码中的类名会如何来编译。
module.exports = { module: { rules: [{ test: /\.css$/, use: ['style-loader', { loader: 'css-loader', options: { modules: true, localIdentName: '[name]__[local]__[hash:base64:5]' } }], }] }}
[name]指代的是模块名,这里被替换为style。[local]指代的是原本的选择器标识符,这里被替换为title。[hash:base64:5]指代的是一个5位的hash值,这个hash值是根据模块名和标识符计算的,因此不同模块中相同的标识符也不会造成样式冲突。
第六章 代码分片
目的是按需加载,减小首屏资源体积,利用缓存,加快首屏加载速度
通过入口划分
每个入口都生成一个对应的文件,适用多页应用,主要适合于那些将接口绑定在全局对象上的库,业务代码中的模块无法直接引用库中的模块。而且需要手工的方式去配置和提取公共模块,比较麻烦复杂
CommonsChunkPlugin
- 提取多入口
chunk之间的公共模块,单页应用第三方类库及业务中不常更新的模块-
配置项:
-
name:用于指定公共chunk的名字; -
filename:提取后的资源文件名; -
chunks:设置提取范围; -
minChunks:提取规则- 数字:为
n时,只有该模块被n个入口同时引用才会进行提取,数组形式入口传入模块仍会提取 Infinity:所有模块都不会被提取。提取特定的几个模块;生成一个没有任何模块而仅仅包含Webpack初始化环境的manifest文件- 函数:每个模块都会经过这个函数的处理,当函数的返回值是
true时进行提取
- 数字:为
-
-
hash与长效缓存:为Webpack runtime单独提取manifest,写在最后,最先引入 -
优点
- 开发过程中减少了重复模块打包,可以提升开发速度;
- 减小整体资源体积;
- 合理分片后的代码可以更有效地利用客户端缓存。
-
缺点
- 一个
CommonsChunkPlugin只能提取一个vendor; manifest会使浏览器多加载一个资源;CommonsChunkPlugin在提取公共模块的时候会破坏掉原有Chunk中模块的依赖关系,导致难以进行更多的优化。
- 一个
-
需要在页面中添加一个
script标签来引入commons.js,并且该JS一定要在其他JS之前引入。
-
optimization.SplitChunks
只需要设置一些提取条件,chunks的值为all,SplitChunks将会对所有的chunks生效,不配置默认只对异步chunks生效
splitChunks: { chunks: "async", // 匹配模式,async(默认,只提取异步)、initial(只对入口生效)和all minSize: { javascript: 30000, style: 50000, }, maxSize: 0, minChunks: 1, maxAsyncRequests : 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, // 根据cacheGroups和作用范围自动为新生成的chunk命名,并以automaticNameDelimiter分隔 cacheGroups:{ vendors: { // 提取所有node_modules中符合条件的模块 test: /[\\/]node_modules[\\/]/, priority: -10, }, default: { // 提取被多次引用的模块 minChunks: 2, priority: -20, reuseExistingChunk: true } } }
资源异步加载
import()——通过import函数加载的模块及其依赖会被异步地进行加载,并返回一个Promise对象。可在任何地方调用,而非必须在顶层- 异步
chunk配置—— 配置chunkFileName为[name].js,就可以通过特殊注释/* webpackChunkName: 'chunkName' */来获取异步chunk名字
第七章 生产环境配置
环境配置的封装
- 一个配置文件,根据
ENV变量判断使用什么配置 - 不同环境使用不同配置文件和命令。可提取公共部分或者
webpack-merge
环境变量
new webpack.DefinePlugin ({ ENV: JSON.stringify('production'), IS_PRODUCTION: true, ENV_ID: 130912098 , CONSTANTS: JSON.stringify({ TYPES: ['foo','bar'] })})
许多框架与库都采用process.env.NODE_ENV作为一个区别开发环境和生产环境的变量。
DefinePlugin在替换环境变量时对于字符串类型的值进行的是完全替换,在替换后就会成为变量名,而非字符串值。因此对于字符串环境变量及包含字符串的对象要加上JSON.stringify。
source map
将编译、打包、压缩后的代码映射回源代码,有一定的安全隐患,即任何人都可以通过dev tools看到工程源码,越好的源码映射,build速度越慢
-
source-map—— 在外部生成一个文件 在控制台会显示错误代码准确信息和源代码的错误位置 -
eval-source-map—— 不会产生单独的文件内嵌到bundle.js中,但是可以显示行和列 -
cheap-module-source-map—— 会产生单独的文件,不会产生列 -
cheap-module-eval-source-map—— 不会产生文件,集成在文件中,不会产生列 -
hidden-source-map:仍然会产出完整的map文件,只不过不会在bundle文件中添加对于map文件的引用。这样一来,当打开浏览器的开发者工具时,我们是看不到map文件的,浏览器自然也无法对bundle进行解析。如果我们想要追溯源码,则要利用一些第三方服务,将map文件上传到那上面。目前最流行的解决方案是Sentry -
nosources-source-map:打包部署之后,我们可以在浏览器开发者工具的Sources选项卡中看到源码的目录结构,但是文件的具体内容会被隐藏起来。
在所有这些配置之外还有一种选择,就是我们可以正常打包出source map,然后通过服务器的nginx设置(或其他类似工具)将.map文件只对固定的白名单(比如公司内网)开放,这样我们仍然能看到源码,而在一般用户的浏览器中就无法获取到它们了。
资源压缩
UglifyJS、terser
缓存
- 资源
hash - 输出动态
HTML:html-webpack-plugin - 使
chunk id更稳定Webpack 3中HashedModuleIds-Plugin,它可以为每个模块按照其所在路径生成一个字符串类型的hash idWebpack 3以下webpack-hashed-module-id-plugin
第八章 打包优化
项目初期看不到优化点的时候不要过早优化,复杂且效果不明显
转义代码流程:
- 从配置中获取打包入口;
- 匹配
loader规则,并对入口模块进行转译; - 对转译后的模块进行依赖查找(如
a.js中加载了b.js和c.js); - 对新找到的模块重复进行步骤2)和步骤3),直到没有新的依赖模块。
HappyPack - 多线程,适用于转义任务比较重的工程
const HappyPack = require('happypack);module.exports = { module: { rules: [{ test: /\.js$/, include: 'node_modules', loader: 'happypack/loader', // 多个loader需加id => 'happypack/loader?id=js' }], plugins: [ new HappyPack({ // 多个loader需加id => id: 'js' loaders: [{ // 原有的babel-loader连同它的配置 loader: 'babel-loader', options: { presets: ['react'] } }] }) ] }}
缩小打包作用域
include和exclude:确定loader规则适用的范围
noParse:忽略某些模块,打包时Webpack不会对其进行任何解析,但仍会打包到bundle中
IgnorePlugin:排除某些模块,即使被引用也不会打包到bundle中
cache:Webpack 5中新增,全局启用文件缓存。⚠️无法自动监测缓存是否过期,现在如果更新需要手动修改cache.version
动态链接库与DLLPlugin
Code Splitting:设置一些特定的规则并在打包的过程中根据这些规则提取模块;
DLLPlugin:将vendor完全拆出来,有自己的一整套Webpack配置并独立打包,在实际工程构建时不用再进行处理,直接取用;
DllReferencePlugin:获取打包好的资源清单
HashedModuleIdsPlugin:解决数字id问题
DLLPlugin的name要与与output.library的值保持一致,因为app.js会通过name字段获取library。
// 单独创建Webpack配置文件const path = require('path');const webpack = require('webpack');const dllAssetPath = path.join(__dirname, 'dll');const dllLibraryName = 'dllExample';module.exports = { entry: ['react'], output: { path: dllAssetPath, filename: 'vendor.js', library: dllLibraryName }, plugins: [ new webpack.DllPlugin({ name: dllLibraryName, // 导出的名字 path: path.join(dllAssetPath, 'mainfest.json') // 资源清单的绝对路径 }) ]} /************* manifest.json **************/{ "name": "dllExample", "content": { "./node_modules/object-assign/index.js": { "id": "MgzW", "buildMeta": { "providedExports": true } } }}
// Webpack.config.js module.exports = { plugins: [ new webpack.DllReferencePlugin({ manifest: require(path.join(__dirname, 'dll/mainfest.json')) }) ]}
tree shakeing
- 只能对
ESM生效 - 如果使用
babel-loader,一定要禁用它的模块依赖解析,modules: false tree shaking只是标记,需要压缩工具去除死代码
第九章 开发环境调优
插件
webpack-dashboardwebpack-mergespeed-measure-webpack-pluginsize-plugin
模块热替换HMR
- 本地资源发生变化时
webpack-dev-server通过websocket向浏览器推送更新事件,并带上这次构建的hash,让客户端与上一次资源进行比对 - 客户端已经知道新的构建结果和当前的有了差别,向
WDS发起一个请求来获取更改文件的列表,即哪些模块有了改动 - 向
webpack-dev-server获取main chunk的增量更新
第十章 更多JavaScript打包工具
Rollup
更专注于JavaScript的打包,不会注入多余的代码
可通过output.format配置输出资源的模块形式
Parcel
速度快
- 利用
worker来并行执行任务; - 文件系统缓存;
- 资源编译处理流程优化。
零配置:并非完全没有,把配置进行了切分,交给Babel、PostHTML和PostCSS等一些特定的工具进行分别管理