# 3.前端工程化

webpack参考学习资料 (opens new window)

# 1.Webpack构建流程

  • 1.初始化配置参数:从配置文件和shell语句中读取与合并参数,得出最终的参数;

  • 2.开始编译:用上一步得到的参数初始化一个Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译

  • 3.确定入口:从配置中的entry找出所有的入口文件

  • 4.模块解析:从入口文件开始,递归解析出文件中所依赖的所有模块

  • 5.模块加载:对于每个依赖的模块,使用相应的loader处理它们。例如:babel-loader将ES6代码转成ES5代码

  • 6.依赖图构建:将所有的模块和它们之间的依赖关系表示为一个依赖图

  • 7.输出资源生成:根据依赖图和配置,生成一系列的输出资源,例如:bundle.js

  • 8.输出完成:根据配置确定输出的路径和文件名,把生成的输出资源内容写入到文件系统,通常是dist文件夹

# 2.webpack打包优化的方式

参考链接:学习 Webpack5 之路(优化篇) (opens new window)

分为优化打包速度和优化打包后的体积。

# 1.优化打包体积

  • 1.代码分割:Webpack 可以将代码分割成不同的块,实现按需加载,提高应用性能。

  • 2.压缩:通过插件,如 TerserWebpackPlugin,Webpack 可以压缩 JS 代码,减小文件大小。

  • 3.Tree Shaking:移除未使用的代码,减少最终包的体积。

  • 4.CDN 加载:将常用的库(如 React, Vue, Lodash)通过 CDN 加载,不打包进主文件。

  • 5.优化 Source Map:在生产环境中禁用 source map 或使用更轻量级的 source map 选项。

  • 6.分析打包结果:使用 webpack-bundle-analyzer 分析打包结果,找出体积大的部分。

# 2.优化打包速度

  • 1.使用缓存:直接配置cache属性缓存模块的输出,第二次构建能大幅提升构建速度,大约能提升10倍,具体依靠场景而定,比较理想的场景可以从5秒到0.5ms,这个优化对于构建速度来说是非常大的优化。
module.exports = {
    cache: {
        type: 'filesystem'
    }
}
  • 2.优化 Loader 搜索范围:确保 loader 只作用于必要的文件.使用包括 exclude(排除文件夹) 或 include(指定哪些文件夹下的内容被 Loader 处理) 选项,

  • 3.使用多线程打包的方法:使用 thread-loader 来并行处理任务。

  • 4.减少解析:减少 Webpack 的解析工作量,使用 alias 配置项为常用模块提供别名,webpack解析的时候可以直接通过别名定位到路径。

# 3.Tree-Shaking

  • Tree-Shaking 是一种通过清除无用代码来减少最终打包文件大小的优化技术。

  • 工作原理

  • 1.静态导入分析:Tree-Shaking 的核心是对 ES6 模块的静态结构进行分析。ES6 模块的导入(import)和导出(export)语句是静态的,这意味着它们不可以在运行时改变。这种静态性使得打包工具能够在构建阶段确定每个模块是否被使用。

  • 2.标记未使用代码:在构建过程中,打包工具会分析代码,标记所有未被使用的模块或导出。这一过程依赖于代码的依赖树。

  • 3.移除未使用代码:在最后的打包阶段,所有被标记为未使用的代码会被移除。这通常是通过压缩工具(如 Terser)实现的,这些工具能够移除未被引用的代码。

# 4.Webpack热更新原理

  • 热更新使用环境:开发环境

# 1.基本原理

  • 服务器和客户端:热更新通过 WebSocket 连接服务器和客户端(浏览器)。Webpack Dev Server(WDS)在服务器端运行,负责触发整个热更新流程。

# 2.文件监听

  • 当源代码发生变化时,WDS 会监听到这些变化。通常是通过轮询文件系统来检测文件是否被修改。

# 3.构建更新

  • 一旦检测到文件变化,WDS 会重新构建发生变化的模块,并创建一个所谓的 "hot update chunk"(包含更新的模块)。

# 4.通知客户端

  • WDS 通过 WebSocket 发送消息到客户端,告知有更新的模块。

# 5.下载更新

  • 客户端(浏览器)接收到更新消息后,会通过 JSONP 请求从服务器下载这些更新。

