webpack 是一个打包工具,他的宗旨是一切静态资源皆可打包。
自从出现模块化以后,大家可以将原本一坨代码分离到个个模块中,但是由此引发了一个问题。
每个 JS 文件都需要从服务器去拿,由此会导致加载速度变慢。
Webpack 最主要的目的就是为了解决这个问题,将所有小文件打包成一个或多个大文件。
webpack 是一个模块绑定器,它可以从AMD 模块, UMD,Common JS 还有 ES 模块这些模块中获取依赖关系。
webpack 将整个文件视为模块。 但是,请不要忘记它的主要目的:加载 ES 模块。
webpack 的最终目标是将所有这些不同的源和模块类型统一起来,从而将所有内容导入 JavaScript 代码,并最生成可以运行的代码。
从 wepack V4.0.0
开始, webpack
是开箱即用的,在不引入任何配置文件的情况下就可以使用。
Webpack 的 entry (入口点) 是收集前端项目的所有依赖项的起点。
实际上,这是一个简单的 JavaScript 文件。
Webpack 的默认入口点(从版本 4 开始)是 src/index.js
,它是可配置的。
webpack 可以有多个入口点。
出口,output 是生成的 JavaScript 和静态文件的地方。
Loaders 可帮助 webpack 处理各种文件扩展名,例如 CSS,图像或 txt 文件
Loader 就是将 Webpack 不认识的内容转化为认识的内容
Loaders 的目标是在模块中转换文件(JavaScript 以外的文件)
注意点:webpack loaders 是从右到左执行的。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}
};
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html")
})
]
};
@import
语法像 import
和 require
一样去处理 css 里面引入的模块css-loader
解析后的内容挂载到 html 页面当中注意 loader
的出现顺序:首先是 sass-loader
,然后是 css-loader
,最后是 style-loader
。
loader
有一个参数可以修改优先级:enforce
参数,其值可以为: pre
(优先执行) 或 post
(滞后执行)。
webpack 本身并不知道如何转换 JavaScript 代码。
该任务已外包给 babel 的第三方 loader,特别是 babel-loader。
babel 是一个 JavaScript 编译器和 “编译器”。 babel 可以将现代 JS (es6, es7…) 转换为可以在(几乎)任何浏览器中运行的兼容代码。
同样,要使用它需要安装一些 Loader:
注意: 即使没有 babel,webpack 也可以正常工作。 仅在执行 ES5 代码时才需要进行代码转换过程。
Plugins 是 webpack 中的插件,就是对 webpack 现有功能的各种扩展,比如打包优化、文件压缩、提取 HTML,CSS 或设置环境变量等
与 Loader 用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务
webpack 有两种操作模式:开发(development)
和生产(production)
。
development
:将 process.env.NODE_ENV
的值设置为 development
,启用 NamedChunksPlugin
和 NamedModulesPlugin
production
:将 process.env.NODE_ENV
的值设置为 production
,启用 TerserWebpackPlugin
, ModuleConcatenationPlugin
, NoEmitOnErrorsPlugin
, OccurrenceOrderPlugin
, SideEffectsFlagPlugin
和 UglifyJsPlugin
在开发模式中,为了便于代码调试方便我们快速定位错误,不会压缩混淆源代码。
在生产模式下,webpack 进行了许多优化:
TerserWebpackPlugin
进行缩小以减小 bundle
的大小ModuleConcatenationPlugin
提升作用域在生产模式下配置 webpack 请打开 package.json
并添加一个 “build” 命令
运行 npm run build
,webpack 会生成一个压缩的包。
代码拆分 (Code splitting) 是指针对以下方面的优化技术:
通过代码拆分,开发人员可以决定仅在响应某些用户交互时加载整个 JavaScript 块,比如单击或路由更改 (或其他条件)。被拆分的一段代码称为 chunk。
在 webpack 中有三种激活 code splitting 的主要方法:
optimization.splitChunks
选项考虑一个使用 Moment.js 的 JS 应用程序,Moment.js
是流行的时间和日期 JS 库。
在项目文件夹中安装该库:
npm i moment
现在清除 src/index.js
的内容,并引入 moment 库:
import moment from "moment";
运行 npm run build
并查看控制的输出内容:
main.js 350 KiB 0 [emitted] [big] main
整个 moment
库都绑定到了 main.js
中这样是不好的。
借助 optimization.splitChunks
,我们可以从主包中移出 moment.js
。
要使用它需要在 webpack.config.js
添加 optimization
选项:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
module: {
// ...
},
optimization: {
splitChunks: { chunks: "all" }
},
// ...
};
运行 npm run build
并查看运行结果:
main.js 5.05 KiB 0 [emitted] main
vendors~main.js 346 KiB 1 [emitted] [big] vendors~main
现在,我们有了一个带有 moment.js 的 vendors〜main.js
,而主入口点的大小更合理。
注意:即使进行代码拆分,moment.js
仍然是一个体积较大的库。 有更好的选择,如使用 luxon
或 date-fns
。
Code splitting 的一种更强大的技术是使用动态导入来有条件地加载代码。
在 ECMAScript 2020 中提供此功能之前,webpack 提供了动态导入。
这种方法在 Vue 和 React 之类的现代前端库中得到了广泛使用(React 有其自己的方式,但是概念是相同的)。
Code splitting 可用于:模块级别、路由级别
例如,你可以有条件地加载一些 JavaScript 模块,以响应用户的交互(例如单击或鼠标移动)。
或者可以在响应路由更改时加载代码的相关部分:
const getUserModule = () => import("./common/usersAPI");
从官网上的描述我们其实不难理解,webpack
的作用其实有以下几点:
webpack
的 Loader
机制,不仅仅可以帮助我们对代码做 polyfill
,还可以编译转换诸如.less, .vue, .jsx
这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。webpack
的 Plugin
机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)url-loader
:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)source-map-loader
:加载额外的 Source Map 文件,以方便断点调试svg-inline-loader
:将压缩后的 SVG 内容注入代码中image-loader
:加载并且压缩图片文件babel-loader
:把 ES6 转换成 ES5ts-loader
: 将 TypeScript 转换成 JavaScriptawesome-typescript-loader
:将 TypeScript 转换成 JavaScript,性能优于 ts-loadersass-loader
:将 SCSS/SASS 代码转换成 CSScss-loader
:加载 CSS,支持模块化、压缩、文件导入等特性style-loader
:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSSpostcss-loader
:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀eslint-loader
:通过 ESLint 检查 JavaScript 代码tslint-loader
:通过 TSLint 检查 TypeScript 代码vue-loader
:加载 Vue.js 单文件组件cache-loader
: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里html-webpack-plugin
:简化 HTML 文件创建 (依赖于 html-loader)web-webpack-plugin
:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用terser-webpack-plugin
: 支持压缩 ES6 (Webpack4)webpack-parallel-uglify-plugin
: 多进程执行代码压缩,提升构建速度mini-css-extract-plugin
: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代 extract-text-webpack-plugin)serviceworker-webpack-plugin
:为网页应用增加离线缓存功能clean-webpack-plugin
: 目录清理ModuleConcatenationPlugin
: 开启 Scope Hoistingspeed-measure-webpack-plugin
: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)webpack-bundle-analyzer
: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)Loader
本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
Plugin
就是插件,基于事件流框架 Tapable
,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
Loader
在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test (类型文件)、loader、options (参数) 等属性。
Plugin
在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
简洁版
1、loader 主要用于转化某些类型的模块,它是一个转化器
2、plugin 是插件,它是对 webpack 本身的扩展,是一个扩展器
与 Loader 用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
初始化参数
:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数开始编译
:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译确定入口
:根据配置中的 entry 找出所有的入口文件编译模块
:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理完成模块编译
:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系输出资源
:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会输出完成
:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统在以上过程中,Webpack
会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
简单说
source map
是将编译、打包、压缩后的代码映射回源代码的过程。
打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。
map 文件只要不打开开发者工具,浏览器是不会加载的。
线上环境一般有三种处理方案:
hidden-source-map
:借助第三方错误监控平台 Sentry 使用nosources-source-map
:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高sourcemap
:通过 nginx 设置将 .map 文件只对白名单开放 (公司内网)注意:避免在生产中使用 inline-
和 eval-
,因为它们会增加 bundle 体积大小,并降低整体性能。
在发现源码发生变化时,自动重新构建出新的输出文件。
Webpack 开启监听模式,有两种方式:
缺点:每次需要手动刷新浏览器
原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout
后再执行。
Webpack
的热更新又称热替换(Hot Module Replacement
),缩写为 HMR
。
这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
HMR 的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分)
实际上 WDS 与浏览器之间维护了一个 Websocket
,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。
客户端对比出差异后会向 WDS 发起 Ajax
请求来获取更改内容 (文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp
请求获取该 chunk 的增量更新。
后续的部分 (拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?) 由 HotModulePlugin
来完成,提供了相关 API 以供开发者针对自身场景进行处理,像 react-hot-loader
和 vue-loader
都是借助这些 API 实现 HMR。
其实是开启了express应用,添加了对 webpack 编译的监听,添加了和浏览器的 websocket 长连接
当文件变化触发 webpack 进行编译并完成后,会通过socket消息告诉浏览器准备刷新。
而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块
webpack-dev-server 可以支持热更新,通过生成文件的 hash 值来比对需要更新的模块,浏览器再进行热替换
文件指纹是打包后输出的文件名的后缀。
Hash
:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改 (图片)Chunkhash
:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash (js)Contenthash
:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变 (css)hash 代表每次 webpack 编译中生成的 hash 值,所有使用这种方式的文件 hash 都相同。每次构建都会使 webpack 计算新的 hash。
chunkhash 基于入口文件及其关联的 chunk 形成,某个文件的改动只会影响与它有关联的 chunk 的 hash 值,不会影响其他文件 contenthash 根据文件内容创建。
当文件内容发生变化时,contenthash 发生变化
使用高版本
的 Webpack 和 Node.js
压缩代码 terser-webpack-plugin 开启 parallel 参数
通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
图片压缩,配置 image-webpack-loader
缩小打包作用域
:
充分利用缓存提升二次构建速度
:服务端设置http缓存头
(cache-control)延迟加载
:使用 import()方式
,可以动态加载的文件分到独立的 chunk, 以得到自己的 chunkhash保持hash值的稳定
:编译过程和文件内通的更改尽量不影响其他文件 hash 的计算,对于低版本 webpack 生成的增量数字 id 不稳定问题,可用 hashedModuleIdsPlugin 基于文件路径生成解决⽤ webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。
压缩代码
:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤ webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS ⽂件, 利⽤ cssnano (css-loader?minimize)来压缩 css利⽤CDN加速
: 在构建过程中,将引⽤的静态资源路径修改为 CDN 上对应的路径。可以利⽤ webpack 对于 output 参数和各 loader 的 publicPath 参数来修改资源路径Tree Shaking
: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动 webpack 时追加参数 --optimize-minimize 来实现Code Splitting
: 将代码按路由维度或者组件分块 (chunk), 这样做到按需加载,同时可以充分利⽤浏览器缓存提取公共第三⽅库
: SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码