【面试题】工程化

Webpack-Loaders 与 Plugins 的区别?

  • 执行顺序 loaders: 自下而上,自右向左边 (compose 的函数式编程方式) plugins: 自上而下,根据顺序挂载插件钩子。
  • 作用 loaders: 用于对模块的源代码进行转换,单纯的文件转换过程 plugins: 目的在于解决 loader 无法实现的其他事
  • plugin 可以扩展 webpack 的功能,使得 webpack 更加灵活。可以在构建的过程中通过 webpack 的 api 改变输出的结果

Webpack 用过哪些 Plugins

  • html-webpack-plugin
    • 创建 HTML 页面文件到你的输出目录
    • 将 webpack 打包后的 chunk 自动引入到这个 HTML 中
  • mini-css-extract-plugin 该插件将 CSS 提取到单独的文件中。它为每个包含 CSS 的 JS 文件创建一个 CSS 文件。支持 CSS 和 SourceMap 的按需加载。
  • clean-webpack-plugin 用于删除/清理您的构建文件夹
  • optimize-css-assets-webpack-plugin 用于优化\最小化 CSS 资源,解决 extract-text-webpack-plugin CSS 重复问题问题
  • terser-webpack-plugin 最小化您的 JavaScript 代码
  • webpack-bundle-analyzer 使用交互式可缩放树图可视化 webpack 输出文件的大小。
  • happypack 通过并行转换文件使初始 Webpack 的构建速度更快
  • webpack.DllPlugin DLL(Dynamic Link Library)文件为动态链接库文件 把复用性较高的第三方模块打包到动态链接库中,在不升级这些库的情况下,动态库不需要重新打包,每次构建只重新打包业务代码
  • webpack.DefinePlugin 定义全局变量,用于处理不同环境的变量配置
  • image-minimizer-webpack-plugin 该插件使用 imagemin 优化图像。

Webpack 用过哪些 Loader

  • babel-loader ES6+ 代码转换成 ES5 浏览器支持的语法
  • css-loader 加载 CSS,支持模块化引入,es6 require
  • style-loader CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • postcss-loader 扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
  • sass|less|stylus-loader (sass|less|stylus) 编译成 css
  • vue-loader 加载 Vue.js 单文件组件
  • url-loader 代码中通过模块引入方式相对 URL 去引用输出的文件,支持转化 Base64
  • ts-loader TypeScript 转换成 JavaScript
  • @svgr/webpack 用于 SVGR 的 Webpack 加载器。

什么是 bundle,chunk,module

  • bundle 是 webpack 打包出来的文件
  • chunk 是 webpack 在进行模块的依赖分析的时候,代码分割出来的代码块,包含多个 module
  • module 是开发中的单个模块

webpack 构建的简单原理

  1. 合并配置参数 将 shell 命令的配置和 webpack 配置文件合并生成最终的配置参数。
  2. 开始编译 初始化 compiler 对象,注册所有插件,插件监听 Webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译
  3. 确定入口 entry 确认所有入口文件
  4. 编译模块 从入口出发,调用各个配置的 loader 对模块进行翻译,递归找出模块的依赖模块。
  5. 完成模块编译 第 4 步之后将会得到所有模块之间的关系,以及模块编译之后的最终内容
  6. 输出资源 根据入口和模块的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 chunk 转换成一个单独的文件并加入到输出列表中
  7. 输出完成 确认好输出内容后,根据配置确认输出的路径和文件名,把文件内容写入到文件系统中

webpack 热更新原理

  1. 通过 webapck-dev-server 负责启动一个 express 服务器监听客户端请求,webpack-dev-middleware 调用 webpack 暴露的 API 对代码变化进行监控,并且告诉 webpack 将代码打包到内存中。
  2. webapck-dev-server 对文件变化的一个监听,devServer.watchContentBase为 true 时,静态资源变触发 live reload,这儿是浏览器刷新,和 HMR 是两个概念
  3. webapck-dev-server 通过 sockjs 与浏览器建立一个长链接,将 webpack 编译的各个阶段的状态发送给浏览器,包括设置的静态文件变化。 服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
  4. 根据 webpack dev-server 的配置决定要不要走 hmr,否则直接 live reload
  5. 如果配置热更新, 则通过 JsonpMainTemplate.runtime 向服务端发起一个 ajax 请求,返回一个包含所有需要更新模块的 Hash 值的 JSON 文件,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。
  6. HotModulePlugin 针对新旧模块对比,是否要更新模块,更新的同时也会更新对应的依赖引用
  7. 如果更新失败,则执行 live reload

如何提升 webpack 构建速度

  • 多线程构建开启,或者用相关的插件 HappyPack(不维护了),thread-loader
  • 关联应用范围缩小
    • excludeinclude
    • resolve.extensions 减少后缀(jsx,tsx...)的处理
    • resolve.modules 确定第三方模块的绝对路径,减少不必要的查找
  • 利用缓存提升二次构建速度
    • cache-loader 缓存,位于 loaders 第一个(实际上是最后一个,顺序原因)
    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
  • dll 动态链 将更改不频繁的代码进行单独编译,避免反复编译浪费时间。
  • 升级最新版本
  • noParse 针对独立库不解析依赖关系,减少耗时。