# 6.替换模块

  • 一旦更新的模块被下载,webpack的模块热替换(HMR)功能会处理模块的替换操作。

# 7.更新应用状态

  • 在这个过程中,应用的当前状态通常会通过使用模块内部的状态管理或 React 的状态保持功能来保持。

# 5.你使用过哪些Plugin

  • 1.terser-webpack-plugin:该插件使用 terser 来压缩 JS

  • 2.html-webpack-plugin:简化 HTML 文件创建,以便为打包出来的webpack其他文件服务

  • 3.eslint-webpack-plugin:在 Webpack 的构建过程中运行 ESLint,检查你的代码是否符合规范

  • 4.progress-bar-webpack-plugin:在构建过程中显示一个进度条

  • 5.mini-css-extract-plugin:将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 按需加载

  • 6.css-minimizer-webpack-plugin:优化和压缩CSS

# 6.你使用过哪些Loader

  • 1.babel-loader:用于将ES6转成ES5

  • 2.css-loader:用于处理css,会对 @import 和 url() 进行处理

  • 3.style-loader:用于将css插入到html中

  • 4.postcss-loader:用于将css添加浏览器前缀,确保你的 CSS 兼容各种浏览器。

  • 4.less-loader:用于将less转成css

  • 5.url-loader:用于将图片转成base64

  • 6.file-loader:用于将图片转成文件

# 7.说一说Loader和Plugin的区别

  • 1.Loader本质上是一个函数,因为有些资源Webpack不认识,需要Loader进行翻译处理,对其他类型的资源做转译工作,返回转换后的结果。然后Loader是在module.rules中配置,类型是一个数组,然后每一项是一个对象,可以配置对应的loader和规则。

  • 2.Plugin是插件,插件可以用来扩展Webpack的功能,例如上面说到的简化 HTML 创建的插件,都是属于扩展Webpack功能的。Plugin是单独配置的,类型也是一个数组,每一项是一个Plugin实例,参数通过构造函数传入。

# 8.Loader Plugin 原理

# 1.Loader 原理

  • 1.转换文件:Loader 用于转换特定类型的文件。Webpack 本身只理解 JavaScript,而 loader 可以将其他类型的文件(如 CSS, HTML, 图片等)转换为有效的模块,供应用程序使用和/或添加到依赖图中。

  • 2.链式传递:Loader 可以链式调用。链中的每个 loader 都对接收到的资源(文件内容)进行转换,然后传递给下一个 loader。在链的最后,经过所有 loader 处理的最终结果会交给 Webpack。

  • 3.同步或异步:Loader 可以是同步或异步的。

  • 4.配置:在 Webpack 的配置文件中,通过 module.rules 数组配置 loader。规则中指定了针对资源文件的测试条件(通常是文件类型),以及应用于这些文件的 loader。

# 2.Plugin 原理

  • 1.扩展 Webpack:Plugin 可以扩展 Webpack 的功能。它们直接访问 Webpack 的内部 API 并且可以访问编译过程(compile process)的所有阶段。

  • 2.事件钩子:Webpack 的编译过程提供了一系列的事件钩子。Plugin 通过在这些事件钩子上注册自己的工作,能够在特定的时点执行操作。

  • 3.执行自定义任务:Plugin 可以执行范围广泛的任务,包括打包优化、环境变量注入、生成 HTML 文件等。

  • 4.配置方式:在 Webpack 配置的 plugins 数组中添加 plugin 实例。Plugin 需要使用其构造函数创建实例,并可能接受选项参数。

# 9.Babel 原理

  • Babel 是一个广泛使用的 JS 编译器,主要用于将 ES6+ 代码转换为向后兼容的 JS 版本。这对于开发现代 JavaScript 应用是非常重要的,因为它允许开发者使用最新的 JavaScript 语言特性,同时确保代码能在旧版浏览器上运行。理解 Babel 的原理可以帮助更好地使用和配置它。

babel核心处理流程:1.将源代码解析成AST 2.转换AST为需要的样子 3.生成AST为源码

