概念

Q1:npm 是什么?

难度:⭐

答案

npm 是 Node.js 的包管理器(Node Package Manager),是 Node.js 生态系统中用于管理和共享代码包(包括库、工具、框架等)的官方工具

它是开发者在 Node.js 环境中获取、安装、发布和管理代码包的首选工具之一

以下是 npm 的主要功能和特点:

  1. 包管理

    npm 提供了一个庞大的包仓库,其中包含了数以万计的开源代码包,涵盖了几乎所有领域的需求

    开发者可以通过 npm 来查找、安装和管理这些代码包

  2. 依赖管理

    npm 可以帮助开发者管理项目依赖,包括生产环境和开发环境中使用的依赖

    开发者可以在项目中使用 npm install 命令来安装项目所需的所有依赖

  3. 版本控制

    npm 支持语义化版本控制(Semantic Versioning),开发者可以在安装包时指定版本号,以确保代码的稳定性和兼容性

  4. 脚本执行

    npm 允许开发者在 package.json 文件中定义各种自定义脚本,例如构建、测试、部署等脚本,并通过 npm run 命令来执行这些脚本

  5. 发布和分享

    开发者可以使用 npm publish 命令将自己的代码包发布到 npm 仓库,从而与其他开发者分享自己的代码和项目

  6. 管理工具

    除了包管理之外,npm 还提供了一些其他实用的命令和工具,例如 npm outdatednpm auditnpm dedupe 等,用于检查过期依赖、安全漏洞、依赖冗余等

总的来说,npm 是一个功能强大、易用的包管理工具,为 Node.js 生态系统的发展和推广提供了重要的支持,使得开发者能够更加轻松地构建、共享和维护自己的 JavaScript 代码


Q2:对 webpack5 模块联邦的了解(Module Federation)

难度:⭐⭐

答案

Webpack 5 的模块联邦(Module Federation)是 Webpack 5 中引入的一个强大的新功能,它允许不同的应用程序共享代码和依赖

模块联邦提供了在应用之间动态加载和运行模块的能力,并且可以实现远程模块加载

这使得前端开发人员能够更好地构建微前端架构,解决大型应用程序的拆分和协作开发问题

基本概念

  1. 模块联邦
    • 模块联邦允许一个应用程序(容器应用)加载另一个应用程序的模块(远程模块),并在其内部使用
    • 这使得应用程序之间能够共享代码和依赖
  2. 容器(Container)
    • 容器应用是一个配置了 ModuleFederationPlugin 插件的 Webpack 构建
    • 容器应用可以加载远程模块并将其作为自己的模块来使用
  3. 远程模块(Remote Module)
    • 远程模块是由另一个 Webpack 构建生成的模块,可以通过模块联邦加载并使用
    • 远程模块可以在不同应用程序之间共享代码和组件
  4. 共享依赖
    • 模块联邦允许应用程序之间共享依赖,例如:React、Lodash 等第三方库
    • 共享依赖可以减少重复的库加载,提高效率

优势和挑战

  1. 优点
    • 在不同应用程序之间共享代码和依赖,提高复用性
    • 支持动态加载远程模块,增强应用的灵活性
    • 有助于实现微前端架构,分割大型应用
  2. 挑战
    • 共享依赖的版本一致性问题需要注意
    • 远程模块加载可能会遇到性能和安全问题
    • 错误处理和调试可能较为复杂
引申

配置和使用

  1. ModuleFederationPlugin 插件
    • ModuleFederationPlugin 是模块联邦的核心插件,通过配置该插件来启用模块联邦
    • 配置选项包括 name(容器应用的名称)、remotes(定义远程模块)、exposes(定义本地模块供远程加载)、shared(定义共享依赖)
  2. 配置选项
    • name:容器应用的名称,唯一标识
    • remotes:定义可以远程加载的模块,格式为 {远程模块名称: "远程容器地址"}
    • exposes:定义本地模块暴露给其他容器使用,格式为 {本地模块路径: "本地模块名称"}
    • shared:定义共享依赖,例如 {库名称: {singleton: true}} 表示共享的依赖使用单例模式

使用

  1. 加载远程模块
    • 使用动态 import 语法加载远程模块。例如:const RemoteComponent = React.lazy(() => import("远程模块名称/模块路径"));
  2. 错误处理
    • 模块联邦可能会遇到远程模块加载失败等问题。可以使用错误边界和 try-catch 块进行错误处理


Q3:介绍tree shaking 及其工作原理

难度:⭐⭐⭐

答案

Tree shaking 是一种在打包过程中移除 JavaScript 项目中未引用代码(即“死代码”)的技术

这个概念来源于 ES2015 模块的静态结构特性,它允许工具在编译时间静态分析出哪些导出和导入是真正被使用的

通过这种方式,可以显著减少最终打包文件的大小,提高应用的加载速度和性能

工作原理

Tree shaking 的工作原理基于 ES modules(即 ES6 的模块化语法),因为 ES modules 是静态的,这使得在编译阶段就能确定模块之间的依赖关系。具体来说,其工作流程分为下面几个步骤:

  1. 导入分析

    工具如 Webpack、Rollup 或其他模块打包器在处理应用程序代码时,会分析模块间的导入(import)和导出(export)语句,建立起模块之间的依赖图(Dependency Graph)

  2. 未使用代码标记

    在建立完依赖图后,打包工具会遍历这个图,标记所有实际被使用(即被其他模块导入)的代码

    同时,那些未被引用的导出(如函数、类、变量等)会被标记为未使用

  3. 移除死代码

    在最后生成打包文件的阶段,打包器会去除那些被标记为未使用的代码

    这一步骤通常需要压缩工具(如 Terser)的协助,因为 ES6 模块的导入和导出语句通常不会被直接修改

注意事项

  • ES Modules

    目前,tree shaking 主要适用于使用 ES6 模块语法(即 importexport)的代码

    对于 CommonJS 或其他非 ES6 模块系统,由于它们是动态的,这使得静态分析变得更复杂,tree shaking 的效果可能会大打折扣

  • 副作用(Side Effects)

    有些模块执行时可能会有副作用(例如,修改全局变量或立即执行的逻辑),即使这些模块没有导出或被其他模块直接引用

    打包工具不能简单地移除这些代码,因为它可能改变程序的行为

    为了处理这种情况,一些打包工具提供了配置选项(如 Webpack 的 sideEffects 标志),让开发者指明哪些文件是“无副作用”(即可以安全地移除未被引用的导出)

Tree shaking 是现代前端工程化工具中一个重要的性能优化手段,可以有效地减小应用的体积和提升加载速度

理解它的工作原理和合理地利用它,可以让我们的应用更加高效


Q4:pnpm是什么

难度:⭐⭐⭐⭐

答案

pnpm是一个高效的包管理器,用于JavaScript和Node.js,旨在解决一些npmYarn在使用过程中可能遇到的问题

它与npmYarn类似,用于自动化Node.js项目的依赖项管理和包安装过程

pnpm的主要特点包括:

  1. 节省磁盘空间和提高安装速度

    pnpm通过使用硬链接和符号链接的方式在多个项目之间共享相同的包版本,从而节省磁盘空间

    一旦一个包版本被下载到全局存储,就可以被多个项目使用而不需要重复下载

  2. 严格的依赖项隔离

    pnpm创建一个虚拟的node_modules目录,使得每个项目只能访问声明在其package.json文件中的依赖项,从而减少因依赖项错误配置而引起的问题

  3. 支持并行操作

    pnpm能够同时运行多个操作,例如安装多个包,这可以显著提升性能

  4. 钩子和自定义脚本

    允许用户执行自定义的脚本或创建钩子,在不同的包安装阶段进行额外的操作。

  5. 兼容性

    尽管pnpm使用不同的链接方式和存储结构,它仍然与包含在npm生态中的包兼容,而且可以使用package.json文件以及相应的锁文件

  6. 锁文件

    npmpackage-lock.jsonYarnyarn.lock类似,pnpm使用pnpm-lock.yaml文件来确保依赖项在不同环境中的一致安装