如何缩小 webpack 构建文件的体积

  • mode production 自带 tree shacking
  • 文件压缩
  • 分包策略 splitChunck
  • 按需加载,动态引入
  • externals 外部引入
  • 优化静态资源大小,如图片
  • 不使用的代码可以不引入,解决方案如:babel-plugin-import
  • polyfill 按需引入(@babel-preset-env 设置 useBuiltIns: 'usage'

Tree-shaking 的原理

  • 判断 Dead Code
    • 代码不会被执行,不可到达
    • 代码执行的结果不会被用到
    • 代码只会影响死变量(只写不读)
  • 代码压缩优化工具 uglify 将Dead Code从 AST(抽象语法树)中删除

ES6 模块加载是静态的,因此依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是 tree-shaking 的基础。而且 tree-shaking 不仅支持 import/export 级别,也支持 statement(声明)级别。

Compiler 与 Compilation

  • Compiler对象包含了Webpack环境所有的的配置信息,包含optionsloadersplugins这些配置信息,这个对象在Webpack启动时候被实例化,它是全局唯一的,可以简单地把它理解为Webpack实例。
  • Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。 由 Compiler 实例化,每次构建创建一个 Compilation 实例。

配置 sourceMap 与 原理

webpack sourceMap: 配置

1
2
3
module.exports = {
  devtool: "source-map",
};
  • source-map: 会生成 map 格式的文件,里面包含映射关系的代码
  • cheap-source-map:
    • cheap 有两种作用,一是将错误只定位到行,不定位到列。
    • 映射业务代码,不映射 loader 和第三方库等。
  • cheap-module-source-map 同 cheap-source-map 只不过 module 会映射 loader 和第三方库
  • inline-source-map: 不会生成 map 格式的文件,包含映射关系的代码会放在打包后生成的代码中
  • inline-cheap-source-map: 同 inline,url 不携带 dataUrl
  • eval 每个模块都使用 eval() 执行,并且都有 //@ sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。

如何实现 webpack 持久化缓存

  • 设置 http 缓存头(Cache-Control,express 等)
  • 保证 hash 值稳定,使用 contenthash
  • 提取第三方通用代码,通过 splitChunks.cacheGroups 设置
  • css 文件缓存 MiniCssExtractPlugin filename 设置 contenthash
  • 升级最新 w 新特性

webpack 里面的插件是怎么实现的

  • webpack 本质是一种事件流机制,核心模块:tapable(Sync + Async)Hooks构造出 => Compiler(编译) + Compilation(创建bundles)
  • compiler 对象代表完整的 webpack 环境配置,在启动 webpack 的时候被创建,并配置好所有可操作的设置,如 options,loader,plugin 等,当应用一个插件时,webpack 将收到 compiler 对象的引用,可以访问 webpack 主环境。
  • compilation 代表一次资源版本构建。表示当前的模块资源,编译生成资源,变化的文件等状态信息。在构建的事件周期可以获取到 compilation 对象信息,从而进行对应的文件处理。
  • 插件函数的 prototype 定义 apply 方法,指定一个绑定到 webpack 自身的事件钩子。
  • 事件的回调内,可以获取到 compilercompilation 对象,可以处理特定的数据等操作

css-loader 与 style-loader 的区别

  • css-loader 处理 css 文件
  • style-loader 把 js 中 import 的样式代码,打包到 js 文件中,运行 js 文件时,会将样式自动插入到 style 标签中

uglify 原理

UglifyJs 是一个 js 解释器、最小化器、压缩器、美化器工具集(parser, minifier, compressor or beautifier toolkit)。 核心是对目标代码进行 AST Transformation(抽象语法树改写)

vite 工作原理,与 webpack 的区别

  • vite 的主要功能就是通过劫持浏览器的请求(使用 script)

webpack5 新特性

  • 更快的构建速度
  • 更灵活的模块组合
  • 使用持久化缓存提高构建性能
  • 更小的打包体积
  • module-federation

module-federation

让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布

Webpack 插件 ModuleFederationPlugin

  • name 当前应用名称,需要全局唯一。
  • remotes 可以将其他项目的 name 映射到当前项目中。
  • exposes 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用。
  • shared 是非常重要的参数,制定了这个参数,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM。

比如设置了 remotes: { app_tw0: "app_two_remote" },在代码中就可以直接利用以下方式直接从对方应用调用模块:

1
import { Search } from "app_two/Search";
  • 模块联邦为更大型的前端应用提供了开箱解决方案,并已经作为 Webpack5 官方模块内置,可以说是继 Externals 后最终的运行时代码复用解决方案。

webpack scope hoisting

Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快。

原理 分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,前提是不能造成代码冗余。因此只有那些被引用了一次的模块才能被合并。

由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效。

babel 编译原理

babel 的编译过程大致分为三个阶段:

babel

  • 解析 (Parsing): 代码字符解析成抽象语法树
  • 转换 (Transformation): 对抽象语法树进行转换操作
  • 生成 (Code Generation): 根据变换后的抽象语法书生成代码字符串
✏️ 如有问题,欢迎指正
上一篇 : 【面试题】浏览器下一篇 : 【面试题】服务端渲染