共计 7944 个字符,预计需要花费 20 分钟才能阅读完成。
前言
本篇文章将介绍一些 webpack
的进阶用法,演示内容继承自上一篇文章的内容,所以没看过上一篇文章的倡议先学习上一篇内容再浏览此篇内容,会更有利于此篇的学习~
文件指纹
文件指纹指的是打包输入的文件名后缀,个别用来做版本治理、缓存等
webpack 的指纹策略有三种:hash
、chunkhash
、contenthash
,它们之间最次要的区别就是每种 hash 影响的范畴不同。
占位符
webpack 提供占位符用于将特定信息附加在打包输入的文件上
名称 | 含意 |
---|---|
[ext] | 资源后缀名 |
[id] | 文件标识符 |
[name] | 文件名称 |
[path] | 文件的相对路径 |
[folder] | 文件所在的文件夹 |
[hash] | 模块标识符的 hash,默认是 md5 生成 |
[chunkhash] | chunk 内容的 hash,默认是 md5 生成 |
[contenthash] | 文件内容的 hash,默认是 md5 生成 |
[query] | 文件的 query,例如,文件名 ? 前面的字符串 |
[emoji] | 一个随机的指代文件内容的 emoji |
咱们能够应用特定的语法,对 hash
、chunkhash
、contenthash
进行切片:[chunkhash:4]
,像 8c4cbfdb91ff93f3f3c5
这样的哈希会最初会变为 8c4c
。
hash
与整个我的项目的构建无关,只有我的项目内文件有批改,整个我的项目构建的 hash 值就会扭转
咱们应用多入口打包来体验一下:
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
index: './src/index.js'
},
output: {filename: '[name].[hash:6].js',
path: __dirname + '/dist',
clean: true
},
// ...
}
此时咱们应用了占位符来设置文件指纹 [name].[hash:6].js
代表的是文件名 + 6 位 hash
此时咱们执行npm run build
,看打包进去的内容如下:
此时两个 js 文件的 hash
都是207495
咱们批改一下 index.js
的内容,再打包一次
咱们会发现此时两个 js 文件的 hash
都变成了9f0e2d
chunkhash
chunkhash 是和 webpack 打包的模块相干,每一个 entry 作为一个模块,会产生不同的 Chunkhash 值,文件扭转时只会影响以后 chunk 组的 hash 值
咱们再来看看 chunkhash
// webpack.config.js
module.exports = {
entry: {
main: './src/main.js',
index: './src/index.js'
},
output: {filename: '[name].[chunkhash:6].js',
path: __dirname + '/dist',
clean: true
},
// ...
}
还是延用下面的例子,这次咱们只批改 main.js
文件内容
批改前两个文件的 hash 值如下:
批改后:
此时只有 main.js
的打包产物的 hash 产生了变动
contenthash
contenthash 是和依据文件内容相干,单个文件发生变化,只会引起此文件的 hash 值
这里咱们应用 miniCssExtractPlugin
将 CSS 内容提取成文件,并为它设置contenthash
// webpack.config.js
const miniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: {
main: './src/main.js',
index: './src/index.js'
},
output: {filename: '[name].[contenthash:6].js',
path: __dirname + '/dist',
clean: true
},
mudole: {
rules: [
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader, 'css-loader']
},
// ...
]
},
plugins: [
// ...
new miniCssExtractPlugin({filename: 'css/[name].[contenthash:6].css'
}),
]
// ...
}
而后打包看一下此时的 hash:
咱们批改 index.css
内容再打包一次
此时只有 index.css
的打包产物 hash 值产生了变动。
依据不同的文件类型个别抉择不同的文件指纹策略,通常状况下:
- JS 文件采纳 [chunkhash] 文件指纹策略
- CSS 文件采纳 [contenthash] 文件指纹策略
- 图片资源采纳 [hash] 文件指纹策略
代码压缩
压缩 JS
目前最成熟的 JavaScript 代码压缩工具是 UglifyJS
,它可能剖析 JavaScript 语法树,了解代码含意,从而能做到诸如去掉有效代码、去掉日志输入代码、缩短变量名等优化。但很遗憾的是UglifyJS
不再保护,并且它不反对 ES6 +。
当初举荐应用的是Terser
,它在 UglifyJS 根底上减少了 ES6 语法反对,并重构代码解析、压缩算法,使得执行效率与压缩率都有较大晋升,并且Webpack5.0 后默认应用 Terser 作为 JavaScript 代码压缩器
简略实用:
// webpack.config.js
module.exports = {
//...
optimization: {minimize: true}
}
须要留神的是在生产模式中构建时,Terser 压缩是默认开启的
当然它也容许你通过提供一个或多个定制过的 TerserPlugin 实例,笼罩默认的压缩工具,实现更精密的压缩性能
// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
//...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions},
})
]
}
}
在 Webpack4 中 默认应用 uglifyjs-webpack-plugin
压缩代码,也能够通过 minimizer
数组替换为 Terser 插件
压缩 CSS
CSS 代码同样也能够应用 webpack 来进行压缩,比拟常见的 CSS 压缩工具有:cssnano
、css-minimizer-webpack-plugin
对于 webpack5 或更高版本,官网举荐应用 CssMinimizerWebpackPlugin
,该插件是应用 cssnano
优化和压缩 CSS,反对缓存和并发模式下运行。
装置:
npm i css-minimizer-webpack-plugin
配置:
// webpack.config.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 用压缩 css
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用来提取 css 成独自的文件
module.exports = {
//...
module: {
rules: [
{
test: /.css$/,
// 留神,MiniCssExtractPlugin.loader 与 style-loader 不能同时应用
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimize: true,
minimizer: [
// Webpack5 之后,约定应用 '...' 字面量保留默认 minimizer 配置
"...",
new CssMinimizerPlugin(),],
},
plugins: [new MiniCssExtractPlugin()],
};
⚠️这里须要留神的是须要应用 mini-css-extract-plugin
将 CSS 代码抽取为独自的 CSS 产物文件,这样能力命中 css-minimizer-webpack-plugin
默认的 test
逻辑。
压缩 HTML
咱们之前应用的html-webpack-plugin
,它除了能够生成 html 模版,也能够用来对 html 进行压缩。
htmlWebpackPlugin常见参数
template
:模板的门路,默认会去寻找src/index.ejs
是否存在。filename
:输入文件的名称,默认为index.html
。inject
:是否将资源注入到模版中,默认为true
。minify
:压缩参数。在生产模式下(production
),默认为true
;否则,默认为false
。
// webpack.config.js
module.exports = {
// ...
plugins: [
// ...
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
minify: true
}),
]
}
生成的 HTML 将应用 html-minifier-terser
和以下选项进行压缩,所以它实际上的压缩性能其实是 html-minifier-terser
来实现的,更多配置能够查看这个工具文档
{
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
}
禁止生成 LICENSE 文件
通过下面这些配置后,我发现了一个奇怪的问题,那就是每个 bundle
产物都多了一个同名的 LICENSE.txt
文件,关上一看外面都是一些正文内容。
为什么会生成这些文件,带着纳闷我去翻了下官网文档,Webpack5 默认压缩代码工具为terser-webpack-plugin
,那就先从它动手吧。
在它的配置中找到了 extractComments
参数,默认值为true
,示意将正文剥离到独自的文件中。
如果咱们不想要,间接关掉该配置就行了
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [new cssMinimizerPlugin(),
new terserPlugin({extractComments: false, // 敞开正文剥离性能}),
'...'
]
},
}
CSS 加强(autoprefixer)
前端最头疼的问题莫过于解决兼容性,因为前端的运行环境并不固定,能够在各种浏览器以及各种 webview 中运行,并且每个浏览器厂商对 CSS 的写法也各不相同,这就势必会导致呈现一些问题。
比方为了兼容各种浏览器内核,圆角属性应该这样写:
.container {
-moz-border-radius: 16px;
-webkit-border-radius: 16px;
-o-border-radius: 16px;
border-radius: 16px;
}
试想一下如果在开发中须要你这样写,那是不是太不合理了?
咱们个别都会通过 webpack 配置插件来帮咱们解决这个问题,解决 CSS 咱们首先会想到 postcss
,没错 webpack 也有应用 postcss 解决 CSS 的 loader — postcss-loader
,而后咱们还须要应用postcss
的插件 autoprefixer
来帮咱们主动增加浏览器前缀。
装置:
npm i postcss postcss-loader autoprefixer
批改配置:
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
//...
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {plugins: ['autoprefixer']
}
}
}]
},
]
}
//...
}
⚠️这里须要留神的是,如果你想自定义转换的规定,最好是将 autoprefixer 的 browsers
选项替换为 browserslist
配置。在 package.json
或。Browserslistrc
文件。应用 browsers
选项可能导致谬误,并且browserslist
配置能够用于 babel、autoprefixer、postcss-norize 等工具。
比方 package.json 中配置browserslist
:
// package.json
{
//...
"browserslist": [
"last 10 Chrome versions",
"last 5 Firefox versions",
"Safari >= 6",
"ie> 8"
]
}
此时咱们打包的 CSS 的产物就会主动增加浏览器前缀
动态资源拷贝
如果咱们须要在 html 中援用一些不须要打包解决的资源,比方上面这种状况
在 index.html
中引入了一些日志的工具函数,这时候咱们间接跑起来会发现这个文件间接 404 了,这是怎么回事?
首先咱们写的门路必定是没问题的,问题在于咱们打包后这个 utils
文件必定是不在这个地位了,所以会报 404
所以这里咱们须要应用 copy-webpack-plugin
将文件拷贝至 dist
目录下
// webpack.config.js
const copyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// ...
plugins: [
new copyWebpackPlugin({
patterns: [{from: 'module', to: __dirname + '/dist/module/'}
]
}),
]
}
此时再打包,咱们会发现 dist
目录下曾经有了module/utils.js
,并且页面也不会再报 404 了
sourcemap
SourceMap 就是一个信息文件,外面贮存着代码的地位信息。这种文件次要用于开发调试,当初代码都会通过压缩混同,这样报错提醒会很难定位代码。通过 SourceMap 能疾速定位到源代码,并进行调试。
比方咱们没有开启sourcemap
,而后开发过程中报错了,它的报错信息是这样的:
定位过来是打包后的内容,这样的话对咱们排查报错十分不不便。
当咱们开启sourcemap
,再来看看这个同样的报错是怎么的:
// webpack.config.js
module.exports = {
// ...
devtool: 'eval-cheap-module-source-map',
}
此时的报错指向就十分清晰了~
关键字
devtool 的值有 20 多种,并且都是由以下七种关键字的一个或多个组成
eval
关键字
当 devtool
值蕴含 eval
时,生成的模块代码会被包裹进一段 eval
函数中,且模块的 Sourcemap 信息通过 //# sourceURL
间接挂载在模块代码内
source-map
关键字
当 devtool
蕴含 source-map
时,Webpack 才会生成 Sourcemap 内容
cheap
关键字
当 devtool
蕴含 cheap
时,生成的 Sourcemap 内容会摈弃 列维度的信息,这就意味着浏览器只能映射到代码行维度
module
关键字
module
关键字只在 cheap
场景下失效,例如 cheap-module-source-map
、eval-cheap-module-source-map
。当 devtool
蕴含 cheap
时,Webpack 依据 module
关键字判断按 loader 联调处理结果作为 source,还是按解决之前的代码作为 source
nosources
关键字
当 devtool
蕴含 nosources
时,生成的 Sourcemap 内容中不蕴含源码内容 —— 即 sourcesContent
字段
inline
关键字
当 devtool
蕴含 inline
时,Webpack 会将 Sourcemap 内容编码为 Base64 DataURL,间接追加到产物文件中
hidden
关键字
通常,产物中必须携带 //# sourceMappingURL=
指令,浏览器能力正确找到 Sourcemap 文件,当 devtool
蕴含 hidden
时,编译产物中不蕴含 //# sourceMappingURL=
指令
devtool 的值以及各自的性能能够在 webpack 文档上查看
如何抉择
-
对于开发环境,适宜应用:
eval
:速度极快,但只能看到原始文件构造,看不到打包前的代码内容;cheap-eval-source-map
:速度比拟快,能够看到打包前的代码内容,但看不到 loader 解决之前的源码;cheap-module-eval-source-map
:速度比拟快,能够看到 loader 解决之前的源码,不过定位不到列级别;eval-source-map
:首次编译较慢,但定位精度最高;
-
对于生产环境,则适宜应用:
source-map
:信息最残缺,但安全性最低,内部用户可轻易获取到压缩、混同之前的源码,谨慎应用;hidden-source-map
:信息较完整,安全性较低,内部用户获取到.map
文件地址时仍然能够拿到源码,谨慎应用;nosources-source-map
:源码信息缺失,但安全性较高,须要配合 Sentry 等工具实现残缺的 Sourcemap 映射。
解决跨域
在开发过程中,咱们势必会遇到跨域问题,对于本地开发咱们个别能够通过配置代理来解决
咱们先来简略写一个接口:
const express = require('express')
const app = express()
app.get('/api/getInfo', (req, res) => {
res.json({
code: 200,
data: {
name: 'nanjiu',
age: 18
}
})
})
app.listen(3000, () => {console.log('服务已启动~')
})
而后把服务跑起来,再到 vue 我的项目中去调用
const getInfo = async () => {
try {const res = await axios.get('http://localhost:3000/api/getInfo')
console.log(res)
} catch(err) {console.log(err)
}
}
这时候你会发现接口调用跨域了
配置代理
接着咱们再来通过 webpack 配置代理解决跨域问题,因为咱们本地应用了webpack-dev-server
,所以咱们能够间接通过它来配置
// webpack.config.js
module.exports = {
// ...
devServer: {
hot: true,
open: true,
proxy: {'/api': 'http://localhost:3000'}
}
}
这个时候咱们的接口申请就失常了
因为篇幅问题,这篇文章就介绍到这里了,前面会接着更新 webpack
更多高级用法。
如果这篇文章有帮忙到你,❤️关注 + 点赞❤️激励一下作者,文章公众号首发,关注 前端南玖
第一工夫获取最新文章~