前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

webpack4配置详解,从 entry 到 devServer 逐项拆解

首页2018-11-18 14:32:12Front-End
Webpack构建工具前端工程化配置详解

接手一个老项目,打开 webpack.config.js,两百行配置里一半的字段你都说不上来它在管什么,改一个 publicPath 得先在本地试三遍。这种情况我遇到过不止一次,问题不在配置难,在于大部分教程都是给你一份能跑的模板,没告诉你每个字段作用在构建的哪一步。

这篇是我当年整理 webpack4 配置时的逐字段笔记,按配置文件从上到下的顺序走一遍:每个字段管什么、典型取值是什么、什么场景下必须改。原文写于 webpack4 刚发布不久,现在 webpack 5 已经是主流,所以每一节后面我另起了一小段说明这块在新版本里有没有变化、该怎么迁移,老写法本身完整保留,因为老项目里它们还在跑。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • entry 的单文件和多文件两种写法,以及 vendors 入口的历史用途
  • output 里 path / filename / chunkFilename / publicPath 分别决定什么
  • hash、chunkhash、contenthash 三种占位符的区别和选用场景
  • mode 这个 webpack4 新增字段到底替代了什么
  • devtool 各档位的取舍,以及生产环境的正确选择
  • optimization 下 minimize / splitChunks / runtimeChunk 的作用
  • resolve 的 extensions / alias / modules 三件套
  • module.rules 的匹配规则和常见 loader 组合,含原文里一处配错的地方
  • devServer 的代理、history 回退、热更新配置
  • webpack4 删掉了哪些东西,以及这份配置迁到 webpack 5 要动哪几处

# 一、entry,构建的起点

entry 告诉 webpack 从哪个文件开始分析依赖。最常见的是对象形式,key 是 chunk 名字,value 是入口文件路径。

//方式一:单文件写法
entry: {
	index: './src/pages/route.js',
	//about: './src/pages/about.js',
	//other:()=>{...}
}

//方式二:多文件写法
entry: {
	/*index:[
		'webpack-hot-middleware/client',
		'./src/root.js'
	],*/
	index: ['./src/root.js'],
	vendors : ['react','react-dom','redux','react-router','classnames'],
}
@前端进阶之旅: 代码已经复制到剪贴板

上面两段的差别在 value 的类型。单个字符串就是一个入口一个文件;数组形式表示这个 chunk 由多个文件合并而成,webpack 会按数组顺序把它们打进同一个 chunk,最后一个文件的导出作为整个 chunk 的导出。

被注释掉的那段 'webpack-hot-middleware/client' 值得说一句。用 webpack-dev-middleware + webpack-hot-middleware 自己搭 Node 服务时,热更新的客户端脚本得手动塞进入口数组里,dev-server 帮你做了这件事你才感觉不到它。

vendors 这个入口是那个年代的典型写法:把第三方库单独列成一个入口,配合 CommonsChunkPlugin 抽出来做长期缓存。

放到今天这条路已经不用了。webpack4 之后 splitChunks 用 test: /[\\/]node_modules[\\/]/ 就能自动识别第三方依赖,不需要你手写一份依赖清单。手写清单最大的麻烦是维护,装了个新库忘了加进去,它就悄悄跑进业务 chunk 里了。同一时期还有 DllPlugin 那条路子,我在 dll 预编译提高 webpack 打包速度 里单独写过,那篇也说明了为什么现在不推荐再用它。

# 二、output,产物往哪放

output 决定打包结果落到哪、叫什么名字、页面通过什么路径去请求它。

  • path: 输出文件的目录
  • filename: 输出的文件名,它一般跟你 entry 配置相对应,如 js/[name].js,[name] 在这里表示的是 index、vendors 这些入口名
  • chunkFilename:块,配置了它,非入口 entry 的模块会被自动拆分文件,也就是大家常说的按需加载,与路由中的 require.ensure 相互对应
  • publicPath:文件输出的公共路径
  • pathinfo:保留相互依赖的包中的注释信息,这个基本不用主动设置,development 模式下默认 true,production 模式下默认 false
  • 主要的就是这些,还有一些其他的 library、libraryTarget、auxiliaryComment 等
output: {
	path: path.resolve(__dirname, '../assets'),
	filename: 'js/[name].js',
	chunkFilename: 'js/[name].[chunkhash:8].js',
	publicPath: '/_static_/', //最终访问的路径就是:localhost:3000/_static_/js/*.js
	//pathinfo:true,
}
@前端进阶之旅: 代码已经复制到剪贴板

这里最容易出问题的是 publicPath。它不影响文件写到磁盘的哪个目录,只影响运行时资源 URL 的前缀。异步 chunk 加载不出来、字体图片 404,十次里有八次是它配错了。本地开发一般用 /,部署到 CDN 就换成完整域名,部署在子路径下就写 /子路径/。

filename 和 chunkFilename 的分工也常被搞混。前者管有名字的入口 chunk,后者管那些通过动态导入产生的、没人给它起名的 chunk。原文里入口不带 hash、异步 chunk 带 [chunkhash:8],这种搭配在有服务端模板渲染 HTML 的项目里能见到,纯前端项目一般两边都要带 hash。

