我们手工的调整 mode
选项,可以实现生产环境
和开发环境
的切换,但很多配置在生产环境和开发环境中存在不一致的情况,比如开发环境没有必要设置缓存,生产环境还需要设置公共路径等等。
拆分开发环境和生产环境,让打包更灵活。
1. 公共路径 publicPath
publicPath
配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所有资源的基础路径。
publicPath
在构建目标 targets
(默认为web
)设置为 web
与 web-worker
时 output.publicPath
默认为 `‘auto’
实质上,发送到 output.path
目录的每个文件,都将从 output.publicPath
位置引用。这也包括(通过 代码分离 创建的)子 chunk 和作为依赖图一部分的所有其他资源(例如 image, font 等)。
在前面,我们没有设置 output.publicPath
,即为auto
时,我们在dist/index.html
看到资源的引用路径为:
当我们想要将打包出的静态资源放置在cdn服务器上,这个dist/index.html
该如何加载资源呢?
webpack.config.js
module.exports = {
// ...
output: {
publicPath: 'http://localhost:9000/',
filename: 'js/[name].[contenthash].js',
path: path.resolve(__dirname, './dist'),
},
}
执行webpack
可以看到,dist/index.html
加载的资源路径已经变化。实际上所有的静态资源路径都发生了变化
2. 环境变量
想要消除 webpack.config.js
在 开发环境 和 生产环境 之间的差异,你可能需要环境变量(environment variable)。
webpack 命令行 环境配置 的 --env
参数,可以允许你传入任意数量的环境变量。而在 webpack.config.js
中可以访问到这些环境变量。例如,--env production
或 --env goal=local
。
npx webpack --env goal=local --env production --progress
Tip
如果设置
env
变量,却没有赋值,--env production
默认表示将env.production
设置为true
。还有许多其他可以使用的语法。更多详细信息,请查看 webpack CLI 文档。
对于我们的 webpack
配置,有一个必须要修改之处。通常,module.exports
指向配置对象。要使用 env
变量,你必须将 module.exports
转换成一个函数:
const path = require('path');
module.exports = (env) => {
console.log('env', env);
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
};
};
3. 拆分配置文件
目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不同的配置文件中。如 webpack.config.dev.js
(开发环境配置)和 webpack.config.prod.js
(生产环境配置)。在项目根目录下创建一个配置文件夹 config
来存放他们。
config/webpack.config.dev.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another_module.js',
},
// 出口配置
output: {
filename: 'js/[name].js', // 出口文件名
// 绝对路径
path: path.resolve(__dirname, '../dist'), // path.resolve(__dirname) 表示获取当前文件的物理路径,即绝对路径
clean: true, // 在生成文件之前清空 output 目录
assetModuleFilename: 'images/[hash][ext][query]'
},
devtool: 'inline-source-map',
mode: 'development', // 打包模式
watch: true,
devServer: {
static: '../dist',
port: 9000,
open: true
},
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpeg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.(css|less)$/i,
use: [
// compiles Less to CSS
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(csv|tsv)$/i,
use: 'csv-loader'
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /(node_modules)/, // 排除编译 node_modules
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime'],
}
}
}
]
},
optimization: {
minimize: true, // 开发环境下启用 CSS 优化
splitChunks: {
cacheGroups: {
vendor: {
// 第三方模块一般处于node_modules中,所以这里缓存 node_modules 中第三方的模块
test: /[\\/]node_modules[\\/]/, // 缓存node_modules中的文件夹名和文件(文件目录前后可能会有斜线/)
name: 'vendors',
chunks: 'all'
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 模板
filename: 'index.html', // 文件名称
inject: 'body' // 定义生成的script所在的位置
}),
// 提取css
new MiniCssExtractPlugin({
filename: 'styles/[hash].css'
})
],
}
config/webpack.config.prod.js
const path = require('path')
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 toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another_module.js',
},
// 出口配置
output: {
filename: 'js/[name].[contenthash].js', // 出口文件名
// 绝对路径
path: path.resolve(__dirname, '../dist'), // path.resolve(__dirname) 表示获取当前文件的物理路径,即绝对路径
clean: true, // 在生成文件之前清空 output 目录
assetModuleFilename: 'images/[hash][ext][query]'
},
mode: 'production', // 打包模式
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpeg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.(css|less)$/i,
use: [
// 提取css
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(csv|tsv)$/i,
use: 'csv-loader'
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /(node_modules)/, // 排除编译 node_modules
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime'],
}
}
}
]
},
optimization: {
minimizer: [
new CssMinimizerPlugin(), // css 压缩
new TerserPlugin() // js 压缩
],
splitChunks: {
cacheGroups: {
vendor: {
// 第三方模块一般处于node_modules中,所以这里缓存 node_modules 中第三方的模块
test: /[\\/]node_modules[\\/]/, // 缓存node_modules中的文件夹名和文件(文件目录前后可能会有斜线/)
name: 'vendors',
chunks: 'all'
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 模板
filename: 'index.html', // 文件名称
inject: 'body' // 定义生成的script所在的位置
}),
// css 提取
new MiniCssExtractPlugin({
filename: 'styles/[hash].css'
})
],
//关闭 webpack 的性能提示
performance: {
hints: false
}
}
拆分成两个配置文件后,分别运行这两个文件:
webpack --config ./config/webpack.config.dev.js
查看开发环境打包结果
webpack serve --config ./config/webpack.config.dev.js
查看开发环境打项目打包加载结果
webpack --config ./config/webpack.config.prod.js
查看开发环境打包结果
webpack serve --config ./config/webpack.config.prod.js
查看开发环境打项目打包加载结果
4. npm脚本
配置 npm 脚本来简化命令行的输入
package.json
{
"scripts": {
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
},
}
开发环境运行脚本npm run start
生产环境运行脚本npm run build
5. 提取公共配置
我们发现这两个配置文件里存在大量的重复代码,可以手动的将这些重复的代码单独提取到一个文件里,
创建 config/webpack.config.common.js
,配置公共的内容:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const toml = require('toml');
const yaml = require('yamljs');
const json5 = require('json5');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another_module.js',
},
// 出口配置
output: {
// 绝对路径
path: path.resolve(__dirname, '../dist'), // path.resolve(__dirname) 表示获取当前文件的物理路径,即绝对路径
clean: true, // 在生成文件之前清空 output 目录
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpeg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 // 4kb
}
}
},
{
test: /\.(css|less)$/i,
use: [
// compiles Less to CSS
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(csv|tsv)$/i,
use: 'csv-loader'
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},
{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},
{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
},
{
test: /\.js$/,
exclude: /(node_modules)/, // 排除编译 node_modules
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime'],
}
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
// 第三方模块一般处于node_modules中,所以这里缓存 node_modules 中第三方的模块
test: /[\\/]node_modules[\\/]/, // 缓存node_modules中的文件夹名和文件(文件目录前后可能会有斜线/)
name: 'vendors',
chunks: 'all'
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html', // 模板
filename: 'index.html', // 文件名称
inject: 'body' // 定义生成的script所在的位置
}),
new MiniCssExtractPlugin({
filename: 'styles/[hash].css'
})
],
}
修改 config/webpack.confjg.dev.js
module.exports = {
// 出口配置
output: {
filename: 'js/[name].js', // 出口文件名
},
devtool: 'inline-source-map',
mode: 'development', // 打包模式
watch: true,
devServer: {
static: '../dist',
port: 9000,
open: true
},
optimization: {
minimize: true, // 开发环境下启用 CSS 优化
},
}
修改 config/webpack.confjg.prod.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
// 出口配置
output: {
filename: 'js/[name].[contenthash].js', // 出口文件名
},
mode: 'production', // 打包模式
optimization: {
minimizer: [
new CssMinimizerPlugin(), // css 压缩
new TerserPlugin() // js 压缩
],
},
//关闭 webpack 的性能提示
performance: {
hints: false
}
}
6. 合并配置文件
配置文件拆分好后,新的问题来了,如何保证配置合并没用问题呢?webpack merge 这个工具可以完美解决这个问题。
npm install webpack-merge -D
在 config
目录下创建 webpack.config.js
,合并代码
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const productionConfig = require('./webpack.config.prod.js')
const developmentConfig = require('./webpack.config.dev.js')
module.exports = (env) =>{
switch (true)
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
throw new Error('No matching configuration was found!');
}
}
修改package.json npm脚本
{
"scripts": {
"start": "webpack serve -c ./config/webpack.config.js --env development",
"build": "webpack -c ./config/webpack.config.js --env production"
},
}
分别执行 npm run start
、npm run build
可以看到编译、打包、加载都是成功的!
源码地址:https://gitee.com/yanhuakang/webpack-test
如果有用,就点个赞吧(\*^▽^\*)