1、序言
Webpack属于构建工具,可以将开发者代码转化成浏览器能识别的代码,让开发者专注代码实现,不用过多关注浏览器兼容性问题。
Webpack常见功能:
-
模块打包:Webpack 可以将项目中的所有模块(包括 JavaScript、CSS、图片等)打包成一个或多个 bundle。这有助于减少 HTTP 请求,提高页面加载速度。
-
代码分割:Webpack 支持代码分割,可以将不同功能模块的代码分割成不同的 bundle,按需加载,从而提高应用的加载速度。
-
模块热替换(HMR):Webpack 支持模块热替换,这意味着在开发过程中,当代码发生变化时,Webpack 可以自动重新打包并更新浏览器中的代码,而不需要刷新页面
-
Loader **翻译器:**Loader 允许你在打包过程中预处理文件,例如将less/scss转换成css、jsx/typescript转换成js、图片转换为 Base64 编码等等
-
开发服务器:Webpack 提供了一个开发服务器,可以在开发过程中提供快速的实时重新加载功能
-
树摇(Tree Shaking):Webpack 可以移除未引用的代码,这有助于减少最终打包文件的大小
-
...
2、配置
2.1、初始化项目
bash
#初始化仓库
npm init
#安装webpack、webpack-cli
npm i webpack
npm i webpack-cli
根目录下新建webpack.config.js:
javascript
module.exports = {
// 入口:entry:String | Object
entry: '',
// 出口
output: {
},
// 各种loader配置
module: {},
// 各种插件配置
plugins: [],
// 优化配置
optimization: {},
// 开发服务器配置
devServer: {},
// development:开发模式,production:生产模式
mode: 'development',
}
文件目录
math.js:加减乘除函数
main.js:
index.html
运行发现:浏览器不能识别esmodule,因此需要webpack打包构建成浏览器能识别的语言
2.2、配置出入口
javascript
module.exports = {
// 入口:entry:String | Object
entry: path.resolve(__dirname, 'src/main.js'),
// 出口
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
}
bash
npx webpack --config webpack.config.js
打包成功:
所有的打包文件合在一起,变成定义的输出的文件名称main.js
修改public下的index.html的script标签地址为dist/main.js
待完善的点:
- 每次重新打包,上次的打包结果会干扰本次打包结果
解决:
- webpack5:output: {clean: true}
- webpack4:安装clean-webpack-plugin插件
bash
npm i clean-webpack-plugin
javascript
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
- 每次还要手动修改public/index.html的对应的script标签的引用地址
bash
npm i html-webpack-plugin
javascript
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
// 以 public/index.html 为模板创建文件
new HtmlWebpackPlugin({
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "public/index.html"),
}),
],
};
- 每次都要在cmd控制台输入一长串的npx webpack --config webpack.config.js
在package.json配置命令
- 每次打包输出结果都是main.js,浏览器认为文件没发生改变,会走浏览器缓存,导致更新部署项目不生效
javascript
output:{
// 根据文件内容生成hash
filename: '[contenthash].js'
}
2.3、开发服务器
解决:每次修改文件内容都要重新打包即手动执行npm run build操作,实现热更新:所见即所得;
原理:npm run build 打包至磁盘,webpack serve开发服务器会打包至内存
bash
npm i webpack-dev-server -D
webpack.config.js
javascript
module.exports = {
// 开发服务器
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
},
}
package.json
javascript
"scripts": {
"dev": "npx webpack serve --config webpack.config.js --mode=development",
"build": "npx webpack --config webpack.config.js --mode=production"
}
tips:执行npm run dev会报错误,解决:升级node版本至18以上即可
2.4、区分环境
生产环境webpack5帮我们做了优化,如压缩js、html、开启tree-shaking,开发环境配置的devServer才生效;
写2套webpack.config.js问题
- 重复配置多
- 心智负担重
因此需要区分webpack环境
前面在package.json里面的script脚本已经设置了--mode=production 或者--mode=development了,然后发现process.env.NODE_ENV打印的结果始终是undefined,这里使用cross-env进行环境切换;
bash
npm i cross-env -D
package.json
javascript
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve --config webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
},
webpack.config.js中
javascript
// 判断是否是生成环境
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
// development:开发模式,production:生产模式
mode: isProduction ? 'production' : 'development',
// 入口:entry:String | Object
entry: path.resolve(__dirname, 'src/main.js'),
// 出口
output: {
filename: isProduction ? 'static/js/[contenthash:10].js' : 'static/js/[name].js',
path: isProduction ? path.resolve(__dirname, 'dist') : undefined,
clean: true
},
// 各种loader配置
module: {},
// 各种插件配置
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html')
})
],
// 优化配置
optimization: {},
// 开发服务器配置
devServer: {
port: 8000,
host: 'localhost',
open: true
},
}
2.5、处理样式文件
问题
- js中引入css文件报错
- 最后打包的静态资源不包含任何样式文件,即css代码不生效
bash
npm i css-loader style-loader less less-loader -D
css-loader
:负责将 Css 文件编译成 Webpack 能识别的模块
style-loader
:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容less-loader:将less转变成css
javascript
module.exports = {
module: {
rules: [
{
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: ["style-loader", "css-loader"]
}
]
},
}
2.5、处理图片资源
当图片小于一定大小时转变成base64,这样可以减少http请求
webpack5:
javascript
module.exports = {
// 各种loader配置
module: {
rules: [
// 处理图片资源
{
test: /\.(png|jpe?g|gif|webp)$/,
type: 'asset',
parser: {
// 小于10kb
dataUrlCondition: {
maxSize: 10 * 1024
}
}
}
]
},
}
webpack4:
bash
npm i url-loader file-loader
javascript
module.exports = {
// 各种loader配置
module: {
rules: [
{
test: /\.(png|jpe?g|gif)$/i, // 匹配png, jpg, jpeg, gif图片文件
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024, // 小于10KB的图片将被转换成Base64
fallback: 'file-loader', // 大于10KB的图片使用file-loader处理
outputPath: 'images/' // 输出路径
}
}
]
}
]
},
}
2.6、代码压缩
生产环境webpack5已经帮我们压缩js、html了,因此需要手动压缩css;
bash
npm i css-minimizer-webpack-plugin
javascript
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
// 优化配置
optimization: {
minimize: true,
minimizer: [
// css压缩
new CssMinimizerPlugin(),
],
},
}
2.7、兼容性处理
2.7.1、css
bash
npm i postcss-loader postcss postcss-preset-env
webpack.config.js
javascript
module.exports = {
// 各种loader配置
module: {
rules: [
// 处理css后缀文件
{
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: ["style-loader", "css-loader",
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env"
]
]
}
}
}
]
},
// 处理less后缀文件
{
test: /\.less$/,
use: ["style-loader", "css-loader", {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env"
]
]
}
}
}, "less-loader"]
}
]
},
}
package.json
javascript
{
// 其他省略
"browserslist": ["last 2 version", "> 1%", "not dead"]
}
这样对低版本浏览器的css3语法就会加前缀,如-webkit
2.7.2、js
js兼容性处理主要使用babel,babel的主要作用如下
- 将es6转化成es5
- 将jsx语法转成js
bash
npm i babel-loader @babel/core @babel/preset-env core-js -D
根目录下新建babel.config.js
javascript
module.exports = {
// 智能预设:能够编译ES6语法
presets: [
[
"@babel/preset-env",
// 按需加载core-js的polyfill
{
// false: 不对当前的JS处理做 polyfill 的填充
// usage: 依据用户源代码当中所使用到的新语法进行填充
// entry: 依据我们当前筛选出来的浏览器.browserslistrc决定填充什么
useBuiltIns: "usage",
corejs: { version: "3", proposals: true }
},
],
],
}
webpack.config.js
javascript
module.exports = {
// 各种loader配置
module: {
rules: [
// 处理js后缀文件
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules代码不编译
use: [
{
loader: "babel-loader",
options: {
// Babel 在每个文件都插入了辅助代码,使代码体积过大,下面插件能减少代码体积
plugins: ["@babel/plugin-transform-runtime"],
}
}
]
},
]
},
}
2.8、提取css
问题:将网络调成3g,然后刷新页面,发现白屏
解决:将css提取到一个文件,通过 link 标签引入
bash
npm i mini-css-extract-plugin -D
webpack.config.js
将style-loader替换成MiniCssExtractPlugin.loader
javascript
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
// 各种loader配置
module: {
rules: [
// 处理css后缀文件
{
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: [MiniCssExtractPlugin.loader, "css-loader",
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env"
]
]
}
}
}
]
},
// 处理less后缀文件
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env"
]
]
}
}
}, "less-loader"]
},
]
},
// 各种插件配置
plugins: [
// 提取css成单独文件
new MiniCssExtractPlugin(),
],
}
2.9、PWA
问题:一旦断网,应用无法打开
PWA:渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。内部通过 Service Workers 技术实现的。
bash
npm i workbox-webpack-plugin -D
webpack.config.js
javascript
const WorkboxPlugin = require("workbox-webpack-plugin");
module.exports = {
// 各种插件配置
plugins: [
// pwa
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何"旧的" ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
],
}
入口文件:main.js
javascript
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js")
.then((registration) => {
console.log("SW registered: ", registration);
})
.catch((registrationError) => {
console.log("SW registration failed: ", registrationError);
});
});
}
2.10、Code Split
问题:打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。
解决:我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
2.10.1、多入口
javascript
const path = require('path')
module.exports = {
entry: {
main: './src/main.js',
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
clear: true,
},
}
结果:dist/js目录下有main.js、app.js文件
2.10.2、多入口优化
问题:如果main.js、app.js都引入math.js,造成math.js被重复打包
解决:
javascript
const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: './src/main.js',
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
clear: true,
},
optimization: {
splitChunks: {
chunks: "all", // 对所有模块都进行分割
}
}
}
2.10.3、按需加载
按需加载:动态加载某个chunk
main.js:点击box元素,动态导入vue
javascript
document.querySelector('.box').addEventListener('click', () => {
import(/* webpackChunkName: "vue" */ 'vue').then((res)=>{
console.log('res:', res)
})
})
webpack.config.js
javascript
const path = require('path')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: './src/main.js',
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
clear: true,
},
plugins: [
// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和目录
filename: "static/css/[name].css",
chunkFilename: "static/css/[name].chunk.css",
}),
],
optimization: {
splitChunks: {
chunks: "all", // 对所有模块都进行分割
}
}
}
实际应用:vue-router的按需加载
- 路由匹配
javascriptconst routes = [ { path: '/test', component: () => import(/* webpackChunkName: "test" */'@/views/test.vue') } ]
- 加载相应的webpackChunkName
2.10.4、按需加载优化
问题:如果动态导入的chunk非常大或者网络极差,就会有明显卡顿的问题
解决:提前加载想要加载的资源
Preload:告诉浏览器立即加载资源;加载优先级高;兼容性好一些;只能加载当前页面需要使用的资源;
Prefetch:告诉浏览器在空闲时才开始加载资源;加载优先级低;兼容性差一些;可以加载当前页面资源,也可以加载下一个页面需要使用的资源;
bash
npm i @vue/preload-webpack-plugin -D
javascript
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
module.exports = {
plugins: [
new PreloadWebpackPlugin({
rel: "preload", // preload兼容性更好
as: "script",
// rel: 'prefetch' // prefetch兼容性更差
}),
]
}
打包结果:
2.11、 提升构建速度
2.11.1、include/exclude
node_modules下的依赖无须下载直接使用,babel不需要对他们进行处理
javascript
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, "../src"),
exclude: /node_modules/, // 排除node_modules代码不编译
use: [
{
loader: "babel-loader",
options: {
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
]
},
]
}
}
2.11.2、oneof
打包时每个文件都会经过所有 loader 处理,尽管有test
正则原因实际没有处理上,但是都要过一遍;比较慢。oneof只要匹配上一个 loader, 剩下的就不匹配了
javascript
const path = require('path')
module.exports = {
module: {
rules: [
{
oneOf: [
...其它loader
// 处理js
{
test: /\.js$/,
// exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
use: [
{
loader: 'thread-loader',
options: {
workers: threads
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
]
}
]
}
]
},
}
2.11.3、cache
作用:缓存之前的Babel 编译结果,只打包变化部分的内容
javascript
const path = require('path')
module.exports = {
// 各种loader配置
module: {
rules: [
// 处理js后缀文件
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules代码不编译
use: [
{
loader: "babel-loader",
options: {
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
}
}
]
},
]
},
}
2.11.4、Thead
问题:项目越来越庞大时,打包速度越来越慢
解决:开启多进程打包
tips:小型项目使用Thread提升打包速度不显著,反而会延长打包时间,大型项目效果会更加显著
bash
npm i thread-loader terser-webpack-plugin -D
webpack.config.js
javascript
const path = require('path')
const os = require("os");
// cpu核数
const threads = os.cpus().length;
// js压缩工具,只在生产环境生效
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
module: {
rules: [
{
oneOf: [
// 处理js
{
test: /\.js$/,
// exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
use: [
{
loader: 'thread-loader',
options: {
workers: threads
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
]
}
]
}
]
},
// 压缩
optimization: {
minimize: true,
minimizer: [
// js压缩
new TerserPlugin({
parallel: threads,
}),
],
splitChunks: {
chunks: 'all',
},
},
}
3、完整配置
webpack.config.js
javascript
const path = require('path')
const os = require("os");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
// 判断是否是生成环境
const isProduction = process.env.NODE_ENV === 'production'
// cpu核数
const threads = os.cpus().length;
module.exports = {
entry: path.resolve(__dirname, 'src/main.js'),
output: {
filename: isProduction ? 'static/js/[contenthash:10].js' : 'static/js/[name].js',
path: isProduction ? path.resolve(__dirname, 'dist') : undefined,
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext]',
clean: true
},
module: {
rules: [
{
oneOf: [
// 处理css后缀文件
{
test: /\.css$/,
// 从右到左、从下到上使用loader
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env"
]
]
}
}
}
]
},
// 处理less后缀文件
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env"
]
]
}
}
},
'less-loader'
]
},
// 处理图片
{
test: /\.(png|svg|jpg|jpeg|gif|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 20 * 1024
}
}
},
// 处理js
{
test: /\.js$/,
// exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
use: [
{
loader: 'thread-loader',
options: {
workers: threads
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
]
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
// 自动帮我们引入打包生成的js资源
template: path.resolve(__dirname, 'public/index.html'),
}),
// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和目录
filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
}),
new PreloadWebpackPlugin({
rel: 'preload',
as: 'script'
}),
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何"旧的" ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
],
// 压缩
optimization: {
minimize: true,
minimizer: [
// js压缩
new TerserPlugin({
parallel: threads,
}),
// css压缩
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: 'all',
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`
},
},
devServer: {
port: 3000,
host: 'localhost',
open: true,
hot: true,
compress: true
},
mode: isProduction ? 'production' : 'development'
}
babel.config.js
javascript
module.exports = {
presets: [
["@babel/preset-env",
// 按需加载core-js的polyfill
{
useBuiltIns: "usage",
corejs: {
version: 3
},
},]
],
}
package.json
javascript
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve --config webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"vue": "^3.5.12",
},
"devDependencies": {
"core-js": "^3.38.1",
"css-loader": "^7.1.2",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"style-loader": "^4.0.0",
"terser-webpack-plugin": "^5.3.10",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@vue/preload-webpack-plugin": "^2.0.0",
"babel-loader": "^9.2.1",
"cross-env": "^7.0.3",
"css-minimizer-webpack-plugin": "^7.0.0",
"html-webpack-plugin": "^5.6.3",
"mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.47",
"postcss-loader": "^8.1.1",
"postcss-preset-env": "^10.0.8",
"thread-loader": "^4.0.4",
"webpack-dev-server": "^5.1.0",
"workbox-webpack-plugin": "^7.1.0"
},
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
}