这是我参与「第五届青训营 」伴学笔记创作活动的第 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
核心流程:
- 入口处理:编译入口,webpack 编译的起点,从
entry文件开始,启动编译流程。 - 依赖解析:从
entry文件开始,根据require或import等语句找到依赖资源。 - 资源解析:根据
module配置项,调用资源转移器,将图片、CSS 等非标准 JS 资源转译为 JS 内容。webpack 内部所有资源都会以 module 对象形式存在,所有关于资源的操作、转译、合并都是以 module 为基本单位进行的。 - 资源合并打包:将转译后的资源内容合并打包为可直接在浏览器运行的 JS 文件。
其中,2、3 步骤会递归调用,直到所有资源处理完毕。
使用
关于 Webpack 的使用方法,基本都围绕配置展开,而这些配置大致可划分为两类:
-
流程类: 作用于流程中某个 or 若干个环节直接影响打包效果的配置项。
- 输入: entry、context
- 模块解析: resolve、externals
- 模块转译: module
- 后处理: optimization、mode、target
- 输出:output
-
工具类: 主流程之外,提供更多工程化能力的配置项。
- 开发效率类:watch、devtool、devServer
- 性能优化类:cache、performance
- 日志类:stats、infrastructureLogging
- 首先,
npm i -D webpack webpack-cli安装。 - 定义入口和产物出口。
- 安装 loader 处理 CSS,
npm add -D css-loader style-loader。 - 安装 loader 接入 Babel,
npm i -D @babel/core ababel/preset-env babel-loader。 - 生成 HTML 需要使用的是插件,
npm i -D html-webpack-plugin。 - Hot Module Replacement(HMR) - 模块热替换。
- 命令需要带 serve:npx webpack serve。
- 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 钩子的插件方案:
- 编译过程的特定节点以钩子形式,通知插件此刻正在发生什么事情;
- 通过 tapable 提供的回调机制,以参数方式传递上下文信息;
- 在上下文参数对象中附带了很多存在 side effect 的交互接口,插件可以通过这些接口改变
与 Loader 区别
都是 Webpack 的扩展机制。
- Loader 是一个函数,负责代码的转换、编译。在 webpack 读取模块内容之后,生成 AST 语法树之前进行。操作的是文件,比如将 A.scss 转换为 A.css,是单纯的文件转换过程。
- 插件是一个类,利用 webpack 提供的 hooks,当什么时,执行什么。可以在 webpack 整个打包过程中进行。功能更强,能够在各个对象的钩子中插入特化处理逻辑,它可以覆盖 Webpack 全生命流程,能力、灵活性、复杂度都会比 Loader 强很多。甚至,Webpack 本身的很多功能也是基于插件实现的。不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些事件钩子,执行任务。通过 plugin 可以访问 compliler 和 compilation 过程,通过钩子拦截 webpack 的执行。