面试-构建工具

30 阅读8分钟

前端模块化

概念

  • 是一种将前端代码拆分成多个独立、可复用模块的开发方式
  • 以提高代码的可维护性、可读性和可复用性

背景

没有模块化的时代,前端代码会通过引用多个<script>进行引入,容易导致一些问题

  • 全局作用域污染:全局变量容易被不同js文件修改
  • 依赖管理困难:文件之间的依赖关系混乱,手动管理顺序
  • 代码复用性差:js文件无法直接引用其他js文件中的函数
  • 可维护性差:随着项目规模增大,代码难以维护

模块化通过将代码拆分成独立的模块,解决了这些问题。

核心概念

  • 模块:一个独立的文件或代码块,包含特定功能或逻辑
  • 导出:将模块中的变量、函数、类暴露给其他模块使用
  • 导入:引入其他模块导出的内容
  • 依赖管理:明确模块之间的依赖关系,确保模块按需加载

解决方案

  • 命名空间:使用对象封装变量,函数

  • 立即执行函数:隔离作用域,实现变量私有化,避免全局污染

  • CommenJS

    • Node.js采用的模块化规范
    • 使用require进行导入,module.exports进行导出
    • 同步加载,适合服务端
  • AMD

    • 通过define定义模块,使用require加载模块
    • 异步加载,适合浏览器端
  • UMD

    • 结合了CommenJS和AMD
    • 跨平台:能同时在浏览器和Node.js环境中使用
    • 代码封装复杂
  • ESM

    • 原生支持模块化,无需额外库
    • 语法简单:使用import/export导入/导出
    • 支持静态分析,在编译阶段就能确定依赖关系

实践

  • ESM:使用模块化,更好的代码组织、便于扩展,实现代码复用,提高可维护性
  • TreeShaking:通过静态分析,移除未使用的代码
  • 动态导入:通过import实现按需加载
  • 打包工具:使用Webpack、Rollup、Vite等工具打包模块

Webpack

概念

  • Webpack是一个静态模块打包工具
  • 将JS、CSS、图片等资源文件视为模块
  • 并根据模块的依赖关系,将它们打包为浏览器可执行的静态文件

核心概念

  • 入口(entry):构建内部依赖图的起点

  • 输出(output):将打包后文件输出到指定目录

  • loader

    • Webpack只能理解JS、JSON文件,loader能让Webpack能够处理其他类型的文件,将它们转化为有效模块

      • ts转化为js,css转化为js,图片转化为base64
  • plugin:执行范围更广的任务

    • 打包优化
    • 资源管理
    • 注入环境变量

工作流程

  • 初始化:读取配置文件,命令行参数,合并成配置对象

  • 构建

    • 解析入口:从入口开始,递归加载所有依赖模块,构建模块依赖图
    • 应用loader:对每个模块应用对应的loader,将其转化为有效的模块
  • 输出:根据配置文件和依赖图,生成最终的打包文件,输出到指定目录

优化webpack构建速度

  • 设置配置项

    • 使用 resolve.aliasresolve.extensions
    • 使用 includeexclude 限制 Loader 作用范围,从而减少文件搜索范围
    • 禁用sourceMap
    • 代码分割:使用splitChunks提取公共代码(vue及相关的库)至vendor,使用import按需加载
  • 通过Loader

    • 使用 thread-loader 进行多线程构建

    • 使用esbuild-loader代替babel-loaderts-loader

      • 支持更快的ES6+语法,TS转译和JS压缩
  • 通过Plugin

    • 使用 DllPluginDllReferencePlugin 预编译依赖

      • DllPlugin:生成一个manifest.json,让DllReferencePlugin能够映射到相应的依赖上
      • DllReferencePlugin:把xx引用到需要预编译的依赖中
      • 通过引用dll的manifest文件,把依赖名称映射到模块的id上,之后在需要时require对应的模块
    • 使用TerserPlugin进行JS压缩,替代Webpack自带的JS压缩

      • 开启多线程
      • 去除console.log
      • 剥离/删除注释
    • hardSourceWebpackPlugin 缓存构建结果,避免重复编译未变化的文件

减少webpack打包文件体积

  • 启用TreeShaking:删除未使用的代码

    • 使用ESM进行导入导出
    • mode: production或直接使用插件
  • 代码分割:将代码分割成多个chunk,按需加载,减少初始加载文件的体积

    • 通过splitChunks提取公共依赖
    • 按需加载
  • 压缩代码

    • 开启TerserPlugin压缩JS代码
    • CssMinimizerWebpackPlugin压缩CSS代码
    • ImageMinimizerWebpackPlugin压缩图片
  • 使用esbuild-loader代替babel-loaderts-loader

  • 按需加载(ElementUI、Lodash-es)

  • 使用更小的库(day.js代替moment.js)

  • CDN

    • 使用externals排除不需要打包的依赖
  • Gzip压缩,生成.gz文件,确保服务器支持

  • 关闭sourceMap

webpack怎么实现HMR

概念

  • HMR是webpack提供的一项开发优化功能
  • 在不刷新整个页面的情况下,更新修改的模块,极大提升开发体验和效率。

实现