# 1.Babel 的工作原理

  • 1.解析(Parsing)

  • 词法分析(Lexical Analysis):这一阶段将源代码字符串分解成一个个令牌(Tokens)。令牌是代码的最小单位,例如数字、字符串、标识符、关键字、符号等。

  • 语法分析(Syntactic Analysis):词法分析之后,Babel 会进行语法分析,将令牌转换成抽象语法树(AST)。AST 是代码的深层结构表示,它以树的形式展示代码的语法结构。

  • 2.转换(Transformation)

  • 在这一阶段,Babel 接收到 AST 并对其进行遍历,应用各种插件和预设(presets)对树进行修改。这些变化可能包括新语言特性的转换、API 的替换等。

  • 3.生成(Code Generation)

  • 最后一阶段是代码生成。Babel 将经过转换的 AST 转换回普通的 JavaScript 代码。这个过程包括将 AST 转换成字符串形式的代码,并创建源码映射(source maps)。

# 2.Babel 的配置

  • 1.插件(Plugins):Babel 通过插件来扩展其功能。这些插件可以控制如何转换代码,例如转换 JSX 或特定的 ES6+ 特性。

  • 2.预设(Presets):预设是一组插件的集合。它们简化了配置过程。常用的预设包括 @babel/preset-env(根据目标环境自动决定需要的 Babel 插件)和 @babel/preset-react(用于转换 React 的 JSX)。

# 3.使用场景

  • 1.语言特性转换:将 ES6+ 代码转换为更广泛兼容的 ES5 代码。

  • 2.浏览器兼容性:确保 JavaScript 代码可以在旧版浏览器上运行。

  • 3.React 开发:转换 JSX 代码为普通的 JavaScript。

# 10.Vite为什么比Webpack快?

# 1. 基于 ES Modules 的开发服务器

  • Vite:在开发模式下,Vite 利用原生 ES Modules 进行模块加载。它允许浏览器直接导入模块,这减少了需要构建的工作量,因为浏览器可以直接解析这些模块。这意味着无需额外的打包步骤,从而大大提高了启动速度和模块热更新(HMR)的速度。

  • Webpack:在开发过程中会对整个应用程序进行打包,这包括模块打包、代码转换和长期缓存优化。这些步骤在项目复杂时可能会变得缓慢。

# 2.按需编译

  • Vite:仅对实际请求的模块进行编译,而不是在启动时编译整个应用程序。这减少了初始加载时间,并提高了响应速度。

  • Webpack:在开发模式下,Webpack 需要处理整个代码库,即使对于小改动也是如此。虽然它提供了模块热替换(HMR),但是整体的构建时间通常比 Vite 长。

# 3.更快的热模块替换(HMR)

  • Vite:由于使用原生 ESM 和按需编译,Vite 能够更快地实现热模块替换。只有实际更改的模块会被重新请求和更新。

  • Webpack:虽然也支持 HMR,但在处理大型项目时,更新的速度可能会更慢,因为它需要处理更多的代码和依赖。

# 4.冷启动优化

  • Vite:在冷启动时,由于使用了原生 ESM,Vite 可以迅速启动,尤其是在大型项目中。

  • Webpack:在大型项目中,冷启动可能会比较慢,因为需要加载和处理整个应用的所有依赖和资源。

# 11.Es Module 和 CommonJs的区别

# 1.语法

  • ES Module: 使用 import 和 export 语句。

  • CommonJS: 使用 require() 和 module.exports。

# 2.加载方式

  • ES Module: 支持静态导入和动态导入。静态导入是在文件的顶部声明的,允许编译器优化,如树摇(tree-shaking)。

  • CommonJS: 模块加载是动态的,发生在代码运行时。这种方式使得静态分析和优化更加困难。

# 3.模块解析

  • ES Module: 输出是值的引用,当导出的值变化时,导入的值也会变化。

  • CommonJS: 输出是值的拷贝,一旦导入,后续导出值的变化不会反映在已导入模块上。

# 4.跨平台兼容性

  • ES Module: 原生支持于现代浏览器和较新版本的Node.js。

  • CommonJS: 主要用于Node.js,虽然可以通过工具(如Webpack)在浏览器中使用。

# 5.生态系统

  • ES Module: 现代前端框架和库趋向于使用ESM。

  • CommonJS: 在Node.js生态中仍然非常普遍,尤其是旧代码和库。