前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版

webpack 回顾篇,核心概念与配置项全景梳理

首页2018-11-21 17:40:08Front-End
Webpack优化前端工程化构建工具

接手一个老项目,webpack.config.js 三百多行,动一下 loader 的顺序整个构建就崩,报错信息还看不出是哪一层出的问题。这种时候光会抄配置是不够的,你得知道每个字段作用在构建流程的哪一段。这篇是我当年把 webpack 从头过一遍时留下的笔记,后来陆续补过几轮,把版本演进、四个核心概念、loader 链、代码分割、CSS 处理、HMR、SourceMap、长缓存和多页面配置串成了一条线。

原文写的时候基线是 webpack 3,现在线上主流已经是 webpack 5。老写法我一个字没删,历史记录本身有价值,看老项目也用得上;每处过时的地方我另起一小段写清楚现在该怎么做,以及为什么变了。

这篇偏「概念与配置项全景」,也就是每个字段是什么、怎么填。如果你要的是「从零跑通一套能用的开发环境」那种手把手流程,看这篇的姊妹篇 webpack4 定制前端开发环境,那边是按 Step 走的实战流水线。

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

  • webpack 从 V1 到 V3 每一代补上了什么能力,怎么判断一份老配置停在哪一代
  • Entry、Output、Loader、Plugin 四个核心概念,以及 Chunk / Bundle / Module 的区别
  • Babel、TypeScript 在 webpack 里的接法,polyfill 的两种垫片选型
  • CommonsChunkPlugin 提取公共代码,以及它在 webpack 4 之后被谁取代
  • require.ensure 与 import() 两种代码分割写法的差异
  • CSS 的完整处理链,style-loader / css-loader / 预处理器 / PostCSS / 提取成文件
  • JS 与 CSS 两条 Tree shaking 路线,以及为什么有些库摇不动
  • 图片、字体、第三方库这三类文件的处理套路
  • dev server、proxy、HMR、SourceMap、ESLint 这套开发期能力怎么配
  • 长缓存优化和多页面应用的两种组织方式

# 一、webpack 的版本演进

# 1.1 版本更迭

先看一眼从 0.x 到 3.x 这条时间线。知道每个大版本是什么时候出来的,以后看到一份陌生的老配置,扫一眼用了哪些插件就能判断它停在哪一代。

webpack 从早期版本到 3.x 的版本发布时间线

下面这张是各个大版本之间的能力变化,重点看每一代往上加了什么。

webpack 各大版本之间的能力变化对比

# 1.2 功能进化

把上面两张图里的信息拆开说,每一代的关键词其实很清楚。

Webpack V1

  • 编译、打包
  • HMR (模块热更新)
  • 代码分割
  • 文件处理

V1 把「模块化 + 打包」这件事做成了,HMR 和代码分割在那个年代是相当超前的能力。

Webpack V2

  • Tree Shaking
  • ES module
  • 动态 Import
  • 新的文档

V2 最重要的动作是原生支持 ES module。有了静态的 import / export,Tree Shaking 才有分析的基础,这是后面所有产物体积优化的地基。

Webpack V3

  • Scope Hoisting (作用域提升)
  • Magic Comments (配合动态import使用)

V3 的 Scope Hoisting 把能合并的模块塞进同一个函数作用域,减少了运行时的闭包包裹,产物小一点、启动快一点。Magic Comments 就是 import() 里那段 /* webpackChunkName */ 注释,后面代码分割那节会用到。

版本迁移

V1 -> V2

迁移指南 https://doc.webpack-china.org/guides/migrating/

V2 -> V3

更新升级即可

顺着上面聊一下现在的情况。V3 之后 webpack 4 引入了 mode 和 optimization.splitChunks,webpack 5 又带来了持久化缓存、Asset Modules 和 Module Federation。也就是说这篇里凡是出现 webpack.optimize.* 系列内置插件的地方,在 webpack 5 上都已经不是推荐写法了,我会在对应章节挨个说明。如果你要的是 webpack 5 那一套构建性能配置,我单独写过一篇 Webpack 5 构建性能优化实战。

# 二、webpack 核心概念

webpack 的配置项看着多,骨架其实就四块,Entry 说从哪进,Output 说往哪出,Loader 管「这类文件怎么翻译成模块」,Plugin 管「构建过程中还要顺手干点什么」。剩下几十个字段都是挂在这四块上的细节。先把这四块的分工关系理顺,后面看任何一份配置都不会慌。

