Webpack 最佳实践
先简单回顾下 webpack 原理
Webpack 可以看做是模块打包机,把解析的所有模块变成一个对象,然后通过入口模块去加载我们的东西,然后依次实现递归的依赖关系,通过入口来运行所有的文件。由于 webpack 只认识 js,所以需要通过一系列的 loader 和 plugin 转换成合适的格式供浏览器运行。
loader主要是对资源进行加载/转译的预处理工作,其本质是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。某种类型的资源可以使用多个loader,执行顺序是从右到左,从下到上。plugin(插件)主要是扩展webpack的功能,其本质是监听整个打包的生命周期。webpack基于事件流框架Tapable, 运行的生命周期中会广播出很多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的 API 改变输出结果。
webpack 安装
新建一个目录,进入目录初始化 package.json,并安装 webpack 依赖
基础配置
webpack 默认配置文件名字为 webpack.config.js,于是在项目根目录下新建一个名为 webpack.config.js 的文件,在配置文件里写最简单的单页面配置:
配置详解
mode - 打包模式
development为开发模式,打包后代码不会被压缩production为生产模式,打包后代码为压缩代码entry - 入口文件
output - 打包文件配置
filename:打包后文件,filename 的值可设置成带hash戳的文件:js/bundle.[hash].js/js/bundle.[hash:8].js(只显示 8 位 hash 戳)path:打包文件路径,需为绝对路径publicPath:上线的 cdn 地址
TIP: 上述代码中
path为内置模块,无需安装,直接引入即可。
新建后还需在项目根目录下的 src/js 目录下新建 index.js 文件,然后随便输入一句 js 代码。
配置后可使用 webpack 命令尝试打包,若报错找不到命令可 npm i webpack -g 全局安装后再打包,打包成功后会输出到项目根目录下的 dist 目录。
项目目录结构大致如下
html 文件打包
由于 webpack 只认识 js,因此需通过 html-webpack-plugin 插件打包 html 文件
安装后在 webpack.config.js 配置文件中
production 模式下可以开启 html 文件的压缩配置:
配置详解
plugins - webpack 插件配置
html-wepack-plugin 配置
template - html 模板文件的相对/绝对路径
minify - 压缩配置
removeAttributeQuotes:删除属性双引号collapseWhitespace:代码压缩成一行hash - 引入文件带上 hash 戳
TIP: 如果不指定模板
template配置,将是插件默认的 html 文件,而不是项目中的 html 文件
开启服务
webpack 通过安装 webpack-dev-server 开启服务
配置 webpack.config.js
参考 webpack 视频讲解:进入学习
配置详解
devServer -
webpack-dev-server配置port - 端口号
compress - 开启
gzip压缩open - 启动后自动把页面打开
client
progress:在浏览器中以百分比显示编译进度
配置好可运行 webpack-dev-server 命令查看效果,若找不到命令可 npm i webpack-dev-server -g 全局安装下
跨域
开发过程中容易遇到接口跨域问题,可通过 devServer.proxy 配置解决
假设接口地址为 http://localhost:3000/api/users,对 /api/users 的请求可如下配置
但实际项目中接口的地址有很多种可能,一般不会有 /api 目录,即一般接口地址为http://localhost:3000/users,因此枚举配置会很麻烦,可通过代理请求解决
即先请求 http://localhost:3000/api/users 接口地址,然后通过 devServer 代理到 http://localhost:3000/users
本文通过 express 开启接口服务,接口地址为 http://localhost:3000/user,接口代码不再赘述,后期上传完整的源码,可通过 node "项目路径\webpack5\src\js\server.js" 启动接口服务,然后配置 webpack.config.js
devServer 配置详解
proxy - 代理配置
target - 接口域名
pathRewrite - 接口路径重写,把请求代理到接口服务器上
mock 接口数据
当后端接口没有写好,又不希望被阻塞进度,可以通过 mock 前期跟后端约定好的接口数据格式来模拟调试页面。可使用有自定义函数和应用自定义中间件的能力的配置 devServer.setupMiddlewares,在 middlewares.unshift 中的回调函数使用 res.send 把需要 mock 的数据传递进去:
样式处理
样式处理需要用到的 loader 及其作用:
less-loader:加载和转译 LESS 文件postcss-loader:使用 PostCSS 加载和转译 CSS/SSS 文件,如可以处理autoprefixercss 包,为 css 添加浏览器前缀css-loader:解析@importandurl()语法,使用 import 加载解析后的 css 文件,并且返回 CSS 代码mini-css-extract-plugin的loader:抽取出 css 文件,通过 link 标签引入 html 文件
安装依赖,若使用的是 sass,则把 less less-loader 换成 node-sass sass-loader 即可
配置 webpack.config.js
还需新建并配置 postcss.config.js
上述文件配置好后,打包后会发现 css3 样式还是没有添加前缀,还需配置 package.json 的 browserlist 才能生效
js 处理及语法校验
es6 或更高级的语法需转化成 es5,并使用 eslint 规范代码:
babel-loader:加载 ES2015+ 代码,然后使用 Babel 转译为 ES5@babel/preset-env:基础的 ES 语法分析包,各种转译规则的统一设定,目的是告诉 loader 要以什么规则来转化成对应的 js 版本@babel/plugin-transform-runtime:解析 generator 等高级语法,但不包含 include 语法,include 语法需安装@babel/polyfill。官方文档说上线需带上@babel/runtime这个补丁,该包还做了一些方法抽离的优化,如 class 语法的抽离(抽离出 classCallCheck 方法)@babel/polyfill:解析更高级的语法,如promise,include等,在 js 文件中require引入即可eslint-loader:校验 js 是否符合规范,可自行在 eslint 网站上配置下载
安装依赖
webpack.config.js
配置 source-map
源码映射配置 source-map 的值:
source-map 映射源码 会单独生成 source-map 文件 出错了会标识当前报错的行和列 大而全
eval-source-map 不会产生单独的文件,可显示行和列
cheap-module-source-map 不会标识列,会生成单独的映射文件
cheap-module-eval-source-map 不会产生文件 集成在打包后的文件中 不会产生列
webpack.config.js
引入 js 全局变量
有三种方式可以引入全局变量
expose-loader
可把变量暴露到 window 全局对象上,以 jquery 为例,先安装依赖
然后在 webpack.config.js 中配置 loader,把 $ 暴露到 window 全局对象上
除了上述方法外还可以在入口 js 文件中暴露
providePlugin
可使用 webapck 内置插件 providePlugin 给每个模块中注入变量,还是以 jquery 为例
在 webapck.config.js 中配置
然后在任意 js 模块中可以直接使用 $调用,无需引入 jquery 包
通过 cdn 引入
还可以通过 cdn 链接的方式引入全局变量,但如果此时 js 文件中多写了 import $ from 'jquery',就会把 jquery 也打包进去,可使用 external 防止将某些 import 的包(package)打包到 bundle 中
index.html
webpack.config.js
这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:
上面的例子。属性名称是 jquery,表示应该排除 import $ from 'jquery' 中的 jquery 模块。为了替换这个模块,jQuery 的值将被用来检索一个全局的 jQuery 变量。换句话说,当设置为一个字符串时,它将被视为全局的(定义在上面和下面)。
样式压缩和 js 压缩
production 模式下需压缩 css 可使用插件 css-minimizer-webpack-plugin,但使用了此插件压缩 css, 会导致 js 不压缩,所以需要安装 js 压缩插件 terser-webpack-plugin
图片处理
需要 loader 解析图片资源:
file-loader:将文件的 import/require()解析为 url,并将文件发送到输出文件夹(dist 文件夹),并返回(相对)URLurl-loader:像file-loader一样工作,但如果文件小于限制,可以返回 data URL,即把图片变成 base64html-loader:可以解析 html 标签引入的图片,可以通过查询参数 attrs,指定哪个标签属性组合(tag-attribute combination)应该被处理,默认值:attrs=img:src
安装依赖
配置 webpack.config.js
TIP: url-loader 可以使用 options.limit 限制,小于多少 k 时使用 base64 转换,大于这个体积使用 file-loader 打包
html-loader 配置报错问题
html-loader 需关闭 es6 模块化,使用 commonjs 解析,否则会报错。原因主要是两个 loader 解析图片的方式不一样。
项目目录结构大致如下
resolve 配置
resolve 常用的属性配置:
modules:告诉 webpack 解析模块时应该搜索的目录。绝对路径和相对路径都能使用,但是要知道它们之间有一点差异。使用绝对路径,将只在给定目录中搜索。使用相对路径,通过查看当前目录以及祖先路径。
如果想要优先于某个目标目录搜索,则需把该目录放到目标目录前面,可详看官网例子
alias:设置别名,方便使用,下面的例子应用于src目录下的路径使用mainFields:当从 npm 包中导入模块时(例如,import * as D3 from 'd3'),此选项将决定在 package.json 中使用哪个字段导入模块。根据 webpack 配置中指定的 target 不同,默认值也会有所不同。这里 browser 属性是最优先选择的,因为它是 mainFields 的第一项extensions:尝试按顺序解析这些后缀名。当引入的文件不带后缀名,且有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
多页面配置
多页面顾名思义就是多个 html 页面,因此一般也会有多个 js 入口文件。
下面的配置中 entry 的 key 值对应的是 output 属性的 [name] 值,HtmlWebpackPlugin 中的属性 chunks 表示引入 [name] 对应的 js 代码文件,不指定 chunks 值将引入所有打包出来的 js 文件。
即本例的 [name] 分别为 home 和 other,即打包出来是 home.js 和 other.js,最终打包的效果是 home.html 引入的是 home.js,other.html 引入的是 other.js 文件
配置 webpack.config.js
webpack 小插件应用
clean-webpack-plugin
清除插件,可用于清除上一次的打包文件,清除目录为 output.path 的值
安装依赖
配置 webpack.config.js
copy-webpack-plugin
拷贝插件,把某个文件夹导出到打包文件夹中,如文档文件夹(如 doc 文件夹)
安装依赖
配置 webpack.config.js
插件配置属性
patterns
from: 源文件,相对于当前目录路径
to:目标文件,相对于 output.path 文件路径,会生成到 dist/doc 目录下
webpack.BannerPlugin
版权声明插件,webpack 内置插件,无需安装
配置 webpack.config.js
watch
可以监听文件变化,当它们修改后会重新编译,可以用在实时打包的场景下
配置 webpack.config.js
配置属性
watchOptions监听参数poll: 每 n 毫秒检查一次变动aggregateTimeout:防抖,当第一个文件更改,会在重新构建前增加延迟。这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位ignored:对于某些系统,监听大量文件会导致大量的 CPU 或内存占用。可以使用正则排除像node_modules如此庞大的文件夹
配置后在命令窗口输入 npm run build 就可以进行监控并实时打包了,如图实时打包了一次
环境变量
通过 webpack 内置插件 DefinePlugin 定义 DEV 环境变量。
还可以把开发和生产模式不同的 webpack 配置抽离出来,即把 webpack.config.js 文件一分为三
公共配置放在
webpack.config.base.js文件开发模式配置放在
webpack.config.dev.js文件,通过webpack-merge合并webpack.config.base.js文件和webpack.config.dev.js文件的配置生产模式配置放在
webpack.config.prod.js文件 (和开发模式配置文件逻辑一致)
webpack.config.dev.js 文件完整代码如下:
使用环境变量后目录结构大致如下
更改配置文件后,打包命令也要做适当调整,打包时需要指定配置文件:
生产模式配置文件和公共配置文件源码后期上传
热更新
webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。默认启用热更新,无需配置,它会自动应用 webpack.HotModuleReplacementPlugin,这是启用 HMR 所必需的。
优化
下面的配置代码都是在 webpack 配置文件中,不再赘述
缩小构建范围
include/exclude 选其一即可
module.noParse
由于 webpack 会通过入口文件解析 import, require 引用的包,还会去分析包的依赖,但有些包是没有依赖的,因此可以通过 noParse 不解析某个引用包中的依赖关系,来提高构建性能。适合没有依赖项的包,如 jquery
webpack.IgnorePlugin
webpack 内置插件 IgnorePlugin 可以阻止生成用于导入的模块,或要求调用与正则表达式或筛选函数匹配的模块。如 moment 包内引入了很多语言包,这些语言包都放在 locale 文件夹下,但大部分实际场景只会引用一个的语言包,因此打包时可忽略 moment 目录下的 locale 语言包
忽略后再重新再 js 文件中引入某个语言包就能正常使用了
抽离公共代码
一般用在多页应用场景或者是单个 js 文件太大,请求需要很长时间,需要拆成几个 js 文件,优化请求速度,使用 optimization 的 splitChunks 属性来优化。
splitChunks.cacheGroups 缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项。但是 test、priority 和 reuseExistingChunk 只能在缓存组级别上进行配置。将它们设置为 false以禁用任何默认缓存组。
看下面配置之前先了解 splitChunks 的几个属性:
priority:抽离代码的优先级,值越高越先被抽离,防止某些模块在前面的模块抽离完了后面没被抽离到,在本例中是防止vendor模块被common模块抽离完了没被抽离到name:每个模块(chunk)的文件名,不定义将是随机名字test:匹配目录chunks:选择哪些 chunk 进行优化initial:从入口处开始提取代码,若有异步模块考虑后面两个值async:异步模块all:可以存在异步和非异步模块minSize:生成 chunk 的最小体积,此处为方便测试设置为 0minChunks:拆分前必须共享模块的最小 chunks 数,当前代码块引用多少次才被抽离,此处为方便设置设置为 1
本例中分割了 common 和 vendor 两个 chunk
为方便大家理解,献上打包后的目录树结构
这一块比较难理解,建议多试几次打包对比差异就懂了
懒加载
通过 es6 的 import() 语法实现懒加载,通过 jsonp 实现动态加载文件,import 函数返回的是 promise 对象。vue 懒加载,react 懒加载都是这样实现的。举个简单的栗子,某些 js 文件在按钮点击后再请求加载。
除了以上的优化方法之外,还有 dll 预构建,多线程构建/压缩,利用缓存提升二次构建速度,动态
polyfill等等,可根据实际情况自行选择优化方案,这里不一一赘述
webpack 自带优化
tree-shaking
使用 import 语法在生产环境下没用到的代码不会被打包, 即 tree shaking, require 语法不支持 tree-shaking
scope hosting
scope hosting(作用域提升),举个栗子:
代码打包出来只有最后一句, webpack 打包会自动省略一些可以简化的代码
手写简易 less-loader
less-loader.js
在 /loaders/less-loader.js 目录文件中引入 less 插件
webpack.config.js
写入以下配置









评论