Webpack | 青训营笔记

49 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

Webpack 介绍

一个前端项目是由 CSS 样式文件、图片文件、JS 文件、Vue 文件、TS 文件、JSX 文件等部分组成。我们可以手动管理这些资源:

  • 如果资源文件过多,手工操作流程繁琐。
  • 当文件之间有依赖关系时,必须严格按依赖顺序书写。
  • 开发与生产环境需要一致,难以接入 JS 和 TS 的新特性。
  • 比较难接入 Less、Sass 等。
  • JS、图片、CSS 资源管理模型不一致。

2009年诞生的 Node.js 和2010年诞生的 npm 将前端项目带入了工程化,而 Node.js 的 CommonJS 模块化规范不兼容浏览器。所以相继出现了一些打包工具,比如 Browserify、Gulp、RequireJS、Rollup、Webpack 等。

Webpack 本质上是一种前端资源译、打包工具。

  • 多份资源文件打包成一个 Bundle,减少 http 请求数
  • 支持 Babel、Eslint、TS、CoffeScript、Less、Sass
  • 支持模块化处理 CSS、图片等资源文件
  • 统一图片、CSS、字体等其它资源的处理模型
  • 支持 HMR + 开发服务器
  • 支持持续监听、持续构建
  • 支持代码分离支持 Tree-shaking
  • 支持 SourceMap

核心流程:

  1. 入口处理:编译入口,webpack 编译的起点,从 entry 文件开始,启动编译流程。
  2. 依赖解析:从 entry 文件开始,根据 require 或 import 等语句找到依赖资源。
  3. 资源解析:根据 module 配置项,调用资源转移器,将图片、CSS 等非标准 JS 资源转译为 JS 内容。webpack 内部所有资源都会以 module 对象形式存在,所有关于资源的操作、转译、合并都是以 module 为基本单位进行的。
  4. 资源合并打包:将转译后的资源内容合并打包为可直接在浏览器运行的 JS 文件。

其中,2、3 步骤会递归调用,直到所有资源处理完毕。

使用

关于 Webpack 的使用方法,基本都围绕配置展开,而这些配置大致可划分为两类:

  • 流程类: 作用于流程中某个 or 若干个环节直接影响打包效果的配置项。

    • 输入: entry、context
    • 模块解析: resolve、externals
    • 模块转译: module
    • 后处理: optimization、mode、target
    • 输出:output
  • 工具类: 主流程之外,提供更多工程化能力的配置项。

    • 开发效率类:watch、devtool、devServer
    • 性能优化类:cache、performance
    • 日志类:stats、infrastructureLogging
  1. 首先,npm i -D webpack webpack-cli 安装。
  2. 定义入口和产物出口。
  3. 安装 loader 处理 CSS,npm add -D css-loader style-loader
  4. 安装 loader 接入 Babel,npm i -D @babel/core ababel/preset-env babel-loader
  5. 生成 HTML 需要使用的是插件,npm i -D html-webpack-plugin
  6. Hot Module Replacement(HMR) - 模块热替换。
  • 命令需要带 serve:npx webpack serve。
  1. Tree-Shaking -树摇,用于删除 Dead Code.

经过模块化包装之后,这段文本内容转身变成 Webpack 可以处理的资源模块,其它 module 也就能引用、使用它了。

上例通过 return 语句返回处理结果,除此之外 Loader 还可以以 callback 方式返回更多信息,供下游 Loader 或者 Webpack 本身使用,例如在 webpack-contrib/eslint-loader 中:

通过 this.callback(null, content, map) 语句同时返回转译后的内容与 sourcemap 内容

插件

前端社区里很多有名的框架都各自有一套插件架构,例如 axios、quill、vscode、webpack、vue、rollup 等等。插件架构灵活性高,扩展性强,但是通常需要非常强的架构能力,需要至少解决三个方面的问题:

  • 接口:需要提供一套逻辑接入方法,让开发者能够将逻辑在特定时机插入特定位置
  • 输入:如何将上下文信息高效传导给插件
  • 输出:插件内部通过何种方式影响整套运行体系

针对这些问题,webpack 为开发者提供了基于 tapable 钩子的插件方案:

  1. 编译过程的特定节点以钩子形式,通知插件此刻正在发生什么事情;
  2. 通过 tapable 提供的回调机制,以参数方式传递上下文信息;
  3. 在上下文参数对象中附带了很多存在 side effect 的交互接口,插件可以通过这些接口改变

与 Loader 区别

都是 Webpack 的扩展机制。

  • Loader 是一个函数,负责代码的转换、编译。在 webpack 读取模块内容之后,生成 AST 语法树之前进行。操作的是文件,比如将 A.scss 转换为 A.css,是单纯的文件转换过程。
  • 插件是一个类,利用 webpack 提供的 hooks,当什么时,执行什么。可以在 webpack 整个打包过程中进行。功能更强,能够在各个对象的钩子中插入特化处理逻辑,它可以覆盖 Webpack 全生命流程,能力、灵活性、复杂度都会比 Loader 强很多。甚至,Webpack 本身的很多功能也是基于插件实现的。不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些事件钩子,执行任务。通过 plugin 可以访问 compliler 和 compilation 过程,通过钩子拦截 webpack 的执行。