# 2.1 Entry

  • 代码的入口
  • 打包的入口
  • 单个或多个

Entry 是整张依赖图的根。webpack 从这里开始读文件、解析 import、递归往下走,最后得到一张完整的模块依赖图。一个入口对应一条独立的依赖链,多入口就是多张图(有交集的部分后面靠代码分割去合并)。

写法上有四种,我建议直接用键值对那种,理由在下面。

最简写法,一个字符串就是一个入口,webpack 会给它一个默认名字 main。

module.exports = {
  entry: 'index.js'
}
@前端进阶之旅: 代码已经复制到剪贴板

传数组表示「把这几个文件打进同一个 bundle」,它们仍然算一个入口,只是这个入口有多个起始文件。常见用途是把 polyfill 塞在业务代码前面。

module.exports = {
  entry: ['index.js','vendor.js']
}
@前端进阶之旅: 代码已经复制到剪贴板

键值对写法,index 这个 key 会成为 chunk 的名字,后面 output.filename 里的 [name] 占位符取的就是它。

module.exports = {
  entry: {
      index:'index.js'
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

前两种能力合起来,多个入口、其中某个入口还有多个起始文件。

module.exports = {
  entry: {
      index:['index.js','app.js'],
      vendor: 'vendor.js'
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

为什么建议一律用键值对写法?因为字符串和数组写法拿不到 chunk 名,产物文件全叫 main,一旦要做多入口或者按 chunk 拆包,你得回头把配置改一遍。一开始就写成对象,后面加入口只是多一行的事。

# 2.2 Output

  • 打包成的文件(bundle)
  • 一个或多个
  • 自定义规则
  • 配合CDN

Output 决定产物落在哪、叫什么名字。它和 Entry 是对应关系,几个入口原则上就会产出几个 bundle,再加上代码分割出来的若干 chunk 文件。

单入口的时候写死文件名就行。

module.exports = {
  entry: 'index.js',
  output: {
      filename: 'index.min.js'
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

多入口就必须用占位符,不然两个入口会往同一个文件名上写。[name] 取的是 entry 的 key,[hash:5] 是本次构建的哈希取前 5 位,用来做浏览器缓存的破缓存标记。

module.exports = {
  entry: {
    index: 'index.js',
    vendor: 'vendor.js'
  },
  output: {
      filename: '[name].min[hash:5].js'
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意,[hash] 是「整次构建」的哈希,只要有任意一个文件变了,所有产物的文件名都会一起变,缓存全部失效。真要做长期缓存得换成 [chunkhash],第六节讲长缓存优化时会专门展开。

output.publicPath 这个字段这里先提一句,它决定运行时去哪个前缀下取异步 chunk 和静态资源,把它指向 CDN 域名就是最常见的「配合 CDN」的做法。

# 2.3 Loaders

  • 处理文件
  • 转化为模块

webpack 自己只认 JS。CSS、图片、字体、TypeScript 这些它一概不懂,Loader 干的就是翻译工作,把非 JS 的东西转成 webpack 能塞进依赖图的模块。

module.exports = {
  module: {
      rules: [
        {
            test: /\.css$/,
            use: 'css-loader'
        }
      ]
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

test 是匹配条件,use 是命中之后交给谁处理。一条 rule 就是一句「凡是长这样的文件,走这条链」。

# 2.3.1 常用Loader

按职责分成三类记就行。

编译相关

  • babel-loader
  • ts-loader

样式相关

  • style-loader
  • css-loader
  • less-loader
  • postcss-loader

文件相关

  • file-loader
  • url-loader

文件这一类现在有变化。webpack 5 内置了 Asset Modules,file-loader、url-loader、raw-loader 这三个都不用装了,直接在 rule 里写 type: 'asset/resource'(等价 file-loader)、type: 'asset/inline'(等价 url-loader 内联)或者 type: 'asset'(按体积自动二选一)。老项目升级 webpack 5 的时候,这几个 loader 记得从 package.json 里删掉,留着反而容易和内置行为打架。

# 2.4 Plugins

  • 参与打包整个过程
  • 打包优化和压缩
  • 配置编译时的变量
  • 极其灵活

Loader 只在「文件转模块」这一步起作用,Plugin 的口子大得多。webpack 内部跑的是一套基于 tapable 的钩子系统,从初始化、编译、生成 chunk 一直到写文件,每个环节都暴露了钩子,Plugin 挂在哪个钩子上就能在哪一步插手。想深入了解常用插件都干了什么,可以看 webpack 常用插件汇总。

module.exports = {
 plugins: [
    new webpack.optimize.UglifyJsPlugin()
 ]
}
@前端进阶之旅: 代码已经复制到剪贴板

# 2.4.1 常用plugins

优化相关

  • CommonsChunkPlugin
  • UglifyJsWebpackPlugin

功能相关

  • ExtractTextWebpackPlugin 提取css
  • HtmlWebpackPlugin 生成HTML模板
  • HotModuleReplacementPlugin 热模块替换
  • CopyWebpackPlugin 拷贝文件

这份清单里有三个已经换代了,先记个账,后面对应章节会细说。CommonsChunkPlugin 在 webpack 4 被 optimization.splitChunks 取代;UglifyJsWebpackPlugin 换成了 terser-webpack-plugin(Uglify 不支持 ES2015+ 语法,Terser 是它的 fork);ExtractTextWebpackPlugin 换成了 mini-css-extract-plugin。HtmlWebpackPlugin、HotModuleReplacementPlugin、CopyWebpackPlugin 这三个到现在还在用,只是版本和参数有调整。

# 2.5 名词

  • Chunk 打包过程分割的代码块
  • Bundle 打包后的文件
  • Module

这三个词在文档和报错信息里混着出现,分不清会很难受,我一开始也被绕过。Module 是源码层面的一个文件,一个 .js 一个 .css 都是 Module;Chunk 是构建过程中的中间产物,webpack 把若干 Module 按依赖关系归成一组,这组就是一个 Chunk;Bundle 是最终落盘的那个文件。

fe
  • 一、webpack 的版本演进
    • 1.1 版本更迭
    • 1.2 功能进化
  • 二、webpack 核心概念
    • 2.1 Entry
    • 2.2 Output
    • 2.3 Loaders
      • 2.3.1 常用Loader
    • 2.4 Plugins
      • 2.4.1 常用plugins
    • 2.5 名词
  • 三、初探 webpack
    • 3.1 使用babel打包es6
      • 3.1.1 编译 ES 6/7
    • 3.2 打包 Typescript
    • 3.3 提取 js 的公用代码
    • 3.4 代码分割和懒加载
    • 3.5 处理 CSS 和 CSS 模块化
    • 3.6 PostCSS in Webpack
    • 3.7 Tree shaking
      • 3.7.1 JS Tree shaking
      • 3.7.2 CSS Tree shaking
  • 四、由浅入深Webpack
    • 4.1 文件处理
      • 4.1.1 图片处理
      • 4.1.2 处理雪碧图、base64、压缩图片
      • 4.1.3 处理字体文件
      • 4.1.4 处理第三方 JS 库
    • 4.2 HTML in webpack(自动生成HTML)
      • 4.2.1 生成 HTML
      • 4.2.2 HTML 中引入图片
      • 4.2.3 配合优化
  • 五、Webpack 环境配置
    • 5.1 Webpack Watch Mode
    • 5.2 Webpack Dev Server
      • 5.2.1 Dev Server
      • 5.2.2 proxy代理远程接口
      • 5.2.3 模块热更新
      • 5.2.4 开启调试SourceMap
      • 5.2.5 设置 ESLint 检查代码格式
      • 5.2.6 区分开发环境 和 生产环境
    • 5.3 使用 middleware 来搭建开发环境
  • 六、webpack实战场景
    • 6.1 分析打包结果
    • 6.2 优化打包速度
      • 6.2.1 方法一:分开 vendor 和 app
      • 6.2.2 方法二:UglifyJsPlugin并行处理
      • 6.2.4 方法四:减少 babel-loader 编译时间
      • 6.2.5 其他
    • 6.3 长缓存优化
    • 6.4 多页面应用
      • 6.4.1 多页面特点
      • 6.4.2 多页面多配置
      • 6.4.3 多页面单配置
  • 七、更多学习
  • 总结
  • 参考

← ESLint 配置文件详解 从 eslintrc 字段到扁平配置webpack常用插件总结篇 →