pnpm致力于提供一个更快、更高效且安全地处理依赖项的方法

它可以通过Node.js的包管理器(如npm)安装,命令通常是npm install -g pnpm

引申

pnpm主要解决的一些npmYarn可能遇到的问题包括:

  1. 磁盘空间使用过多

    使用npmYarn管理的每个项目都会在本地存储一个node_modules文件夹,里面包含着该项目的所有依赖包

    这意味着,如果多个项目使用同一个包,这个包会被复制存储多次,导致了磁盘空间的浪费

    pnpm采用一个全局存储方式,多个项目共享同一个包,这极大地节省了磁盘空间

  2. 严格的依赖项管理

    npmYarnnode_modules结构可能导致一些隐藏问题,如未在package.json中声明但实际上项目中用到的包,或者依赖的版本与package.json中声明的版本不符合

    这是因为npmYarn的依赖解析方式可能会导致一个包可以访问并使用到它并未直接依赖的包,而这可能带来一些不明确的行为和难以追踪的错误

    pnpm`则提供了严格的包隔离,每个包只能使用明确声明的依赖

  3. 文件系统链接

    不同于npmYarnpnpm通过硬链接和符号链接对依赖进行管理,这让它在安装速度和磁盘空间使用上更具优势

    npmYarn中,每当在项目中安装一个依赖,系统都需要完全复制该依赖包的内容,无论是否已经存在相同版本的依赖包

    pnpm可以通过链接重用已经安装的依赖包,而无需进行复制

三者之间的区别:

特性 / 工具npmYarnpnpm
依赖安装速度一般快(通过并行下载加快)快(共享存储空间)
磁盘空间利用低(每个项目的依赖被复制存储)低(每个项目的依赖被复制存储)高(通过硬链接和符号链接共享)
依赖隔离不存在(允许非直接依赖的包被使用)不存在(允许非直接依赖的包被使用)存在(只允许使用明确声明的依赖)
锁定文件存在(package-lock.json存在(yarn.lock存在(pnpm-lock.yaml
并行安装不支持支持支持
交互式工具有限丰富(如交互式升级工具)有限


Q5:AST语法树是什么

难度:⭐⭐⭐⭐⭐

答案
  1. 构成

    抽象语法树(AST)由节点组成

    每个节点代表源代码中的一个构造,如表达式、语句、声明等

    节点之间存在父子关系,反映了源代码中不同构造之间的嵌套关系

    • 根节点

      代表了整个源代码文件或一段代码的最外层

    • 内部节点

      代表了源代码中的结构性元素,如循环、条件判断、函数声明等

    • 叶子节点

      代表了源代码中的最基本元素,如变量名、常量、操作符等

  2. 作用

    AST在编程语言的编译器或解释器中扮演至关重要的角色

    它使得代码的分析、优化与转换变得可行

    在现代开发实践中,AST广泛应用于:

    • 代码编译与转换

      编译器将源代码转换成AST,再进一步转换成目标代码或中间代码

    • 代码优化

      在AST上应用各种优化策略,如消除无效代码,优化循环等

    • 静态代码分析

      通过分析AST来识别代码中的问题,如潜在的bug、性能瓶颈等

    • 代码格式化与重构

      通过修改AST来改变代码的格式化风格或进行重构

  3. 工作原理

    将源代码转换成AST的过程一般分为几个步骤:

    1. 词法分析(Lexical Analysis)

      将源代码分解为一系列的符号(tokens),如关键字、标识符、字面量等

    2. 语法分析(Syntactic Analysis)

      将这些符号构造成AST

      这一步骤涉及到语法规则的应用,判断哪些符号可以组成有效的语言构造

    3. 语义分析(Semantic Analysis)(在某些场景中)

      确保AST遵守语言的语义规则,如类型检查、作用域解析等

  4. 例子

    以JavaScript为例,源代码:

    1
    2
    3
    function add(x, y) {
    return x + y;
    }

    大致对应的AST结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    FunctionDeclaration
    |--- Identifier (name="add")
    |--- Parameters
    |--- Identifier (name="x")
    |--- Identifier (name="y")
    |--- BlockStatement
    |--- ReturnStatement
    |--- BinaryExpression
    |--- Identifier (name="x")
    |--- "+" Operator
    |--- Identifier (name="y")


Q6:package,json 文件中的 devDependencies 和dependencies 对象有什么区别

难度:⭐⭐⭐

答案
  1. dependencies

    • 用途

      dependencies 对象用于列出项目在运行时所需的依赖包

      这些包是你的应用在生产环境中正常运行所必需的

    • 安装命令

      当你运行 npm installyarn install 时,这些依赖会被安装

    • 示例

      1
      2
      3
      4
      5
      6
      7
      {
      "dependencies": {
      "react": "^17.0.2",
      "react-dom": "^17.0.2",
      "axios": "^0.21.1"
      }
      }
  2. devDependencies

    • 用途

      devDependencies 对象用于列出项目在开发时所需的依赖包

      这些包通常包括测试框架、构建工具、代码格式化工具等,它们在生产环境中并不需要

    • 安装命令

      当你运行 npm installyarn install 时,这些依赖也会被安装

      不过,如果你只想安装生产依赖,可以使用 npm install --productionyarn install --production,这时 devDependencies 中的包不会被安装

    • 示例

      1
      2
      3
      4
      5
      6
      7
      {
      "devDependencies": {
      "webpack": "^5.24.2",
      "babel-loader": "^8.2.2",
      "eslint": "^7.23.0",
      "jest": "^26.6.3"
      }

主要区别

  1. 用途不同
    • dependencies:生产环境和开发环境都需要的包
    • devDependencies:仅在开发环境中需要的包
  2. 安装时机不同
    • 默认情况下,运行 npm installyarn install 时会安装所有依赖
    • 使用 npm install --productionyarn install --production 时,只会安装 dependencies 中的包
  3. 影响打包
    • 在打包或部署应用时,通常只会包含 dependencies 中的包,以减小包的大小和提高性能


Q7:webpack5 的主要升级点有哪些

难度:⭐⭐⭐

答案
  1. 模块联邦(Module Federation)

    • 功能:允许多个独立构建的应用程序在运行时共享代码。这对于微前端架构特别有用

    • 优势:简化了代码共享,减少了重复代码,提高了应用的可维护性

  2. 持久化缓存(Persistent Caching)

    • 功能:Webpack 5 引入了持久化缓存,以提高构建性能

    • 优势:通过缓存生成的模块和 chunks,后续构建速度显著提升

  3. 更好的 Tree Shaking

    • 功能:改进了对未使用代码的检测和删除

    • 优势:减少了输出文件的大小,提升了应用的性能

  4. 内置文件系统缓存(File System Cache)

    • 功能:允许将缓存写入文件系统,以便在重启构建时读取

    • 优势:加快了开发和构建过程,特别是在大型项目中

  5. 改进的代码拆分(Code Splitting)

    • 功能:增强了代码拆分的策略和配置选项

    • 优势:更灵活地管理代码拆分,提高了应用的加载性能

  6. 默认开启的长期缓存(Long-Term Caching)

    • 功能:Webpack 5 默认启用了更好的长期缓存策略

    • 优势:通过内容哈希(content hashing)等技术,确保浏览器能够有效地缓存资源,从而减少加载时间

  7. 内置的 WebAssembly 支持

    • 功能:提供了对 WebAssembly 的更好支持

    • 优势:简化了 WebAssembly 模块的集成和使用

  8. 更好的兼容性和迁移工具

    • 功能:提供了迁移工具和文档,帮助开发者从 Webpack 4 迁移到 Webpack 5

    • 优势:减少了迁移过程中的摩擦,确保了向后兼容性

  9. 移除了 Node.js Polyfills

    • 功能:Webpack 5 不再自动为 Node.js 核心模块提供 polyfills

    • 优势:减少了打包后的代码大小,开发者需要手动引入所需的 polyfills

  10. 改进的插件和 Loader API

    • 功能:增强了插件和 loader 的 API,使其更为强大和灵活

    • 优势:开发自定义插件和 loader 更加容易,扩展性更强

  11. 更好的性能和内存优化

    • 功能:通过多项优化措施,Webpack 5 提升了整体性能和内存使用效率

    • 优势:构建速度更快,内存占用更低,特别适合大型项目

  12. 更好的错误和警告信息

    • 功能:改进了错误和警告信息的输出,使其更为清晰和易于理解

    • 优势:帮助开发者更快地定位和解决问题

  13. 实验性功能

    • 功能:引入了一些实验性功能,如模块热替换(HMR)的改进等

    • 优势:开发者可以提前试用新特性,并提供反馈


Q8:与webpack类似的工具还有哪些?区别是什么

难度:⭐⭐⭐

答案
  1. Parcel
    • 特点:零配置,开箱即用,快速的打包速度
    • 区别:相比于Webpack,Parcel更注重简化配置,适合快速原型开发和小型项目。它自动处理大多数常见的配置,减少了开发者的学习曲线
  2. Rollup
    • 特点:专注于ES模块的打包,生成更小、更优化的包
    • 区别:Rollup主要用于库的打包,它生成的代码更简洁,适合于需要发布为npm包的库。相比Webpack,Rollup的配置更简单,但功能也更专注于模块打包
  3. Gulp
    • 特点:基于流的自动化构建工具,使用代码来定义任务
    • 区别:Gulp更像是一个任务运行器,适合用来处理文件转换、压缩等任务。与Webpack的模块打包不同,Gulp更灵活,但需要手动配置更多的任务
  4. Browserify
    • 特点:将Node.js风格的模块系统带到浏览器端
    • 区别:Browserify专注于将CommonJS模块打包到浏览器中,适合于从Node.js迁移到前端的项目。相比Webpack,它的功能更单一,但也更简单
  5. Vite
    • 特点:基于ES模块的开发服务器和构建工具,快速启动和热模块替换
    • 区别:Vite利用现代浏览器的原生ES模块支持,提供更快的开发体验。相比Webpack,Vite的启动速度更快,适合现代框架如Vue 3和React


Q9:loader跟plugin的区别是什么

难度:⭐⭐⭐⭐

答案
  1. Loader

    定义

    Loader 是用于对模块的源代码进行转换的工具

    它们可以在 importrequire 模块时对模块的内容进行预处理,从而使 Webpack 能够理解和处理非 JavaScript 文件(例如,CSS、图片、字体、TypeScript 等)

    作用

    • 文件转换

      将一种文件类型转换为另一种文件类型

      例如,将 TypeScript 转换为 JavaScript,将 SCSS 转换为 CSS

    • 预处理

      在模块被打包之前对其进行预处理

      例如,代码压缩、代码检查(Linting)等

    使用方法

    Loader 在 Webpack 配置文件中的 module.rules 字段中进行配置

    每个规则包含一个或多个 loader,用于指定如何处理特定类型的文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    module.exports = {
    module: {
    rules: [
    {
    test: /\.css$/, // 匹配 .css 文件
    use: ['style-loader', 'css-loader'], // 使用 style-loader 和 css-loader
    },
    {
    test: /\.ts$/, // 匹配 .ts 文件
    use: 'ts-loader', // 使用 ts-loader
    },
    ],
    },
    };
  2. Plugin

    定义

    Plugin 是用于扩展 Webpack 功能的工具

    它们可以在 Webpack 构建过程的不同阶段执行任务,从而实现更复杂的功能

    插件可以访问 Webpack 的整个编译过程,并对其进行干预和扩展

    作用

    • 打包优化:优化打包输出,例如代码拆分、压缩、去除重复代码等
    • 资源管理:管理和处理静态资源,例如生成 HTML 文件、复制文件、生成清单文件等
    • 环境变量:定义环境变量,注入到构建过程中,以便在代码中使用

    使用方法

    Plugin 在 Webpack 配置文件中的 plugins 字段中进行配置

    每个插件通常是一个类的实例,需要通过 new 关键字来创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');

    module.exports = {
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html', // 使用模板生成 HTML 文件
    }),
    new CleanWebpackPlugin(), // 清理输出目录
    ],
    };

区别

  1. 目的不同
    • Loader:用于转换模块的源代码,使 Webpack 能够理解和处理不同类型的文件
    • Plugin:用于扩展 Webpack 的功能,能够在构建过程的不同阶段执行任务
  2. 使用位置不同
    • Loader:配置在 module.rules 中,通过 testuse 字段指定
    • Plugin:配置在 plugins 中,通过实例化插件类来使用
  3. 处理范围不同
    • Loader:处理单个文件或模块的内容
    • Plugin:可以访问和操作 Webpack 的整个编译过程,具有更广泛的影响力
  4. 实现方式不同
    • Loader:通常是一个函数,接受源代码作为输入,返回转换后的代码
    • Plugin:通常是一个类,通过 Webpack 提供的钩子机制进行工作

总结

  1. Loader:用于模块的源代码转换,配置在 module.rules
  2. Plugin:用于扩展 Webpack 功能,配置在 plugins


Q9:webpack编译生命周期

难度:⭐⭐⭐

答案
  1. 初始化阶段

    在这个阶段,Webpack 读取配置文件,解析配置项,并初始化各种内部插件和外部插件

    • 读取配置:从 webpack.config.js 文件中读取配置
    • 初始化插件:实例化配置中定义的插件,并调用插件的 apply 方法,将 Webpack 的 compiler 实例传递给插件
  2. 编译阶段

    在这个阶段,Webpack 会从入口点开始递归地解析依赖关系,并生成依赖图

    • 确定入口点:根据配置找到所有的入口点
    • 递归解析模块:从入口点开始,递归解析所有依赖的模块和子模块
    • 应用 loader:在解析模块的过程中,应用配置的 loader 对模块进行转换
    • 生成 AST:将解析的模块代码转换成抽象语法树(AST)
  3. 构建阶段

    在这个阶段,Webpack 会根据依赖图生成每个模块的最终代码,并将其打包成一个或多个文件

    • 生成模块代码:根据 AST 生成每个模块的最终代码
    • 打包模块:根据配置将模块打包成一个或多个文件
  4. 输出阶段

    在这个阶段,Webpack 会将打包好的文件输出到指定的目录

    • 写入文件:将打包好的文件写入到输出目录(通常是 dist 文件夹)
    • 生成 source map:如果配置了 source map,会在这个阶段生成对应的 source map 文件

Webpack 编译周期的详细步骤

  1. 环境准备
    • 初始化参数:合并 Shell 传入的参数、配置文件中的参数以及默认配置
    • 加载配置文件:读取并解析 Webpack 配置文件
  2. 创建 Compiler 对象
    • 初始化 Compiler 对象:根据合并后的参数初始化 Compiler 对象
    • 加载插件:调用插件的 apply 方法,将 Compiler 对象传递给插件
  3. 配置解析
    • 确定入口:根据配置找到所有的入口文件
    • 初始化 Compilation 对象:创建 Compilation 对象,管理模块的编译过程
  4. 编译模块
    • 从入口文件开始递归解析模块:使用 loader 处理模块,生成 AST
    • 解析依赖:递归解析模块的依赖,生成依赖图
    • 生成模块代码:将 AST 转换为最终的模块代码
  5. 生成依赖图
    • 根据解析的模块和依赖关系生成依赖图。
    • 优化依赖图:应用各种优化策略,如代码分割、去重等
  6. 生成 Chunk
    • 根据依赖图生成 Chunk:将模块分组,生成 Chunk
    • 优化 Chunk:应用各种优化策略,如代码分割、去重等
  7. 生成输出文件
    • 根据 Chunk 生成输出文件:将 Chunk 转换为最终的输出文件
    • 生成 source map:如果配置了 source map,会在这个阶段生成对应的 source map 文件
  8. 写入文件系统
    • 将生成的文件写入输出目录:将打包好的文件写入到配置的输出目录
  9. 完成编译
    • 触发 done 钩子:通知所有插件编译完成


Q10:webpack生命周期的钩子

难度:⭐⭐⭐

解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class MyPlugin {
apply(compiler) {
compiler.hooks.environment.tap('MyPlugin', () => {
console.log('环境配置加载之前');
});

compiler.hooks.afterEnvironment.tap('MyPlugin', () => {
console.log('环境配置加载之后');
});

compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
console.log('入口选项处理之后');
});

compiler.hooks.beforeRun.tapAsync('MyPlugin', (compiler, callback) => {
console.log('编译器运行之前');
callback();
});

compiler.hooks.run.tapAsync('MyPlugin', (compiler, callback) => {
console.log('编译器运行时');
callback();
});

compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('编译开始时');
});

compiler.hooks.thisCompilation.tap('MyPlugin', (compilation) => {
console.log('创建 compilation 对象时');
});

compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
console.log('compilation 对象创建之后');
});

compiler.hooks.make.tapAsync('MyPlugin', (compilation, callback) => {
console.log('从入口点开始解析依赖时');
callback();
});

compiler.hooks.afterCompile.tapAsync('MyPlugin', (compilation, callback) => {
console.log('编译完成之后');
callback();
});

compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
console.log('生成输出文件之前');
callback();
});

compiler.hooks.afterEmit.tapAsync('MyPlugin', (compilation, callback) => {
console.log('生成输出文件之后');
callback();
});

compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('编译完成时');
});

compiler.hooks.failed.tap('MyPlugin', (error) => {
console.log('编译失败时');
});

compiler.hooks.watchRun.tapAsync('MyPlugin', (compiler, callback) => {
console.log('监听模式下,编译器运行时');
callback();
});

compiler.hooks.watchClose.tap('MyPlugin', () => {
console.log('监听模式下,编译器停止时');
});
}
}

module.exports = MyPlugin;
答案
  1. Environment Hooks

    • environment: 在环境配置加载之前触发

    • afterEnvironment: 在环境配置加载之后触发

  2. Entry Option Hooks

    • entryOption: 在入口选项(entry options)被处理之后触发。通常用于插件初始化
  3. Initialization Hooks

    • initialize: 在 Compiler 初始化时触发
  4. Before Run Hooks

    • beforeRun: 在编译器运行之前触发

    • run: 在编译器运行时触发

  5. Compilation Hooks

    • beforeCompile: 在编译开始之前触发

    • compile: 在编译开始时触发

    • thisCompilation: 在创建 compilation 对象时触发

    • compilation: 在 compilation 对象创建之后触发

  6. Make Hooks

    • make: 在从入口点开始解析依赖时触发
  7. After Compile Hooks

    • afterCompile: 在编译完成之后触发
  8. Emit Hooks

    • emit: 在生成输出文件之前触发

    • afterEmit: 在生成输出文件之后触发

  9. Done Hooks

    • done: 在编译完成时触发

    • failed: 在编译失败时触发

  10. Watch Hooks

    • watchRun: 在监听模式下,编译器运行时触发

    • watchClose: 在监听模式下,编译器停止时触发


Q11:webpack-dev-server 的原理

难度:⭐⭐⭐⭐

答案

webpack-dev-server 是一个开发服务器,它与 Webpack 配合使用,为开发者提供实时重新加载、热模块替换(HMR)等功能

其工作原理主要包括以下几个方面:

  1. 静态文件服务

    webpack-dev-server 使用 express 或其他类似的 HTTP 服务器来提供静态文件服务

    当你启动 webpack-dev-server 时,它会在内存中编译你的 Webpack 配置,并将编译后的文件存储在内存中,而不是写入磁盘

    这使得文件的读取和写入速度更快,从而提高了开发效率

  2. 实时重新加载

    webpack-dev-server 使用 webpack-dev-middlewarewebpack-hot-middleware 来实现实时重新加载和热模块替换

    • webpack-dev-middleware

      该中间件将 Webpack 编译后的文件保存在内存中,并通过 express 提供服务

      它监听文件的变化,当源代码发生变化时,它会重新编译并将新的编译结果提供给客户端

    • webpack-hot-middleware

      该中间件实现了热模块替换(HMR),允许在不刷新整个页面的情况下替换、添加或删除模块

      它通过 WebSocket 与客户端进行通信,当检测到代码变化时,发送更新信息给客户端

  3. 热模块替换(HMR)

    HMR 是 webpack-dev-server 的核心功能之一

    它使得在不刷新整个页面的情况下,动态地替换、添加或删除模块。HMR 的工作流程如下:

    1. 编译和打包: Webpack 编译源代码,并生成模块的哈希值
    2. 监听文件变化: webpack-dev-server 监听文件变化,当检测到变化时,重新编译受影响的模块。
    3. 通知客户端: 通过 WebSocket 连接,服务器将更新信息发送给客户端
    4. 更新模块: 客户端接收到更新信息后,通过 HMR API 更新模块。对于 JavaScript 模块,HMR 会调用模块的 accept 方法,替换旧的模块。如果是 CSS 模块,HMR 会直接替换样式表
  4. 代理请求

    webpack-dev-server 可以配置代理,将特定请求转发到其他服务器

    这在开发环境中非常有用,特别是当前端和后端分离时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    module.exports = {
    devServer: {
    proxy: {
    '/api': {
    target: 'http://localhost:3000',
    changeOrigin: true,
    },
    },
    },
    };
  5. 设置和配置

    webpack-dev-server 提供了多种配置选项,可以通过 Webpack 配置文件或命令行参数进行设置

    例如,可以设置开发服务器的端口、启用 HTTPS、配置静态文件目录等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    devServer: {
    contentBase: path.join(__dirname, 'public'),
    compress: true,
    port: 9000,
    hot: true,
    open: true,
    },
    };
  6. 中间件和插件

    webpack-dev-server 还支持使用中间件和插件来扩展其功能

    例如,可以使用 connect-history-api-fallback 中间件来支持 HTML5 历史 API 路由

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const history = require('connect-history-api-fallback');

    module.exports = {
    devServer: {
    before: function(app, server) {
    app.use(history());
    },
    },
    };

总结

webpack-dev-server 通过内存编译、实时重新加载、热模块替换、代理请求等机制,极大地提升了开发体验和效率

它的核心原理在于利用 Webpack 的编译能力和中间件机制,提供一个高效、灵活的开发服务器环境


Q12:webpack的module、bundle和chunk分别指什么

难度:⭐⭐⭐

答案

在 Webpack 中,modulebundlechunk 是三个核心概念

它们在 Webpack 的打包和构建过程中扮演着不同的角色

以下是对它们的详细解释:

  1. Module(模块)

    模块是 Webpack 的基本构建单元

    每个文件(JavaScript、CSS、图片等)都可以被视为一个模块

    Webpack 使用各种加载器(loaders)来处理不同类型的文件,并将它们转换为 JavaScript 模块

    • JavaScript 模块:通过 importrequire 引入的文件
    • CSS 模块:通过 css-loaderstyle-loader 处理的 CSS 文件
    • 其他资源:如图片、字体等,通过相应的加载器处理后也会被视为模块
    1
    2
    3
    4
    5
    // example.js
    import './style.css';
    import image from './image.png';

    console.log('Hello, Webpack!');

    在这个例子中,example.jsstyle.cssimage.png 都是模块

  2. Bundle(包)

    是 Webpack 打包后的输出文件

    它包含了应用程序的所有模块以及 Webpack 运行时代码

    通常,一个 Webpack 配置会生成一个或多个包

    • 单入口点:一个入口点会生成一个包
    • 多入口点:多个入口点会生成多个包
    1
    2
    3
    4
    5
    6
    7
    8
    // webpack.config.js
    module.exports = {
    entry: './src/index.js',
    output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    },
    };

    在这个配置中,Webpack 会将所有依赖的模块打包到 bundle.js

  3. Chunk(块)

    是 Webpack 在构建过程中的中间产物

    一个块可以包含一个或多个模块

    块的概念主要用于代码分割(Code Splitting)和按需加载(Lazy Loading)

    Webpack 会根据配置和依赖关系生成多个块,并将它们打包到最终的包中

    • 入口块:由入口点生成的块
    • 异步块:通过动态导入(import())或 require.ensure 生成的块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // index.js
    import('./moduleA').then(module => {
    module.default();
    });

    // webpack.config.js
    module.exports = {
    entry: './src/index.js',
    output: {
    filename: '[name].bundle.js',
    chunkFilename: '[name].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    },
    };

    在这个例子中,index.js 是入口块,而 moduleA 是异步块

    最终会生成两个文件:main.bundle.jsmoduleA.chunk.js

总结

  • Module(模块):Web 应用程序中的独立文件,如 JavaScript、CSS、图片等
  • Bundle(包):Webpack 打包后的输出文件,包含所有模块和运行时代码
  • Chunk(块):Webpack 在构建过程中生成的中间产物,用于代码分割和按需加载


Q13:什么是CI/CD

难度:⭐⭐

答案

CI/CD 是软件开发中的一种实践和方法论,旨在提高软件开发和交付的效率、质量和可靠性

CI/CD 分为两个主要部分:

  1. 持续集成(Continuous Integration,CI)
  2. 持续交付/部署(Continuous Delivery/Deployment,CD)

以下是对这两个部分的详细解释:

  1. 持续集成(CI)

    持续集成是一种软件开发实践,开发人员频繁地将代码更改集成到共享代码库中

    每次集成后,自动化构建和测试过程都会运行,以便尽早发现和解决问题

    • 频繁提交代码:开发人员频繁地将代码提交到版本控制系统(如 Git)
    • 自动化构建:每次提交代码后,CI 服务器会自动拉取代码并进行构建
    • 自动化测试:在构建过程中,自动运行单元测试、集成测试等,以确保代码的正确性和质量
    • 快速反馈:开发人员可以快速得到构建和测试的结果,及时修复问题

    常见的 CI 工具有 Jenkins、Travis CI、CircleCI、GitLab CI 等

  2. 持续交付(CD)和持续部署(CD)

    持续交付持续部署是持续集成的延续,进一步自动化了软件的发布过程

    • 持续交付(Continuous Delivery)

      持续交付的目标是使代码在任何时候都可以安全地发布到生产环境

      虽然发布过程是自动化的,但最终的发布决策通常是手动的

      • 自动化部署:代码通过持续集成后,会自动部署到测试环境或预生产环境中
      • 发布准备:确保每个版本都经过充分测试,并且可以随时发布到生产环境
      • 手动发布:最终将代码发布到生产环境的决策是手动的,通常由开发团队或运维团队决定
    • 持续部署(Continuous Deployment)

      持续部署是持续交付的进一步扩展,目标是每次通过自动化测试的代码更改都会自动发布到生产环境中

      • 完全自动化:从代码提交到发布到生产环境的整个过程都是自动化的
      • 快速发布:每次代码更改都会尽快发布到生产环境,使新功能和修复能够迅速交付给用户
      • 高可靠性:要求高水平的自动化测试和监控,以确保每次发布都是安全和可靠的

CI/CD 的好处

  • 提高开发效率:自动化构建、测试和部署过程,减少人为干预
  • 提高代码质量:频繁集成和测试,及时发现和修复问题
  • 快速交付:缩短从代码提交到发布的时间,使新功能和修复能够更快地交付给用户
  • 降低风险:自动化测试和构建过程,减少发布过程中的人为错误和风险

总结

  • 持续集成(CI):频繁集成代码,自动化构建和测试,快速反馈和修复问题
  • 持续交付(CD):自动化部署到测试或预生产环境,手动决策发布到生产环境
  • 持续部署(CD):完全自动化,从代码提交到发布到生产环境,确保每次代码更改都能快速、安全地交付


Q14:什么是SSG

难度:⭐

答案

SSG(Static Site Generation,静态网站生成)是指在构建时预先生成静态页面,并将这些页面部署到 CDN 或者其他存储服务中,以提升 Web 应用的性能和用户体验

具体来说,SSG 的实现方式通常包括以下几个步骤:

  1. 在开发阶段,使用模板引擎等技术创建静态页面模板
  2. 将需要展示的数据从后台 API 中获取或者通过其他渠道获取,并将其填充到静态页面模板中,生成完整的 HTML 页面
  3. 使用构建工具(例如 Gatsby、Next.js 等)对静态页面进行构建,生成静态 HTML、CSS 和 JavaScript 文件
  4. 部署生成好的静态文件到服务器或者 CDN 上,以供用户访问

相比于传统的动态网页,SSG 具有如下优势:

  1. 加载速度快:由于不需要每次请求都动态地渲染页面,SSG 可以减少页面加载时间,从而提高用户体验和搜索引擎排名
  2. 安全性高:由于没有后台代码和数据库,SSG 不容易受到 SQL 注入等攻击
  3. 成本低:由于不需要动态服务器等设备,SSG 可以降低网站的运维成本和服务器负担

需要注意的是,SSG 不适用于频繁更新的内容和动态交互等场景,但对于内容较为稳定和更新较少的网站则是一个性能优化的好选择


工具

Q1:Babel 是什么?

难度:⭐

答案

Babel 是一个流行的 JavaScript 编译器,用于将 ECMAScript 2015+(ES6+)或者其他较新版本的 JavaScript 代码转换成向后兼容的 JavaScript 代码,以便在当前和旧版本的浏览器或者环境中运行

Babel 可以帮助开发者在使用最新的 JavaScript 特性的同时,确保其代码在各种环境中的兼容性

主要功能和特点包括:

  1. 转换新特性:Babel 可以将 ECMAScript 2015+(ES6+)及其以上版本的代码转换成 ES5 或者其他较旧版本的 JavaScript 代码。这包括箭头函数、解构赋值、模板字符串、类、模块等等。
  2. 插件系统:Babel 使用插件来实现不同的转换功能。它提供了大量的插件,开发者可以根据自己的需要选择并配置所需的插件。此外,Babel 还支持自定义插件,使得开发者可以根据特定需求定制转换规则。
  3. 集成工具链:Babel 可以与各种构建工具和框架(如Webpack、Rollup、Gulp等)无缝集成,使得开发者可以方便地将其包含在自己的项目中,构建出符合需求的 JavaScript 代码。
  4. 源映射支持:Babel 支持生成源映射(Source Maps),可以方便地在调试时将转换后的代码映射回原始源代码,提高了调试的效率。
  5. 支持 JSX 转换:Babel 还支持将 JSX(JavaScript XML)语法转换成普通的 JavaScript 代码,使得开发者可以在不同的环境中使用 React 或者其他支持 JSX 的库。

总的来说,Babel 是一个非常强大的 JavaScript 编译器,可以帮助开发者轻松地使用和转换最新的 JavaScript 特性,并确保其代码在各种环境中的兼容性和稳定性。


Q2:webpack proxy工作原理是什么?为什么能解决跨域?

难度:⭐⭐⭐⭐

答案

Webpack 的代理(proxy)功能通常通过 devServer.proxy 选项来配置,它主要用于在开发环境中解决跨域请求问题

以下是它的工作原理和为什么能解决跨域问题的解释:

工作原理

  1. 开发服务器

    • Webpack DevServer 是一个小型的 Node.js 服务器,主要用于开发环境
    • 当你启动 Webpack DevServer 时,它会在本地运行一个服务器,默认情况下监听 localhost 和指定的端口(例如 localhost:8080
  2. 代理配置

    • webpack.config.js 文件中,通过 devServer.proxy 选项配置代理规则

    • 代理规则定义了哪些请求需要被转发到其他服务器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      module.exports = {
      // 其他配置项
      devServer: {
      proxy: {
      '/api': {
      target: 'http://backend.server.com',
      changeOrigin: true,
      pathRewrite: { '^/api': '' },
      },
      },
      },
      };
  3. 请求转发:

    • 当浏览器向 Webpack DevServer 发起请求时,DevServer 根据代理配置检查请求路径

    • 如果请求路径匹配代理规则,DevServer 会将该请求转发到目标服务器(target

    • 例如,上述配置中,所有以 /api 开头的请求都会被转发到 http://backend.server.com

为什么能解决跨域问题

跨域请求问题通常是由于浏览器的同源策略(Same-Origin Policy)引起的,这种策略限制了从一个源(协议、域名、端口)加载的网页对另一个源的资源进行请求

Webpack DevServer 的代理功能通过以下方式解决了跨域问题:

  1. 同源请求
    • 浏览器向 Webpack DevServer 发起请求时,源是相同的(例如 localhost:8080),因此不会触发跨域限制
  2. 服务器端代理
    • Webpack DevServer 作为一个中间代理,将请求转发到目标服务器(例如 http://backend.server.com
    • 由于这是服务器到服务器的请求,不受浏览器的同源策略限制
  3. 响应转发
    • 目标服务器的响应会被 Webpack DevServer 接收,然后再转发回浏览器
    • 对于浏览器来说,响应仍然来自同一源(localhost:8080),因此不会触发跨域问题


Q3:webpack 热更新工作原理是什么?实现原理是什么?怎么做到的

难度:⭐⭐⭐⭐

答案

HMR全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用

例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失

如果使用的是 HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用

工作原理

  1. 监视文件变化
    • Webpack 在编译过程中会监视项目中的文件变化
    • 当文件发生变化时,Webpack 会重新编译受影响的模块
  2. 通知客户端
    • Webpack DevServer 使用 WebSocket 与客户端(浏览器)通信
    • 当文件变化时,DevServer 会通过 WebSocket 向客户端发送更新通知
  3. 获取更新
    • 客户端接收到更新通知后,会向 DevServer 请求更新的模块
    • DevServer 会返回更新后的模块代码和更新信息
  4. 替换模块
    • 客户端使用更新后的模块代码替换应用程序中的旧模块
    • 如果模块实现了 HMR 接口(例如,通过 module.hot.accept),它可以进行自定义的更新处理

实现原理

image-20240523120406167

  • Webpack Compile:将 JS 源代码编译成 bundle.js
  • HMR Server:用来将热更新的文件输出给 HMR Runtime
  • Bundle Server:静态资源文件服务器,提供文件访问路径
  • HMR Runtime:socket服务器,会被注入到浏览器,更新文件的变化
  • bundle.js:构建输出的文件
  • 在HMR Runtime 和 HMR Server之间建立 websocket,即图上4号线,用于实时更新文件变化

上面图中,可以分成两个阶段:

  • 启动阶段为上图 1 - 2 - A - B

在编写未经过webpack打包的源代码后,Webpack Compile 将源代码和 HMR Runtime 一起编译成 bundle文件,传输给Bundle Server 静态资源服务器

  • 更新阶段为上图 1 - 2 - 3 - 4

当某一个文件或者模块发生变化时,webpack监听到文件变化对文件重新编译打包,编译生成唯一的hash值,这个hash值用来作为下一次热更新的标识

根据变化的内容生成两个补丁文件:manifest(包含了 hashchundId,用来说明变化的内容)和chunk.js 模块

由于socket服务器在HMR RuntimeHMR Server之间建立 websocket链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash值,如下图的h属性,作为下一次热更细的标识

在浏览器接受到这条消息之前,浏览器已经在上一次socket 消息中已经记住了此时的hash 标识,这时候我们会创建一个 ajax 去服务端请求获取到变化内容的 manifest 文件

mainfest文件包含重新build生成的hash值,以及变化的模块,对应上图的c属性

浏览器根据 manifest 文件获取模块变化的内容,从而触发render流程,实现局部模块更新

关于webpack热模块更新的总结如下

  • 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
  • socket server 是一个 websocket 的长连接,双方可以通信
  • 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新


Q4:怎么编写loader跟plugin

难度:⭐⭐⭐

答案
  1. 编写自定义Loader

    一个 Webpack loader 是一个导出为函数的 JavaScript 模块

    这个函数会在 Webpack 处理模块时被调用,并接受模块的源代码作为参数

    它需要返回处理后的代码

    示例:编写一个将所有字母转换为大写的 loader

    1. 创建 loader 文件

      创建一个名为 uppercase-loader.js 的文件:

      1
      2
      3
      4
      5
      module.exports = function(source) {
      // `source` 是模块的源代码
      const transformedSource = source.toUpperCase(); // 将源代码中的所有字母转换为大写
      return transformedSource;
      };
    2. 配置 Webpack 使用自定义 loader

      webpack.config.js 中配置使用这个自定义 loader:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const path = require('path');

      module.exports = {
      module: {
      rules: [
      {
      test: /\.txt$/, // 匹配所有 .txt 文件
      use: path.resolve(__dirname, 'uppercase-loader.js'), // 使用自定义 loader
      },
      ],
      },
      };

    高级功能

    如果你需要更复杂的功能,loader 还可以使用 Webpack 提供的一些实用工具,比如 this.cacheablethis.async

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = function(source) {
    this.cacheable && this.cacheable(); // 标记结果是可缓存的
    const callback = this.async(); // 获取异步回调函数
    // 模拟异步处理
    setTimeout(() => {
    const transformedSource = source.toUpperCase();
    callback(null, transformedSource);
    }, 1000);
    };
  2. 编写自定义Plugin

    Plugin 基本结构

    一个 Webpack plugin 是一个包含 apply 方法的 JavaScript 类

    这个 apply 方法在 Webpack 构建过程中会被调用,并传入一个 compiler 对象

    你可以使用 compiler 对象提供的各种钩子来执行特定的操作

    示例:编写一个在构建开始时打印消息的 plugin

    1. 创建 plugin 文件

      创建一个名为 MyPlugin.js 的文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      class MyPlugin {
      apply(compiler) {
      // 使用 Webpack 提供的钩子
      compiler.hooks.compile.tap('MyPlugin', (params) => {
      console.log('The webpack build process is starting!!!');
      });
      }
      }

      module.exports = MyPlugin;
    2. 配置 Webpack 使用自定义 plugin

      webpack.config.js 中配置使用这个自定义 plugin:

      1
      2
      3
      4
      5
      6
      7
      const MyPlugin = require('./MyPlugin');

      module.exports = {
      plugins: [
      new MyPlugin(), // 使用自定义插件
      ],
      };

    高级功能

    你可以使用 Webpack 提供的各种钩子来执行更复杂的操作

    例如,你可以在 emit 阶段修改输出文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class MyPlugin {
    apply(compiler) {
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
    // 修改输出的文件内容
    compilation.assets['output.txt'] = {
    source: function() {
    return 'This is the new content!';
    },
    size: function() {
    return this.source().length;
    }
    };
    callback();
    });
    }
    }

    module.exports = MyPlugin;

总结

  1. Loader

    是一个函数,接受源代码作为输入,返回转换后的代码

    可以在 module.rules 中配置

    Loader 可以是同步的也可以是异步的

  2. Plugin

    是一个类,需要包含一个 apply 方法,通过 Webpack 提供的钩子机制进行工作

    可以在 plugins 中配置

    Plugin 可以在 Webpack 构建的不同阶段执行各种操作


Q5:webpack5常使用的plugin有哪些

难度:⭐⭐⭐

答案

内置插件

  1. DefinePlugin

    • 作用:定义全局常量,可以在代码中使用这些常量替换相应的值

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      const webpack = require('webpack');

      module.exports = {
      plugins: [
      new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      ],
      };
  2. HotModuleReplacementPlugin

    • 作用:启用模块热替换(HMR),在应用程序运行时替换、添加或删除模块,而无需重新加载整个页面

    • 示例

      1
      2
      3
      4
      5
      6
      7
      const webpack = require('webpack');

      module.exports = {
      plugins: [
      new webpack.HotModuleReplacementPlugin(),
      ],
      };
  3. HtmlWebpackPlugin

    • 作用:简化 HTML 文件的创建,自动注入生成的 JavaScript 和 CSS 文件

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      const HtmlWebpackPlugin = require('html-webpack-plugin');

      module.exports = {
      plugins: [
      new HtmlWebpackPlugin({
      template: './src/index.html',
      }),
      ],
      };
  4. MiniCssExtractPlugin

    • 作用:将 CSS 提取到独立的文件中,而不是嵌入到 JavaScript 中

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const MiniCssExtractPlugin = require('mini-css-extract-plugin');

      module.exports = {
      module: {
      rules: [
      {
      test: /\.css$/,
      use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      ],
      },
      plugins: [
      new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
      }),
      ],
      };
  5. CleanWebpackPlugin

    • 作用:在每次构建之前清理输出目录(如 dist 文件夹)

    • 示例

      1
      2
      3
      4
      5
      6
      7
      const { CleanWebpackPlugin } = require('clean-webpack-plugin');

      module.exports = {
      plugins: [
      new CleanWebpackPlugin(),
      ],
      };

第三方插件

  1. CopyWebpackPlugin

    • 作用:将文件或文件夹从一个地方复制到构建目录

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const CopyWebpackPlugin = require('copy-webpack-plugin');

      module.exports = {
      plugins: [
      new CopyWebpackPlugin({
      patterns: [
      { from: 'source', to: 'dest' },
      { from: 'other', to: 'public' },
      ],
      }),
      ],
      };
  2. CompressionWebpackPlugin

    • 作用:使用 gzip 或 Brotli 压缩生成的文件,以减少文件大小

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const CompressionWebpackPlugin = require('compression-webpack-plugin');

      module.exports = {
      plugins: [
      new CompressionWebpackPlugin({
      algorithm: 'gzip',
      test: /\.js$|\.css$|\.html$/,
      threshold: 10240,
      minRatio: 0.8,
      }),
      ],
      };
  3. BundleAnalyzerPlugin

    • 作用:可视化 Webpack 输出文件的大小,帮助你优化构建

    • 示例

      1
      2
      3
      4
      5
      6
      7
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

      module.exports = {
      plugins: [
      new BundleAnalyzerPlugin(),
      ],
      };
  4. TerserWebpackPlugin

    • 作用:使用 Terser 压缩和混淆 JavaScript 代码

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      const TerserWebpackPlugin = require('terser-webpack-plugin');

      module.exports = {
      optimization: {
      minimize: true,
      minimizer: [new TerserWebpackPlugin()],
      },
      };
  5. WebpackManifestPlugin

    • 作用:生成一个 manifest 文件,映射源文件到输出文件

    • 示例

      1
      2
      3
      4
      5
      6
      7
      const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

      module.exports = {
      plugins: [
      new WebpackManifestPlugin(),
      ],
      };


Q6:webpack5常使用的loader有哪些

难度:⭐⭐⭐

答案
  1. babel-loader:

    主要用于将现代 JavaScript(ES6+)代码转换为向后兼容的 JavaScript(ES5),以确保在旧浏览器中运行

  2. ts-loader:

    用于将 TypeScript 编译为 JavaScript

  3. css-loader:

    允许你在 JavaScript 中 import CSS 文件,并解析 CSS 文件中的 @importurl() 语法

  4. style-loader:

    将 CSS 插入到 DOM 中的 <style> 标签中

  5. sass-loader:

    将 SCSS/SASS 文件编译为 CSS


Q7:webpack解决了什么问题

难度:⭐

答案
  1. 模块化开发

    • 问题: 在没有模块化工具的情况下,管理和组织大量的 JavaScript 文件变得非常困难,特别是在大型项目中

    • 解决方案: Webpack 支持各种模块化标准(例如 ES6 模块、CommonJS、AMD),允许开发者将代码拆分成小的、可重用的模块

  2. 依赖管理

    • 问题: 手动管理依赖关系非常繁琐,容易出错

    • 解决方案: Webpack 自动解析模块之间的依赖关系,生成一个依赖图,从而确保所有依赖项都能正确加载

  3. 资源打包

    • 问题: 在现代前端开发中,不仅仅是 JavaScript,CSS、图片、字体等资源也需要打包和优化

    • 解决方案: Webpack 提供了各种 loader 和插件,可以处理不同类型的资源文件(如 CSS、SCSS、图片、字体等),并将它们打包到一起

  4. 代码拆分

    • 问题: 单个大的 JavaScript 文件会导致页面加载速度慢,影响用户体验

    • 解决方案: Webpack 支持代码拆分(Code Splitting),允许将代码拆分成多个小块,以便按需加载,提高页面加载速度和性能

  5. 开发体验

    • 问题: 传统的开发流程缺乏实时反馈,调试和测试不便

    • 解决方案: Webpack Dev Server 提供了热模块替换(Hot Module Replacement, HMR)功能,可以在代码修改后实时更新浏览器中的内容,而不需要手动刷新页面

  6. 优化和压缩

    • 问题: 未优化的代码会导致较大的文件体积,影响性能

    • 解决方案: Webpack 提供了多种优化插件,可以对代码进行压缩、去除无用代码(Tree Shaking)、代码分割等优化操作,减少文件体积,提高加载速度

  7. 跨平台兼容

    • 问题: 不同浏览器和平台之间的兼容性问题

    • 解决方案: 通过使用 Babel 等工具,Webpack 可以将现代 JavaScript 代码转换为向后兼容的代码,确保在不同浏览器和平台上的兼容性

总的来说,Webpack 通过模块化、依赖管理、资源打包、代码拆分、优化和提升开发体验等多个方面,极大地简化了现代前端开发流程,提高了开发效率和代码质量


流程策略

Q1:sourcemap的原理是什么

难度:⭐⭐⭐⭐⭐

答案

Source Map 是一个信息文件,它包含一些映射信息,使得调试工具在调试以源码进行编译、打包、压缩、混淆等操作后的代码时,依然能够像调试源码一样进行调试

例如,当我们使用Babel或者TypeScript这样的编译器把ES6或者TypeScript代码编译为ES5代码后,或者使用Webpack等工具对代码进行打包压缩后,代码的结构和内容都会大幅度地改变,这时候如果没有Source Map,调试工具(例如Chrome的开发者工具)只能展示编译/打包/压缩后的代码,这会大大降低我们的调试效率

而如果有了Source Map,即使代码被修改过,我们依然能找到代码执行时对应的原始源码位置

实现原理

创建Source Map的过程,可以理解为将编译、打包、压缩后的代码,重新逆向映射回源码的过程

  1. 首先,我们会有一个源代码(Source)的文件,例如index.js

  2. 在编译、打包、压缩后,我们会得到一个经过处理的代码文件,例如index.min.js

  3. 然后,在生成index.min.js的同时,编译/打包/压缩工具(Babel/Webpack/UglifyJS等)也会创建一个Source Map文件,例如index.min.js.map

    这个Source Map文件中,记录了index.min.js中的每一个位置,对应index.js中的什么位置

  4. 当我们使用调试工具打开index.min.js并进行调试时,调试工具会自动读取index.min.js.map,找到对应的源代码位置,并展示在我们的调试界面上


Q2:如果线上除了事故级别的问题,大批量的用户不能提交了,如何去处理

难度:⭐⭐⭐

答案

处理线上大批量用户提交失败的问题需要一个结构化的方法来快速定位并解决问题,同时确保用户的影响最小化

以下是解决这类问题的一般步骤:

  1. 紧急响应机制启动

    • 立即启动紧急响应队伍,确保所有相关团队成员(如开发、运维、客服)都在通知范围内并准备好响应
    • 在必要时,启用服务的维护状态或错误页面,通知用户正在处理问题
  2. 问题定位

    • 检查监控系统和报警,快速定位问题的范围和可能性区域

      重点检查新近部署的代码更改、第三方服务、数据库性能、网络问题或服务器负载

    • 查看日志文件和错误报告,尝试找到问题模式或特定的错误信息

    • 如果可能,尝试与用户沟通以收集更多问题描述和现象

  3. 快速修复或回滚

    • 根据问题的性质,决定是立即修复还是回滚到稳定的版本

      在决策时要权衡变更的风险和影响

    • 如果采取回滚,请确保理解回滚的影响,尤其是对于数据一致性的影响

  4. 沟通更新

    • 定期向内部团队和用户更新进展情况,即使是告诉大家目前还在调查中也好

      避免让用户感到被忽略

    • 如果有客户支持团队,确保他们了解情况和沟通策略,可以正确且一致地响应用户询问

  5. 问题解决

    • 一旦问题被修复,确保全面测试并监控服务恢复情况
    • 逐步放开流量,观察系统表现,确认问题彻底解决
  6. 事后复盘

    • 事故结束后,组织一个事故复盘会议,包括所有参与解决问题的成员
    • 详细记录事故发生的原因、解决过程、采取的措施和未来预防此类问题的改进措施
    • 改进监控、报警、部署流程等,以减少未来此类事件的发生
  7. *对外沟通

    • 根据情况,可能需要对外发布事故报告,包括事故原因、影响、解决措施和预防措施

整个过程中,保持冷静、迅速响应、清晰沟通是非常关键的

对用户展示出你们正认真对待问题,并采取一切必要措施以尽快解决,这对维护用户信任和品牌声誉至关重要


Q3:webpack优化前端性能的手段有哪些

难度:⭐⭐⭐⭐

答案
  1. 代码分割(Code Splitting)

    • 动态导入:使用 import() 语法进行动态导入,按需加载模块

    • 多入口文件:配置多个入口文件,生成多个 bundle

    • 分离第三方库:使用 SplitChunksPlugin 将第三方库和应用代码分离

  2. Tree Shaking

    • 描述:移除未使用的代码

    • 实现:确保使用 ES6 模块语法(importexport),Webpack 会自动进行 tree shaking

  3. 代码压缩(Minification)

    • TerserPlugin:使用 Terser 插件来压缩 JavaScript 代码

    • CSS Minification:使用 css-minimizer-webpack-plugin 来压缩 CSS 代码

  4. 缓存(Caching)

    • 内容哈希:使用 contenthash 生成文件名,这样当文件内容改变时,文件名也会改变,从而实现缓存

    • 持久化缓存:启用 Webpack 5 的持久化缓存功能,减少重新构建时间

  5. 预加载和预获取(Preloading and Prefetching)

    • Preload:使用 webpackPreload 指令,告诉浏览器尽早加载某些资源

    • Prefetch:使用 webpackPrefetch 指令,告诉浏览器在空闲时间加载资源

  6. 图片优化

    • Image Compression:使用 image-webpack-loader 等插件压缩图片资源

    • Lazy Loading:使用 loading="lazy" 属性实现图片懒加载

  7. 使用更快的解析和打包工具

    • Thread-loader:使用 thread-loader 在多线程环境中处理繁重的任务

    • 缓存加载器:使用 cache-loader 缓存编译结果,提升二次构建速度

  8. 减少编译范围

    • Include/Exclude:在 loader 配置中使用 includeexclude 选项,限制处理文件的范围

    • Resolve Aliases:使用 resolve.alias 配置简化模块路径解析

  9. Bundle 分析

    • Webpack Bundle Analyzer:使用 webpack-bundle-analyzer 插件生成包大小报告,识别和优化过大的模块
  10. DLLPlugin

    • 描述:预编译特定的第三方库,减少每次构建时的打包时间

    • 实现:使用 DllPluginDllReferencePlugin 配置


Q4:Vite 和 Webpack 的区别

难度:⭐⭐

答案

Vite 和 Webpack 都是前端打包工具,它们的作用类似,但实现方式和使用方法有所不同

以下是它们之间的一些区别:

  1. 构建速度

    Vite 的构建速度比 Webpack 更快

    因为 Vite 在开发环境下使用了浏览器原生的 ES 模块加载

    而不是像 Webpack 一样使用打包后的文件进行模块加载

    在 Vite 中,每个模块都可以独立地进行编译和缓存,这意味着它只需要重新编译修改过的模块,而不是整个应用程序

    这使得 Vite 开发起来更加高效

  2. 配置复杂度

    Vite 的配置相对更简单,因为它无需进行大量的配置,只需指定一些基本的选项就可以开始开发

    Webpack 的配置更加复杂,需要针对具体项目进行不同的配置,且需要理解各种插件、Loader 等概念

  3. 生态环境

    Webpack 的生态环境更加成熟,在社区中拥有广泛的支持和丰富的插件库。而 Vite 尚处于发展阶段,尽管其已经获得了很多关注,但其生态系统仍然不太完善。

  4. 功能特性

    Webpack 是一个功能更加全面的打包工具

    支持各种 Loader 和插件,可以处理多种类型的文件和资源

    而 Vite 的设计初衷是专注于开发环境下的快速构建,因此其对一些高级特性的支持相对较少

综上所述,Vite 更适合用于开发环境下的快速构建,而 Webpack 则更适合用于生产环境下的复杂应用程序的打包处理


新技术趋势

Q1:微前端中的应用隔离是什么,一般怎么实现的

难度:⭐⭐⭐⭐⭐

答案

微前端是一种类似于微服务的架构,它将不同的前端应用(通常由不同的团队开发和维护)集成在同一用户界面中。每个应用作为一个独立的整体存在,可以各自独立部署、升级,互不干扰

在微前端架构中,”应用隔离”是一个重要的概念

应用隔离主要指的是在微前端的环境下,各个子应用需要在运行时保持状态和生命周期的隔离,防止相互之间的干扰

主要体现在以下三个方面:

  1. JS隔离

    避免各个应用之间的全局变量、事件等资源的冲突

    • 可以通过 JavaScript 的闭包或者 IIFE(立即执行函数表达式)来实现变量的隔离
    • 另外也可以通过使用 Webpack 打包,把全局变量封装在模块作用域中,防止全局污染
  2. CSS隔离

    防止子应用的CSS样式之间的污染

    • 可以使用CSS Modules,CSS in JS等技术,把样式限制在组件或者模块作用域内
    • 另外,Shadow DOM也是一种可以实现样式隔离的方式,但在一些老的或者特定的浏览器环境下可能会存在兼容性问题
  3. DOM隔离

    每个子应用有自己独立的根节点,防止不同子应用间DOM操作相互干扰