Vue2 项目的 Webpack 配置
Webpack 安装
安装 webpack,目前是 webpack5。
npm install -D webpack webpack-cli
创建 webpack 配置文件。在项目跟目录下创建文件夹:
- build
- webpack.base.conf.js
- webpack.dev.conf.js
- webpack.prod.conf.js
安装 webpack-merge 合并配置文件参数
npm install -D webpack-merge
安装开发环境服务器
npm install -D webpack-dev-server
在 package.json 配置启动脚本,要在脚本传参,需要兼容不同的平台,还要引入一个插件
npm install -D cross-env
"scripts": {
"start": "npm run dev",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.conf.js",
"dev": "cross-env NODE_ENV=development webpack serve --config ./build/webpack.dev.conf.js"
}
基础配置
- entry:入口文件,webpack 的功能就是将入口文件引用的文件找到,全部塞到入口文件里,因此默认情况,以及没有异步的情况下,打包出来只有一个js文件。
- output:输出配置
- path:打包结果的目录
- publicPath:资源的公共路径,一般用于配置将项目部署到子目录。在 html 中引用打包结果的资源时,会在路径前面加上 publicPath。
- filename:打包结果的文件名称
- chunkFilename:打包结果异步模块的文件名称
- module.rules:非js文件的规则配置,webpack 默认只能解析 js 文件。loader 就是用来解析各种非js文件,顺序是从后往前。
- webpack5 用 type = 'asset/source' | 'asset/inline' | 'asset/resource' 来代替原来的 raw-loader,url-loader 和 file-loader。type = asset 相当于'asset/inline' | 'asset/resource'的结合,即 file-loader 的功能。
- resolve.extensions:文件名扩展,可以省略引用文件的后缀名
- resolve.alias:路径别名
// webpack.base.conf.js
const path = require('path')
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: isProd ? './' : '/',
filename: 'js/[name]_[chunkhash:8].js',
chunkFilename: 'js/[name]_[chunkhash:8].js'
},
module: {
rules: [
{
test: /\.(eot|ttf|otf|woff2?)(\?\S*)?$/,
type: 'asset',
parser: {
dataUrlCondition: { maxSize: 1024 * 5 }
},
generator: {
filename: 'font/[name]_[hash:8][ext]'
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: { maxSize: 1024 * 5 }
},
generator: {
filename: 'images/[name]_[hash:8][ext]'
}
}
]
},
resolve: {
extensions: ['.js', '.vue', '.scss', '.css', '.json'],
alias: {
'src': path.resolve(__dirname, '../src'),
'views': path.resolve(__dirname, '../src/views'),
'components': path.resolve(__dirname, '../src/components'),
'directives': path.resolve(__dirname, '../src/directives'),
'filters': path.resolve(__dirname, '../src/filters'),
'images': path.resolve(__dirname, '../src/images'),
'modules': path.resolve(__dirname, '../src/modules'),
'style': path.resolve(__dirname, '../src/style'),
'utils': path.resolve(__dirname, '../src/utils'),
}
}
}
html 的配置
通过 html-webpack-plugin 可以自动生成一个模板或者指定一个html作为模板。webpack 会将同步模块注入到这个模板。在生产环境时,还可以压缩这个 html。
npm install -D html-webpack-plugin
// webpack.base.conf.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: 'xxx系统',
template: 'index.html',
...(isProd ? {
minify: {
removeComments: true,
collapseWhitespace: true
}
}, {})
})
]
}
babel 配置
babel 用来将 es6+ 的语法转换成低版本的js,使之可以在低版本的浏览器上运行。
npm install -D babel-loader @babel/preset-env @babel/core
npm install --save core-js@3
// webpack.base.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory=true'],
include: path.resolve(__dirname, '../src'),
exclude: /(node_modules)/
},
]
}
}
在根目录新增 .babelrc 文件。以下配置就能实现 polyfill 的按需引入。
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
另一个实现 polyfill 按需引入的方案是 @babel/plugin-transform-runtime + @babel/runtime + @babel/runtime-corejs3。
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime @babel/runtime-corejs3
// .babelrc
{
"plugins": [
"@babel/plugin-transform-runtime",
{
corejs: 3
}
]
}
vue 的配置
npm install -D vue-loader vue-style-loader
vue-loader 用来处理 .vue 文件。v15后的配置更加简单,原先需要在 vue-loader 的参数中指定 loader 处理 .vue 文件内部的脚本和样式,现在只需要引入 VueLoaderPlugin 这个插件即可,处理这些内容的规则与相应后缀名的文件处理相同。
// webpack.base.conf.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
// ...
{
test: /\.vue$/,
include: path.resolve(__dirname, '../src'),
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
// ...
}
}
}
同时将配置中使用 style-loader 的地方替换成 vue-style-loader。
开发环境配置
通过 webpack-merge 可以合并基础配置。
- mode: 指定当前环境,设置为 development 或 production 时都能开启一些默认功能。
- target: 设置该参数是为了处理 HMR 失效的问题
- devtool: 让控制台的信息映射到真实代码,因为实际上运行的代码时打包处理过的。多个值可选,看需要的信息的详细程度。
- devServer:开发服务器配置,相当于 express 和 webpack-dev-middleware 的结合
- port:服务运行的端口
- hot:是否使用 HMR
- compress:是否开启 gzip
- open:运行时是否自动打开窗口
- proxy:代理
// webpack.dev.conf.js
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
module.exports = merge(baseConfig, {
mode: 'development',
target: 'web',
devtool: 'eval-cheap-module-source-map',
devServer: {
port: 9090,
hot: true,
compress: true,
open: true,
proxy: {
'/api/': {
target: 'http://example.com:9090',
changeOrigin: true
}
}
}
})
eslint 配置
eslint 用于语法检查。eslint-loader 即将被 eslint-webpack-plugin 替代。
npm install -D eslint eslint-loader @babel/eslint-parser eslint-plugin-vue
// webpack.dev.conf
module.exports = {
module: {
rules: {
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
include: path.resolve(__dirname, '../src'),
}
}
}
}
在项目根目录新增 .eslintrc.js 配置文件。
// .eslintrc.js
module.exports = {
root: true,
env: {
'browser': true
},
parserOptions: {
parser: '@babel/eslint-parser'
},
extends: [
'plugin:vue/essential'
'eslint:recommended'
]
}
.eslintignore 文件可以忽略那些不用语法检查的文件
// .eslintignore
/src/lib
生产环境配置
生成环境需要对代码进行压缩和拆分。
npm install -D clean-webpack-plugin
clean-webpack-plugin 插件可以在构建时清除上一次的内容
// webpack.prod.conf.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
minimize 开启优化,TerserPlugin 配置 js 压缩的相关参数。splitChunks 对模块进行拆分。下面的拆分方法是将 node_modules 里的模块单独打包成一个文件。
// webpack.prod.conf.js
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: {
format:{
comments: false
},
toplevel: true
}
}),
`...`
],
splitChunks: {
cacheGroups: {
vendors: {
chunks: 'all',
name: 'vendors',
test: /[\\/]node_modules[\\/]/
}
}
}
}
}
css & sass 的配置
npm install -D style-loader css-loader postcss-loader autoprefixer sass-loader sass-resources-loader
npm install --save postcss
- css-loader:将 css 解析成字符串
- style-loader:将 css 字符串作为内联样式插入页面
- postcss-loader:css后处理器,配合autoprefixer给属性补充浏览器前缀
- sass-loader:解析sass
- sass-resources-loader: 可以把资源注入到每个文件,用来设置全局变量。
// webpack.dev.conf.js
module: {
rules: [
{
test: /\.s[ac]ss$/,
include: srcPath,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
resources: [
path.resolve(__dirname, '../src/style/variable.scss')
]
}
}
]
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
]
}
在根目录创建 postcss.config.js 文件
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
};
生产环境的配置会有所不同。生产环境需要将 css 抽离成单独的文件,避免 js 文件体积过大。且需要压缩体积。
npm install -D mini-css-extract-plugin css-minimizer-webpack-plugin
- mini-css-extract-plugin 将 css 抽离成单独的文件
- css-minimizer-webpack-plugin 用于压缩 css
// webpack.prod.conf.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
module.exports = merge(baseConfig, {
mode: 'production',
module: {
rules: [
{
test: /\.s[ac]ss$/,
include: srcPath,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
resources: commonScssFile
}
}
]
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
ignoreOrder: true,
filename: 'css/[name]_[contenthash:8].css',
chunkFilename: 'css/[name]_[contenthash:8].css'
})
],
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
})
analyze
通过 webpack-bundle-analyzer 插件可以分析打包结果模块之间的依赖关系和模块大小。
npm install -D webpack-bundle-analyzer
// package.json
{
"scripts": {
"analyze": "cross-env ANALYZE=true npm run build"
}
}
// webpack.prod.conf.js
const isAnalyze = process.env.ANALYZE
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
...(isAnalyze ? [new BundleAnalyzerPlugin()] : [])
]
}
完整代码
- package.json
{
"scripts": {
"start": "npm run dev",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.prod.conf.js",
"dev": "cross-env NODE_ENV=development webpack serve --config ./build/webpack.dev.conf.js",
"analyze": "cross-env ANALYZE=true npm run build"
}
}
- webpack.base.conf.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: isProd ? './' : '/',
filename: 'js/[name]_[chunkhash:8].js',
chunkFilename: 'js/[name]_[chunkhash:8].js'
},
module: {
rules: [
{
test: /\.(eot|ttf|otf|woff2?)(\?\S*)?$/,
type: 'asset',
parser: {
dataUrlCondition: { maxSize: 1024 * 5 }
},
generator: {
filename: 'font/[name]_[hash:8][ext]'
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
parser: {
dataUrlCondition: { maxSize: 1024 * 5 }
},
generator: {
filename: 'images/[name]_[hash:8][ext]'
}
},
{
test: /\.s[ac]ss$/,
include: srcPath,
use: [
isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, '../src/style/variable.scss')
}
}
]
},
{
test: /\.css$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'css-loader',
'postcss-loader'
]
}
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory=true'],
include: path.resolve(__dirname, '../src'),
exclude: /node_modules/
},
{
test: /\.vue$/,
include: path.resolve(__dirname, '../src'),
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: 'xxx系统',
template: 'index.html',
...(isProd ? {
minify: {
removeComments: true,
collapseWhitespace: true
}
}, {})
})
],
resolve: {
extensions: ['.js', '.vue', '.scss', '.css', '.json'],
alias: {
'src': path.resolve(__dirname, '../src'),
'views': path.resolve(__dirname, '../src/views'),
'components': path.resolve(__dirname, '../src/components'),
'directives': path.resolve(__dirname, '../src/directives'),
'filters': path.resolve(__dirname, '../src/filters'),
'images': path.resolve(__dirname, '../src/images'),
'modules': path.resolve(__dirname, '../src/modules'),
'style': path.resolve(__dirname, '../src/style'),
'utils': path.resolve(__dirname, '../src/utils'),
'vue$': 'vue/dist/vue.esm.js'
}
}
}
- webpack.dev.conf.js
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
module.exports = merge(baseConfig, {
mode: 'development',
target: 'web',
devtool: 'eval-cheap-module-source-map',
devServer: {
port: 9090,
hot: true,
compress: true,
open: true,
proxy: {
'/api/': {
target: 'http://example.com:9090',
changeOrigin: true
}
}
}
})
- webpack.prod.conf.js
const isAnalyze = process.env.ANALYZE
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
ignoreOrder: true,
filename: `${assets}/css/vendor.css?v=[contenthash:${hashLen}]`,
chunkFilename: `${assets}/css/[name].css?v=[contenthash:${hashLen}]`
}),
...(isAnalyze ? [new BundleAnalyzerPlugin()] : [])
],
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
extractComments: false,
terserOptions: {
format:{
comments: false
},
toplevel: true
}
}),
`...`
],
splitChunks: {
cacheGroups: {
vendors: {
chunks: 'all',
name: 'vendors',
test: /[\\/]node_modules[\\/]/
}
}
}
}
})
- .browserslistrc
> 1%
last 2 versions
ie >= 9
- .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}
- .eslintrc.js
module.exports = {
root: true,
env: {
'browser': true
},
parserOptions: {
parser: '@babel/eslint-parser'
},
extends: [
'plugin:vue/essential'
'eslint:recommended'
]
}
- .eslintignore
/src/lib
- postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
};
转载自:https://juejin.cn/post/7036648518735364110