关键配置项

  • entry

查找依赖的入口文件配置,入口文件可以多个

单页面应用入口配置

1
2
3
4
5
6
7
module.exports = {
entry: {
vendor: './src/vendor.js', // 第三方依赖库
polyfill: './src/polyfill.js', // 特性填充库
index: './src/index.js' // 单页面应用入口文件
}
}

多页面应用入口配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const path = require('path')
const fs = require('fs')

// 处理公共 entry
const commonEntry = ['./src/vendor.js', './src/polyfill.js']
// 页面目录
const PAGE_DIR = './src/pages/'
const entry = {}

// 遍历页面目录
const getPages = () => {
return fs.readdirSync(PAGES_DIR).filter(item => {
let filepath = path.join(PAGES_DIR, item, 'index.js')
if(!fs.existsSync(filepath)){
filepath = `${filepath}x`
}
if (!fs.existsSync(filepath)) {
return false
}
return true
})
}

getPages(options).forEach(file => {
cosnt name = path.basename(file)
entry[name] = [...commonEntry, `${PAGES_DIR}/${file}/index`]
})

module.exports = {
entry
}
  • 入口 boundle 如何插入对应的 html?

使用 HtmlWebpackPlugin 自动处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cosnt plugins = []
if(mode = 'single'){
plugins.push(
new HtmlWebpackPlugin({
minify: false,
filename: 'index.html',
template: './src/index.html'
})
)
}

if(mode = 'multi'){
const files = getPages(options)
files.forEach(file => {
cosnt name = path.basename(file)
file = `${PAGES_DIR}/${file}/index.html`
const chunks = [`runtime~${name}`, name]
plugins.push(
new HtmlWebpackPlugin({
minify: false,
filename: `${name}.html`,
template: file,
chunks
})
)
})
}
module.exports = {
plugins
}
  • output

输出 bundle 相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
{
output: {
// name是你配置的entry中key名称,或者优化后chunk的名称
// hash是表示bundle文件名添加文件内容hash值,以便于实现浏览器持久化缓存支持
filename: '[name].[hash].js',
// 在script标签上添加crossOrigin,以便于支持跨域脚本的错误堆栈捕获
crossOriginLoading: 'anonymous',
//静态资源路径,指的是输出到html中的资源路径前缀
publicPath: './fudao/pc/',
//文件输出路径
path: './dist'
}
}
  • resolve

该配置主要用于解析模块依赖的自定义项:
modules: 用于加速绝对路径查找效率
alias: 用户自定义模块查找路径

1
2
3
4
5
6
7
8
9
resolve: {
modules: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules')
],
alias: {
components: path.resolve(__dirname, '/src/components')
}
}

扩展:使用了绝对路径,vscode 智能代码导航失效。根目录配置 jsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"],
"components/*": ["./src/components/*"],
"assets/*": ["./src/assets/*"],
"pages/*": ["./src/pages/*"]
}
},
"include": ["./src/**/*"]
}
  • module

配置 rules, 对于不同资源的处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module: {
// 当前库不依赖其他库,不需要解析可以加快编译速度
// 通常可以将一些大型的库且已经编译好的库排除,减少webpack解析耗时
noParse: /node_modules\/(moment|chart\.js)/,
rules: [
{
test: /\.jsx?$/,
use: resolve('babel-loader'),
// 需要被 load 处理的资源
include: [
path.resolve(projectDir, 'src'),
path.resolve(projectDir, 'node_modules/@tencent'),
].filter(Boolean),
// 忽略压缩文件
exclude: [/(.|_)min\.js$/]
}
]
}
  • optimization

minimizer: 配置输出的文件压缩插件

js: webpack 集成的 uglifyjs,Terser支持es6、支持多进程压缩
css: optimize-css-assets-webpack-plugin(使用 cssnano 作为处理引擎,去除重复代码)

splitChunks: webpack4.x 新功能,优化公共 chunk 提取策略

  • plugin

包含整个构建过程任何阶段:报告构建耗时、修改输出代码支持主域重试、添加构建进度报告、代码压缩
、资源替换等

实现插件对输出结果处理时,应当在文件输出到磁盘前处理

开发体验优化

  • 组件热刷新、CSS热刷新
  1. dev 模式使用 webpack.HotModuleReplacementPlugin 插件
  2. 所有 entry 中插入 require.resolve('../utils/webpackHotDevClient') react官方 create-react-app
  3. webpack-dev-server 模块启动参数添加 hot:true
  4. 需要热加载的js文件添加如下代码:
if(process.env.NODE_ENV === 'development' && module.hot){
  module.hot.accept()
}
  • SSR热调试

解决方案: webpack watch+nodemon,node 服务需要的 html/js 通过 webpack 插件动态输出,当 nodemon 检查到变化后将自动重启, html 文件中的静态资源全部替换成 dev 模式下的资源,保持 socket 连接自动更新页面

  • style 调试

style-loader 开始 sourceMap 后,sourceMap 是内联在 style 文件中,需要通过 link 导入,本质通过 javascript 生成 blob 后 link 标签解析。(问题:FOUC 页面加载后闪烁)

解决方法:singleton: true

性能优化

  • node_modules 缓存

JB系统缓存(@tencent/im-build模块自动缓存)
OCI编译系统

  • 构建中间结果缓存

webpack 缓存的内容:loader处理结果、plugin处理结果、输出文件结果

  1. babel-loader 缓存,通过 cacheDirectory 开启缓存
1
2
3
4
5
6
7
8
9
test: /\.jsx?$/,
use: [{
loader: resolve('babel-loader'),
options: {
babelrc: false,
cacheDirectory: path.resolve(options.cache, 'babel-loader'),
presets:[[require('babel-preset-imt'), { isSSR }]]
}
}]
  1. eslint-loader 缓存,通过 cache 指定缓存路径
1
2
3
4
5
6
7
8
test: /\.(js|mjs|jsx)/,
enforce: 'pre',
use: [{
options: {
cache: path.resolve(options.cache, 'eslint-loader'),
},
loader: require.resolve('eslint-loader')
}]
  1. css/scss 缓存
1
2
3
4
5
6
7
8
9
10
11
{
loader: resolve('cache-loader'),
options: { cacheDirectory: path.join(cache, 'cache-loader-css')}
},
{
loader: resolve('css-loader'),
options: {
importLoaders: 2,
sourceMap
}
}
  1. 输出代码压缩缓存,JS压缩引擎多进程处理
1
2
3
4
5
6
7
8
9
10
11
12
{
// 设置缓存目录
cache: path.resolve(cache, 'terser-webpack-pugin'),
// 开启多进程
parallel: true,
sourceMap,
terserOptions: {
compress: {
drop_console: true // 删除所有 console 语句
}
}
}

编译流程

参考文档

webpack讲解