目前
vue3
官网推荐的工具链已经是vite
了,就算是要使用webpack
甚至是webpack5
,也可以直接使用vue-cli
。然而之所以写这个,可以当是webpack5
的一个学习文章。同时也是因为之前有个项目是在vue3
刚出来的时候使用vue-cli
(那是官网还是推荐使用webpack
,以及但是得版本为webpack4
)开发的,手动改成webpack5
,想记录下。
Webpack
是什么
本质上,webpack
是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles
,它们均为静态资源,用于展示你的内容。
这个话是官网的原话,官网地址:webpack 中文
核心内容
- Entry
- Output
- Loader
其作用是让
webpack
处理那些非 js, json 文件。由于webpack
自身只能试别 js, json,其他后缀的文件需要经过loader
处理,将他们转化成有效模块。loader
可以是同步的,也可以是异步的,支持链式调用。
- Plugins
loader
用于转换某些类型的模块,而插件则可以用于执行范围更广的任务:打包优化,资源管理,注入环境变量。plugin
会运行在webpack
的不同阶段,贯穿整个编译周期,目的在于解决loader
无法实现的其他事。
- Module
vue3 项目搭建
首先创建项目名称,以及下载相关的库。
# 创建项目
mkdir webpack-vue3
cd webpack-vue3
# 初始化 npm
npm init -y
# 下载 webpack
npm install webpack webpack-cli webpack-dev-server -D
# 下载 vue
npm install vue
# 下载 css 相关 loader
npm install css-loader style-loader vue-loader postcss-loader postcss autoprefixer -D
# 下载 vue 相关
npm install vue-loader @vue/compiler-sfc -D
# 下载 babel 相关
npm install babel-loader @babel/core @babel/preset-env -D
# 下载 plugin
npm install html-webpack-plugin -D
首先根目录创建 src
、public
文件夹,文件夹内容如下所示:
<!-- public > index.html -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div id="app"></div>
</body>
</html>
// src > main.js
import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App)
app.mount('#app')
<!-- src > App.vue -->
<template><div class="container"><p>Vue3 Project</p><p class="text">count: {{ count }}</p></div>
</template><script setup>
import { ref } from 'vue'
const count = ref(1)
</script><style>.text {color: blue;}
</style>
初始化 vue
单页面程序后,下面开始配置 webpack
,在根目录下创建 webpack.config.js
文件。
// . > webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')module.exports = {mode: 'development',entry: './src/main.js',output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist'),clean: true},devtool: 'source-map',devServer: {static: './dist',hot: true},module: {rules: [{test: /\.vue$/,use: 'vue-loader'},{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env']}}},{test: /\.css$/,use: ['style-loader', 'css-loader',{loader: 'postcss-loader',options: {postcssOptions: {plugins: [require('autoprefixer'), // 使用 autoprefixer 插件]}}}]},{test: /\.(png|jpg|gif|svg|jpeg)$/,type: 'asset/resource', // 使用 asset/resource 类型generator: {filename: 'public/images/[name].[contenthash:8][ext]', // 输出目录和文件名格式},}]},plugins: [new VueLoaderPlugin(),new HtmlWebpackPlugin({template: './public/index.html'})],resolve: {alias: {vue: 'vue/dist/vue.esm-bundler.js'},extensions: ['.js', '.vue', '.json']}
}
然后 package.json
配置脚本
"scripts": {"test": "echo \"Error: no test specified\" && exit 1","server": "webpack server","build": "webpack"},
完成到这一步,我们就可以在终端运行 npm run server
来跑起项目,也可以通过 npm run build
打包项目。这个只是一个简单的 webpack
配置,运行环境 mode
在正式打包的时候还要手动修改成 production
,以及 source-map
移除等。对于要正式上线的项目,这样的配置往往是不够的。
优化 webpack 配置
拆分配置,配置运行环境
首先,我们先对项目进行环境的划分。
npm install cross-env -D
下载 cross-env
库,用于设置环境变量。下载完后,修改 package.json
{"scripts": {"test": "echo \"Error: no test specified\" && exit 1","server": "webpack server --config build/webpack.dev.js","build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js","build:test": "cross-env NODE_ENV=test webpack --config build/webpack.prod.js"},
}
--config
后面是指定的配置文件,这个我们待会创建。build
这个指令是用来打包正式上线的,build:test
这个是用来测试打包效果的,用来展示打包量化工具等。这个你也可以用来用作 测试服的打包。就比如有些公司会分内测,外测以及正式服。你就可以不写 build:test
,直接用 inner
、outer
、formal
代替。
然后就是创建对应的配置文件了。根据上面的路径,在根目录创建 build
文件夹,里面创建 webpack.common.js
、webpack.dev.js
、webpack.prod.js
三个文件。
// build > webpack.common.jsconst path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')module.exports = {entry: path.resolve(__dirname, '../src/main.js'),output: {filename: 'public/js/bundle.js',path: path.resolve(__dirname, '../dist'),clean: true},module: {rules: [{test: /\.vue$/,use: 'vue-loader'},{test: /\.js$/,exclude: /node_modules/,use: 'babel-loader'},{test: /\.css$/,use: ['style-loader', 'css-loader', 'postcss-loader']},{test: /\.(png|jpg|gif|svg|jpeg)$/,type: 'asset/resource', // 使用 asset/resource 类型generator: {filename: 'public/images/[name].[contenthash:8][ext]', // 输出目录和文件名格式},}]},plugins: [new VueLoaderPlugin(),new HtmlWebpackPlugin({template: path.resolve(__dirname, '../public/index.html')}),],resolve: {alias: {'@': path.resolve(__dirname, '../src'),vue: 'vue/dist/vue.esm-bundler.js',},extensions: ['.js', '.vue', '.json']}
}
webpack.common.js
中的配置和 webpack.config.js
中的大体一致,移除了 devtool
和 devServer
的配置,其他的也只是写小修改,例如 output
的时候输出位置以及添加了@
别名。babel-loader
和 postcss-loader
的配置也提取出去了,这里要在根目录下创建 .babelrc
和 postcss.config.js
。
// . > babelrc
{"presets": ["@babel/preset-env"]
}
// . > postcss.config.js
module.exports = {plugins: [require('autoprefixer')]
}
接下来就是 webpack.dev.js
// build > webpack.dev.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')module.exports = merge(commonConfig, {mode: 'development',devtool: 'source-map',devServer: {static: '../dist',hot: true,open: true},
})
dev
配置就是 devtool
以及 devServer
了,然后通过 webpack-merge
将 common
和 dev
合并起来。
// build > webpack.prod.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')module.exports = merge(commonConfig, {mode: 'production'
})
prod
当前来看则是这样,相对简单。
添加 sass 预处理器
npm install sass sass-loader -D
然后在 webpack.common.js
的 module.rules
中添加 scss
的规则
// build > webpack.common.js
moudle.exports = {// ...module: {rules: {//...{test: /\.scss$/,use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']}}}
}
然后 .vue
中就能使用 lang="scss"
以及引入 .scss
文件。
拆分 chunk
拆分之前,我们先安装一下 webpack-bundle-analyzer
,它是一个用于分析 Webpack 打包结果的工具。它可以生成一个可视化的报告,帮助开发者理解各个模块的大小以及它们在最终打包文件中的占比。
npm i -D webpack-bundle-analyzer
然后我们在 webpack.prod.js
中配置一下,这里要用到我们前面做的环境区分。我们只在 build:test
的时候才使用 webpack-bundle-analyzer
。
// build > webpack.prod.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')const ENV = process.env.NODE_ENV
console.log('运行环境:' + ENV)function getPlugins() {const plugins = []if (ENV === 'test') {plugins.push(new BundleAnalyzerPlugin({analyzerMode: 'static', // 生成静态报告openAnalyzer: true,}))}return plugins
}module.exports = merge(commonConfig, {mode: 'production',plugins: getPlugins()
})
这时候我们执行 npm run build:test
就可以看到打包出的 bundle.js
大小以及分布图。
接下来就是对 chunk 拆分了
通过 optimization.splitChunks
来进行拆分
// build > webpack.prod.js
module.exports = merge(commonConfig, {mode: 'production',plugins: getPlugins(),optimization: {splitChunks: {cacheGroups: {defaultVendors: {name: 'vendor',test: /[\\/]node_modules[\\/]/,priority: 1,chunks: 'all',minChunks: 1,minSize: 0},common: {name: 'common',minChunks: 2,priority: 0,chunks: 'all',reuseExistingChunk: true,minChunks: 2,minSize: 0}}}},
})
配置完这个后,如果你直接执行 build
测试的话,会报错。因为这里你 output
的配置中的 filename
是定死的文件名。这里需要修改一下 webpack.common.js
中的配置。
// build > webpack.common.js
module.exports = {entry: path.resolve(__dirname, '../src/main.js'),output: {// 修改部分filename: 'public/js/[name].[contenthash:8].js',path: path.resolve(__dirname, '../dist'),clean: true},// ... 其他配置
}
这时在进行 npm run build:test
,就可以发现会打包出两个 js 文件
。同时可以看到解析图也发生了改变。
对于某个路由的拆分
路由的拆分比较简单,只需要动态引入组件的时候使用 webpackChunkName
。
首先实现路由环境。
npm i vue-router
然后在 src
下创建 routers
和 views
文件夹。一个用来存路由配置,一个用来存具体的路由组件。
// src > routers > index.js
import { createWebHashHistory, createRouter } from "vue-router"const Home = () => import(/* webpackChunkName: "home" */ '@/views/home/index.vue')
const Detail = () => import(/* webpackChunkName: "detail" */ '@/views/detail/index.vue')const routes = [{path: '/',component: Home},{path: '/detail',component: Detail}
]const router = createRouter({history: createWebHashHistory(),routes
})export default router// src > main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from "./routers"const app = createApp(App)
app.use(router).mount('#app')
<!-- src > views > detail > index.vue -->
<template><div class="detail-container">detail{{ count }}</div>
</template><script setup>
const count = ref(1)
</script><style lang="scss">
.detail-container {color: yellow;
}
</style><!-- src > views > home > index.vue -->
<template><div class="main-container">home{{ count }}</div>
</template><script setup>
const count = ref(1)
</script><style lang="scss">
.main-container {color: blue;
}
</style><!-- src > App.vue -->
<template><div class="container"><p>Vue3 Project</p><p class="text">count: {{ count }}</p><!-- 新增部分 --><RouterView></RouterView></div>
</template><script setup>
// 新增部分
import { RouterView } from 'vue-router'
import { ref } from 'vue'
const count = ref(1)
</script><style lang="scss">
.container {.text {color: blue;}
}
</style>
运行 build:test
会发现,这时候会多出 detail.[base:8]
和 home.[base:8]
。即说明拆分成功了。
提取 css
目前我们打包出来的都是 js
文件,样式也是在 js
文件中的。这里我们需要 mini-css-extract-plugin
库来将 css
样式从 javascrip
中提取出来。
npm install mini-css-extract-plugin -D
配置如下
// build > webpack.common.js
// ... 其他已有引用
const MiniCssExtractPlugin = require('mini-css-extract-plugin')module.exports = {module: {rules: [// ... 其他loader// 修改部分{test: /\.css$/,use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']},{test: /\.scss$/,use: [MiniCssExtractPlugin.loader,'css-loader','postcss-loader','sass-loader']},]},plugins: [// ... 其他 pluginsnew MiniCssExtractPlugin({filename: 'public/css/[name].[contenthash:8].css'}),]
}
配置完成后打包,就会生成对应的 css 文件。
添加缓存
webpack5
自己提供了 cache
功能
// build > webpack.prod.js
module.exports = {cache: {type: 'filesystem'}
}
打包后在 node_modules
中可以看到 .cache
文件夹。
其他配置
利用 progress-bar-webpack-plugin
库,显示打包进度。
// build > webpack.prod.js
// ... 其他引入
const ProgressBarPlugin = require('progress-bar-webpack-plugin')function getPlugins() {const plugins = [new ProgressBarPlugin({format: ` :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`})]if (ENV === 'test') {// ... test 配置}return plugins
}
同时可以关闭自带的显示信息
module.exports = {// ...stats: {// 显示详细信息all: false,assets: true, // 显示打包的文件timings: true, // 显示构建时间modules: false,chunks: false,version: true, // 显示 Webpack 版本errors: true, // 显示错误},
}
最终打包的时候可以看到具体进度以及耗时。