1. 开篇
1.1 Vue CLI搭建项目
vue create <project-name> # Vue CLI >= 3
# vue init <template-name> <project-name> (vue CLI 2.x)
Vue CLI可以生成vue.js+webpack的项目模板,用于快速搭建vue.js项目。
Vue CLI 2.x 对应命令是从vuejs-templates/webpack仓库中拉取模板,生成项目文件。
1.2 Vue CLI内置了哪些webpack的功能
- ES6+代码转换为ES5代码(处理JS代码兼容性问题)
- 解析scss/sass/less/stylus资源为css资源
- 处理图片、字体等资源文件
- 自动给css代码添加浏览器厂商前缀(处理CSS兼容性问题)
- 代码热更新
- 资源预加载
- 每次构建代码之前清除之前生成的代码
- 定义环境变量
- 区分开发环境和生产环境打包
- ... 下面的命令查看Vue CLI创建的项目对应的webpack配置文件:
vue-cli-service inspect --mode development >> webpack.dev.js # 开发环境
vue-cli-service inspect --mode production >> webpack.prod.js # 生产环境
2. 不同模式下的webpack配置
webpack提供了mode配置选项,用来告知webpack使用相应模式的内置优化,我们也可以根据模式的不同编写不同的配置文件,提供特定模式下的优化。
2.1 开发模式和生产模式的区别
开发模式和生产模式的区别:
- 内置优化的不同:参考链接
- 构建目标不同:
- 开发环境模式下,我们需要强大的、具有实时重新加载或热模块替换能力和本地服务器。
- 在生产模式下,我们更关注更小的包和资源的优化。
2.2 基础配置
2.2.1 .vue文件相关依赖
安装:
npm i vue -S
npm i vue-loader -D
npm i @vue/compiler-sfc -D
vue-loader:解析.vue文件,解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML模板template,再分别把他们交给对应的loader去处理。参考链接
@vue/compiler-sfc:将解析完的vue单页面组件(sfc)编译为js。参考链接
配置:
const { VueLoaderPlugin } = require('vue-loader');
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
];
}
plugins: [
new VueLoaderPlugin(),
]
2.2.2 处理样式资源
处理CSS、Less、Sass、Scss和Styl样式资源。css代码提取成单独文件。css兼容性处理。css压缩。
多个loader配合使用时,处理的顺序是从下到上,从右到左。
安装:
npm i css-loader style-loader less-loader sass-loader sass stylus-loader mini-css-extract-plugin postcss-loader postcss postcss-preset-env css-minimizer-webpack-plugin -D
- css-loader:分析各个css文件的关系,把各个css文件合并成一段css代码
- style-loader:动态创建一个style标签,里面放经过css-loader处理得到的css代码
- less-loader:把less代码编译成css代码
- sass-loader:加载sass/scss文件并把它们编译成css代码
- stylus-loader: 把stylus文件编译成css文件
- mini-css-extract-plugin:把css代码提取成单独文件
- css-minimizer-webpack-plugin:优化和压缩css
配置:
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtraPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env' // 能解决大多数样式兼容性问题
]
}
}
},
preProcessor
].filter(Boolean);
};
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use: getStyleLoaders()
},
{
test: /\.less$/,
use: getStyleLoaders('less-loader')
}
]
},
plugins: [
new MiniCssExtraPlugin({
filename: 'css/index.css'
}),
new CssMinimizerPlugin()
]
}
控制兼容性:
在package.json文件中,添加browserslist控制兼容性做到什么程度。browserslist 文档
{
// 其他省略
"browserslist": ["last 2 version", "> 1%", "not dead"]
}
2.2.3 处理图片资源
在webpack4中,处理图片资源是通过file-loader和url-loader来处理的,webpack5已经把这两个loader功能内置到webpack中了。
优化:将小于某个大小的图片转化成data URI形式(base64格式)
- 优点:减少请求数量
- 缺点:体积变大
配置:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
},
generator: {
filename: 'static/images/[hash:8][ext][query]' // 输出到指定文件夹下
}
}
}
]
}
}
2.2.5 处理字体图标资源
配置:
module.exports = {
...
module: {
rulse: [
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:8][ext][query]",
},
}
]
}
}
type: "asset/resource"和type: "asset"的区别:
type: "asset/resource"相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理type: "asset"相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式
2.2.6 处理js资源
- 针对代码格式,使用eslint完成
- js兼容性处理,使用babel完成
Eslint
可组装的JavaScript和JSX检查工具。
安装:
npm i eslint-webpack-plugin eslint -D
生成eslint配置文件:
npx eslint --init
根据提示选择配置 生成的
.eslintrc.json文件:
{
"env": {
"browser": true, // 可以使用window对象
"es2021": true // 可以使用es2021的语法
},
// 继承其他规则
"extends": [
"eslint:recommended", // 使用这个集合的代码规范
"plugin:vue/essential"
],
// 解析选项
"parserOptions": {
"ecmaVersion": "latest", //ES 语法版本
"sourceType": "module" // ES 模块化
},
// 引用额外的rules
"plugins": [
"vue"
],
// 具体规则,可能会覆盖extends继承的规则(位置靠后的覆盖前面的)
"rules": {
}
}
Babel
主要作用:ES6+语法编写的代码转换为向后兼容的JS语法,以便能够运行在当前和旧版本的浏览器或其他环境中
安装:
npm i babel-loader @babel/core @babel/preset-env -D
配置:
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
exclude: /mode_modules/,
loader: 'babel-loader'
}
]
}
}
2.2.7 处理html资源
安装:
npm i html-webpack-plugin -D
配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'index.html'),
filename: 'index.html'
})
]
}
2.2.8 自动清除上次的打包文件
配置:
module.exports = {
output: {
...
clean: true // 自动将上次打包目录资源清空
}
}
2.2.9 开启服务器&自动化
安装:
npm i webpack-dev-server -D
配置:
module.exports = {
...
devServer: {
host: 'localhost', //启动服务器域名
port: 3001, // 启动服务器端口号
open: true // 自动打开浏览器
}
}
运行脚本:
npx webpack serve
2.3 优化配置
2.3.1 提升开发体验
SourceMap
SourceMap(源代码映射)是一个用来生成源代码和构建后代码一一映射的方案。它会生成一个xxx.map文件,里面包含源代码和构建后代码每一行、每一列的映射关系。让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。
开发模式配置:
module.exports = {
devtool: 'cheap-module-source-map'
}
- 优点:打包编译速度快,只包含行映射
- 缺点:没有列映射
生产模式配置:
module.exports = {
devtool: 'source-map'
}
- 优点:包含行/列映射
- 缺点:打包编译速度更慢
2.3.2 提升打包构建速度
OneOf
打包的时候,每个文件都会经过所有loader处理,因为test正则匹配原因,实际上可能只有一个loader处理。OneOf设置每个文件只匹配一个loader,剩下的就不匹配了。
配置:
module.exports = {
module: {
rules: [
{
OneOf: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
...
]
}
]
}
}
HotModuleReplaceMent
HotModuleReplaceMent(HMR/模块热替换):在程序运行时,替换、添加或删除模块,而无需重新加载整个页面。
配置:
module.exports = {
// 其他省略
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
},
};
html,css代码这时已经支持HMR功能了,但是js代码不支持。
js配置:
// 判断是否支持HMR功能
if (module.hot) {
module.hot.accept("./js/tab.js", function () {
console.log('我现在已经支持HMR功能啦!')
});
}
vue代码的HMR支持由vue-loader处理。
Cache
每次打包js文件时,都要经过eslint和babel编译,主要缓存eslint检查结果和babel编译结果。 配置:
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, '../src'),
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false // 缓存文件不要压缩
}
}
]
},
plugins: [
new ESlintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
exclude: 'node_modules',
cache: true, // 开启缓存
cacheLocation: path.resolve(
__dirname,
'../node_modules/.cache/.eslintcache'
)
})
]
}
2.3.3 减少代码体积
Tree Shaking
Tree Shaking(摇树优化)指的是一种通过移除多于代码,来优化打包体积的,生产环境默认开启。我们也可以自己配置。
// webpack.config.js
module.exports = {
usedExports: true,
minimize: true
}
// package.json
"sideEffects": [
"*.css",
"*.less",
"*.vue"
]
Babel
Babel为编译的每个文件都插入了辅助代码,会使得代码体积变大。
@babel/plugin-transform-runtime是一个可以重用babel辅助代码的插件,可以减少代码体积大小。
安装:
npm install --save-dev @babel/plugin-transform-runtime
配置:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: ['@babel/plugin-transform-runtime'] // 减少代码体积
}
}
]
}
]
}
}
Scope Hoisting
Scope Hoisting 又称为“作用域提升”,可以让webpack打包出来的代码文件更小、运行的更快。
原理:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。因此,只有那些被引用了一次的模块才能被合并。
2.3.4 优化代码运行性能
Code Split
对于单入口文件来说,webpack打包代码时会把所有js文件打包到一个文件中,体积太大。如果我们只渲染首页,就应该只加载首页的js文件,其他文件不应该加载。
配置:
module.exports = {
optimization: {
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\/]node_modules[\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
}
}
2.4 webpack.common.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env' // 能解决大多数样式兼容性问题
]
}
}
},
preProcessor
].filter(Boolean);
};
module.exports = {
entry: './src/main.js',
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
},
{
oneOf: [
{
test: /\.css$/,
use: getStyleLoaders()
},
{
test: /\.less$/,
use: getStyleLoaders('less-loader')
},
{
test: /\.(png|jpe?g|gif|webp)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
generator: {
filename: 'static/images/[hash:8][ext][query]'
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
plugins: ['@babel/plugin-transform-runtime']
}
}
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new MiniCssExtractPlugin({
filename: 'css/[name].css'
})
],
resolve: {
alias: {
'@': '../src',
vue$: 'vue/dist/vue.runtime.esm.js'
},
extensions: [
'.mjs',
'.js',
'.jsx',
'.vue',
'.json',
'.wasm'
],
modules: [
'node_modules'
]
}
}
2.5 webpack.dev.js
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
// devtool: 'cheap-source-map',
devtool: 'cheap-module-source-map',
entry: {
index: './src/main.js'
},
output: {
path: undefined,
filename: 'js/[name].js',
publicPath: '/',
chunkFilename: 'js/[name].js'
},
devServer: {
hot: true,
port: 3000
},
optimization: {
usedExports: false
}
});
运行脚本:
"dev": "webpack serve --config ./build/webpack.dev.js"
2.6 webpack.prod.js
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
// devtool: 'source-map',
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, '../dist'),
clean: true
},
plugins: [
new CssMinimizerPlugin()
],
optimization: {
minimize: true,
usedExports: true,
splitChunks: {
chunks: 'all'
}
}
});
运行脚本:
"build": "webpack --config ./build/webpack.prod.js"