require.ensure 是 webpack 1 时代的按需加载 API。现在统一用 import('./xxx'),它是 ECMAScript 标准语法,配合 /* webpackChunkName: "foo" */ 魔法注释还能给 chunk 起名字。require.ensure 至今仍能用,但没有理由在新代码里写它。

# 三、hash 占位符怎么选

文件名里的那几个方括号占位符,用错了会直接毁掉浏览器缓存。常用的有三种:

模板 描述
hash 模块标识符的 hash,一般应用于 filename:'[name].[hash].js'
chunkhash 按需加载块内容的 hash,根据 chunk 自身的内容计算而来,'js/[name].[chunkhash:8].js'
contenthash 在提取 css 文件时根据内容计算而来的 hash,结合 ExtractTextWebpackPlugin 插件使用
hash 长度 默认 20,可自定:[hash:8]、[chunkhash:16]

三者的关键区别在计算范围。[hash] 是整次构建一个值,你改任何一个文件,所有产物的文件名全变,缓存全部作废,所以生产环境几乎不该用它。[chunkhash] 按 chunk 算,改业务代码不影响 vendors chunk 的名字。[contenthash] 按最终文件内容算,粒度最细。

那为什么 JS 和 CSS 打在一个 chunk 里时非要用 [contenthash] 呢?因为一个 chunk 会同时产出 .js 和 .css 两个文件,用 [chunkhash] 的话,你只改了一行 CSS,同 chunk 的 JS 文件名也跟着变,白白让用户重新下一遍 JS。

现在的建议很简单:生产环境所有产物统一用 [contenthash]。原文提到的 ExtractTextWebpackPlugin 在 webpack4 之后已经换成了 mini-css-extract-plugin,它对 [contenthash] 的支持更完整。这块的迁移细节我在 webpack4 升级篇 里展开写了。

# 四、mode,webpack4 省下的一大堆配置

mode 属于 webpack4 才新增的字段,4 之前大家一般用 DefinePlugin 手动设置环境变量,再自己挨个开插件。

  • mode 取值为 development、production、none
  • development:开发模式,打包的代码不会被压缩,开启代码调试
  • production:生产模式,正好反之

//方法一
webpack --mode development/production

//方法二
……
mode:'development/production'
……

@前端进阶之旅: 代码已经复制到剪贴板

这个字段的价值不在于它本身,而在于它背后自动开了一整套预设。设成 production 之后,webpack 会自动把 optimization.minimize、optimization.usedExports、optimization.concatenateModules、optimization.sideEffects 这些统统打开,同时把 process.env.NODE_ENV 注入成 'production'。设成 development 则相反,走的是不压缩、保留可读性、开 namedModules 那套。

先说结论,绝大多数项目只需要设 mode,不需要再手动配那一堆开关。我见过不少配置文件里 mode: 'production' 和一长串 optimization 手动开关同时存在,其实是重复劳动,还容易改出前后矛盾的组合。

none 这个值是给要做精细控制或者调试 webpack 本身的场景用的,它什么优化都不开,产物最接近原始模块结构。想看清楚 webpack 的运行时代码长什么样,用它跑一次很直观。

webpack 5 这块没变,mode 的语义和默认值都延续下来了。

# 五、devtool,SourceMap 的取舍

devtool 控制是否生成、以及如何生成 source map 文件,开发环境下更有利于定位问题,默认 false。它的开启会影响编译速度,所以生产环境一定要谨慎。

常用的值有 cheap-eval-source-map、eval-source-map、cheap-module-eval-source-map、inline-cheap-module-source-map 等等,更详细的可以去 官方文档查看。一般使用 eval-source-map 较多,每个取值都有它不一样的特性。

这些看着眼花的名字其实是几个关键词拼出来的,拆开就好记了:

  • eval:用 eval() 包裹模块代码,构建和重建都最快,代价是产物里全是字符串
  • cheap:只映射到行,不映射到列,也不映射 loader 处理前的源码,速度提升明显
  • module:把 cheap 丢掉的那部分补回来,映射回 loader 处理之前的原始源码
  • inline:map 内容直接以 base64 塞进产物文件,不单独产出 .map
  • hidden:生成 map 文件但不在产物末尾写引用注释

这里有个坑要注意,cheap 不带 module 的时候,你在浏览器里看到的是 Babel 转译后的代码,不是你写的那份 JSX 或 TS。调试组件时发现断点位置对不上,多半就是漏了 module。

fe
  • 一、entry,构建的起点
  • 二、output,产物往哪放
  • 三、hash 占位符怎么选
  • 四、mode,webpack4 省下的一大堆配置
  • 五、devtool,SourceMap 的取舍
  • 六、optimization,webpack4 的优化总开关
  • 七、resolve,模块怎么找
  • 八、module.rules,编译规则
  • 九、项目中常用 loader
  • 十、plugins 与 loader 的分工
  • 十一、webpack-dev-server
  • 十二、webpack4 删掉的东西
  • 十三、这份配置迁到 webpack 5 要动哪几处
  • 总结
  • 参考

← React性能优化总结,从render机制到memo的落地实践next项目部署到服务器pm2进程守护 →