持续更新:2022.05.02
生产|开发环境构建
不同环境config文件
开发环境和生产环境构建存在巨大差异,所以通常编写独立的webpack配置。但为了遵循不重复原则,我们又会存在common webpack配置
为了将这些配置合并在一起,可以使用 webpack-merge 插件,也可以采用其他方法
👇
目录
demo
|- build
|- webpack.common.js
|- webpack.dev.js
|- webpack.prod.js
|- package.json
|-postcss.config.js
|-.babelrc
|- /dist
|- /src
|- index.js
|- /node_modules
webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const commonConfig = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}, {
test: /\.(jpg|gif|png)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 1024 //100KB
}
}
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}, {
test: /\.scss$/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html' // 以src/目录下的index.html为模板打包
}),
new CleanWebpackPlugin({}),
],
output: {
filename: '[name].js',
// publicPath: "https://cdn.example.com/assets/",
path: path.join(__dirname, '../dist')
}
}
module.exports = commonConfig
webpack.dev.js
const path = require('path')
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common')
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9000,
open: true,
hot: true,
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
],
optimization:{
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig)
webpack.prod.js
const { merge } = require('webpack-merge')
const commomConfig = require('./webpack.common')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = merge(commomConfig, prodConfig)
打包脚本
我们将 npm run dev 定义为开发环境脚本,并在其中使用 webpack-dev-server,将 npm run build 定义为生产环境脚本
{
"name": "demo",
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js",
"start": "npx webpack --config ./build/webpack.dev.js"
},
}
打包一个library(库)
- 安装工具
npm install --save-dev webpack webpack-cli
- 增加
output.library选项,让构建产物以 library 形式输出,纯字符串:
output: {
library: "myLib", // 这样打包只能以 script 方式引入,无法适配 CommonJS/AMD/Node等环境
}
更改为对象:
output: {
library: {
name: 'myLib',
type: 'umd', // 以umd的格式打包lib,通常使用这个 type
},
}
library 详解请看 output篇
- 用到的第三方库,安装到
devDependencies中,避免库体积很大,如
npm install --save-dev lodash
- 将 lodash 从library里剔除,以 peerDependency 形式出现(让使用该library的应用自行安装lodash),避免包过大
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_',
},
},
这意味着你的 library 需要一个名为 lodash 的依赖,这个依赖在 consumer 环境中必须存在且可用。
5. 将生成 bundle 的文件路径,添加到 package.json 中的 main 字段中
// package.json
{
...
"main": "dist/webpack-numbers.js",
...
}
- 发布成npm包
如何提高 webpack 构建速度?
这篇文章针对第一点有更详细介绍,如有需要可点击
1. 缩小编译范围,减少不必要的编译工作
- exclude/include 指定
- resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
- resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
- resolve.extensions 尽可能减少后缀尝试的可能性
- noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
- IgnorePlugin, webpack 的内置插件,作用是忽略第三方包指定目录。有些包我们只需要核心功能,可以排除掉不需要的部分(并不是都可以这样)
- 合理使用alias
- externals 将一些 JS文件存储在
CDN上(减少 Webpack 打包出来的 js 体积)
const resolve = dir => path.join(__dirname, '..', dir);
resolve: {
modules: [ // 指定以下目录寻找第三方模块,避免webpack往父级目录递归搜索
resolve('src'),
resolve('node_modules'),
resolve(config.common.layoutPath)
],
mainFields: ['main'], // 只采用main字段作为入口文件描述字段,减少搜索步骤
alias: {
vue$: "vue/dist/vue.common",
"@": resolve("src") // 缓存src目录为@符号,避免重复寻址
}
},
module: {
noParse: /jquery|lodash/, // 忽略未采用模块化的文件,因此jquery或lodash将不会被下面的loaders解析
rules: [
{
test: /\.js$/,
include: [ // 表示只解析以下目录,减少loader处理范围
resolve("src"),
resolve(config.common.layoutPath)
],
exclude: file => /test/.test(file), // 排除test目录文件
loader: "happypack/loader?id=happy-babel" // 后面会介绍
},
]
}
2. 压缩代码
多进程并行压缩
可以使用 webpack-parallel-uglify-plugin,如何使用参考 三石的webpack(Plugins篇)
css 单独压缩
通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 optimize-css-assets-webpack-plugin 压缩 CSS,如何使用参考 三石的webpack(Plugins篇)
图片压缩
- 使用基于 Node 库的 imagemin
- 配置 image-webpack-loader
3. 并行处理 Loader
- 使用HappyPack,如何使用参考 三石的webpack(Plugins篇)
缺点是 HappyPack 已经不太维护了 - 使用
thread-loader,如何使用参考 三石的webpack(Loader篇)
4. DllPlugin
第三方库打包成动态链接库,如何使用参考 三石的webpack(Plugins篇)
5. 利用缓存提升二次构建速度
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
- splitChunks 抽取公共模块,公共代码下载一次就缓存起来,避免重复下载
参考官网-构建性能
webpack 如何优化前端性能
-
压缩代码
-
删除冗余代码 「Tree-Shaking」
webpack4 以后,mode设置成 "production"即可
- 第三方库按需加载、路由懒加载
// 按需加载,避免全部引入,加大项目体积
import { connect } from 'react-redux';
//路由懒加载
const showImage = () => import('@/components/common/showImage');
- 代码分割 进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
- 提取第三方库 「vendor」
module.exports = {
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom'],
},
}
- 依赖库分离 「splitChunks」
optimization: {
splitChunks: {
chunks: "async", // 必须三选一: "initial" | "all"(推荐) | "async" (默认就是async)
minSize: 30000, // 最小尺寸,30000
minChunks: 1, // 最小 chunk ,默认1
maxAsyncRequests: 5, // 最大异步请求数, 默认5
maxInitialRequests : 3, // 最大初始化请求书,默认3
automaticNameDelimiter: '~',// 打包分隔符
name: function(){}, // 打包后的名称,此选项可接收 function
cacheGroups:{ // 这里开始设置缓存的 chunks
priority: 0, // 缓存组优先级
vendor: { // key 为entry中定义的 入口名称
chunks: "initial", // 必须三选一: "initial" | "all" | "async"(默认就是async)
test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk
name: "vendor", // 要缓存的 分隔出来的 chunk 名称
minSize: 30000,
minChunks: 1,
enforce: true,
maxAsyncRequests: 5, // 最大异步请求数, 默认1
maxInitialRequests : 3, // 最大初始化请求书,默认1
reuseExistingChunk: true // 可设置是否重用该chunk
}
}
}
},
- 利用CDN加速 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
通用缓存方案
-
output.filename 采用 可替换模板字符串 命名,这样内容hash变化文件名就会变化,避免更新不及时
filename: '[name].[contenthash].js' -
采用代码分离
runtimeChunk,这样,chunk1 引入变化的chunk2,最终 chunk1 能够成功缓存(详情见 optimization篇)
optimization: {
runtimeChunk: 'single',
}
- 将第三方库提到单独的 vendor chunk 中,因为它们在开发中不会频繁修改
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
- moduleIds 设置 'deterministic',避免新增文件影响到自己的 moduleId
optimization: {
moduleIds: 'deterministic'
}
综合如下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
多页面应用打包
使用 html-webpack-plugin插件生成多个实例,点击 查看详情