依赖Webpack Dev Server(Websocket)

  • 文件更改:当开发者修改并保存代码时,webpack检测文件变化
  • 重新编译:webpack重新编译修改的模块,并生成一个更新补丁
  • 通知客户端:WDS通过Websocket向浏览器发送更新通知
  • 接收更新:浏览器中的 HMR Runtime 接收到更新通知,并通过HTTP请求获取更新补丁
  • 替换模块:HMR Runtime 使用新模块替换旧模块
  • 更新页面

Vite

概念

  • 由Vue团队开发的新一代前端构建工具
  • 利用ESM和原生浏览器的能力,提供更快的冷启动和热更新,大幅提升开发体验

和webpack的区别

  • 冷启动更快

    • Vite利用现代浏览器都支持ESM,按需加载模块
    • webpack分析依赖图,应用loader,打包所有依赖后启动
  • 热更新更快

    • Vite使用使用ESM动态导入,并对源码进行协商缓存,依赖进行强缓存
    • webpack使用HMR(WDS利用Websocket),更新速度会随应用的规模的增长而显著下降
  • 预构建方式不同,启动更快

    • Vite默认使用esbuild进行预构建(GO)
    • Webpack默认使用babel-loader(JavaScript)
  • 打包方式不同

    • Vite使用Rollup进行打包,Vite本身使用ESM,TreeShaking机制更加彻底
    • webpack自己的打包机制:代码分割、代码压缩、动态加载

vite优势

  • 更快的冷启动

    • Vite将应用的模块区分为依赖源码两类,改进了开发服务器的启动时间

      • 依赖

        • 大多为开发时不会变动的代码(JS)
        • Vite会使用esbuild预构建依赖
      • 源码

        • 通常是各种文件(JS、CSS、Vue、png、...)时常会被编辑
        • 并不是所有的源码都需要同时加载
        • Vite以ESM方式提供源码,只需要在浏览器请求源码时进行转换,并按需提供源码
  • 更快的热更新

    • HMR的更新速度,会随着应用规模的增长而显著下降

    • 在Vite中,HMR是在ESM上执行的。当编辑一个文件时,Vite只需要精确地使已编辑的模块与其最近的HMR边界之间的链失活(本身)

    • Vite利用HTTP头进行缓存

      • 依赖进行强缓存
      • 源码进行协商缓存
  • 使用Rollup进行打包

Vite预构建是什么

概念

  • Vite启动时的一个优化步骤

  • 预先处理第三方依赖

    • 如果是CommenJS和UMD形式的依赖,会将其转化为ESM
  • 使用esbuild编译这些依赖,并输出到node_modules/.vite

重新预构建(依赖发生变化,删除.vite目录,启动时使用--force

优化Vite

  • 检查浏览器设置

    • 关闭不需要的浏览器插件
    • 确保Network没有启用“disable cache”
  • 检查Vite插件

    • 社区插件的性能无法保证
  • 减少解析操作

    • 导入文件时,加上后缀名,以减少解析路径所需次数和时间
  • 避免使用桶文件

    • 不方便TreeShaking
  • 预热常用文件(server.warmup)

    • 将常用的utils文件放入配置里

Rollup

概念

  • 用于JS的模块打包工具
  • 利用ESM,进行高效的TreeShaking,生成的代码更简洁,体积更小
  • 适合构建JS库或应用程序

特点

  • 基于ESM,支持TreeShaking
  • 打包后代码更简洁,体积更小
  • 支持多种输出格式(ESM,CommenJS,UMD)
  • 插件丰富

核心概念

  • 入口(input):构建依赖图的入口
  • 输出(output):打包后输出的目录
  • plugins:扩展Rollup的功能
  • TreeShaking:通过静态代码分析,去除未使用的代码

工作流程

  • 读取配置文件

  • 解析入口文件

    • 从入口开始,递归解析依赖关系,生成依赖图
  • TreeShaking

    • 通过静态代码分析,去除未使用的代码
  • 打包模块

    • 根据配置文件和依赖关系,对文件进行打包
  • 输出文件

    • 调用插件对打包文件进行优化

和webpack的区别

功能上

  • Rollup功能少,要实现改为丰富的功能,依赖插件
  • webpack功能丰富(公共代码提取、按需导入,代码分割,loader)

模块系统

  • Rollup只支持ESM,CommenJS需要引入插件
  • Webpack支持ESM、CommenJS

TreeShaking

  • Rollup默认支持
  • Webpack需设置mode: producion,或引入Plugin

开发服务器

  • Rollup:开发环境相对简单
  • webpack:webpack-cli、WDS提供强大的开发服务器和热更新

应用场景

  • Rollup:npm库,工具库等纯JS代码
  • Webpack:需要开发环境,处理各种类型文件的复杂应用

为什么Vite选择Rollup进行打包

  • Rollup打包生成的文件小

    • 没有额外的runtime代码
    • ESM和TreeShaking非常适合
  • Vite开发阶段不需要复杂的loader

    • 使用ESM和esbuild预构建依赖
    • 按需加载源码
    • 直接让浏览器解析非JS文件(.css、.vue、.png)
  • Vite用插件处理非JS文件