Posted in

【第 3340 期】前端构建系统概述_AI阅读总结 — 包阅AI

包阅导读总结

1. 关键词:前端构建系统、转译、打包、压缩、开发者工具

2. 总结:本文介绍了前端构建系统,包括其概念、构建步骤(转译、打包、压缩)、常用工具和趋势,还提及开发者工具如元框架、源映射等,阐述了构建系统中的各种问题及解决方案。

3. 主要内容:

– 前端构建系统概述

– 概念及构建步骤

– 现代前端开发需要构建步骤的原因

– 构建步骤

– 转译

– 解决语言特性问题

– 常用转译器及特点

– 打包

– 解决网络请求和瀑布效应问题

– 常用打包器及特点

– 代码分割

– 解决大型应用程序包过大问题

– Next.js 的文件系统路由器优化

– 树摇(Tree Shaking)

– 优化包大小

– 影响其效率的因素

– 压缩

– 解决文件过大问题

– 常用压缩器

– 开发人员工具

– 元框架

– 源映射

– 热重载

– 热模块替换

– 无包范式

– 单体代码库

– 趋势

– 新构建工具强调性能但功能集小

– 服务器端渲染流行但对构建系统无根本差异

思维导图:

文章地址:https://mp.weixin.qq.com/s/e5sHD_QrDBMhNkPzIcdBXA

文章来源:mp.weixin.qq.com

作者:azuline

发布时间:2024/8/9 0:02

语言:中文

总字数:5431字

预计阅读时间:22分钟

评分:80分

标签:前端构建系统,转译,打包,压缩,性能优化


以下为原文内容

本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com

前言

介绍了前端构建系统的概念、构建步骤(包括转译、打包和压缩)、开发者工具以及当前的前端构建趋势。今日前端早读课文章由 @飘飘翻译分享。

正文从这开始~~

开发者编写 JavaScript;浏览器运行 JavaScript。从根本上说,前端开发不需要任何构建步骤。那么,为什么在现代前端开发中需要构建步骤呢?

随着前端代码库的不断扩大,以及开发者舒适度变得越来越重要,直接将 JavaScript 源代码发送到客户端会导致两个主要问题:

  • 不受支持的语言特性:由于 JavaScript 是在浏览器中运行的,而且市面上有各种版本的浏览器,因此您使用的每个语言特性都会减少能够执行您的 JavaScript 的客户端数量。此外,像 JSX 这样的语言扩展并非有效的 JavaScript 代码,在任何浏览器中都不会运行。

  • 性能:浏览器必须逐个请求每个 JavaScript 文件。在大型代码库中,这可能导致成千上万次的 HTTP 请求来渲染一个页面。在过去,在 HTTP/2 之前,这还会导致成千上万次的 TLS 握手。

此外,可能需要几次连续的网络往返才能加载所有 JavaScript。例如,如果 index.js 导入 page.js,而 page.js 导入 button.js,则需要三次连续的网络往返才能完全加载 JavaScript。这被称为瀑布效应。

源文件还可能由于长变量名和空白缩进字符而变得不必要地庞大,增加了带宽使用量和网络加载时间。

前端构建系统会处理源代码,并生成一个或多个经过优化的 JavaScript 文件,以便发送到浏览器中。最终生成的可分发文件通常是无法被人类阅读的。

构建步骤

前端构建系统通常包括三个步骤:转译、打包和压缩。

有些应用程序可能不需要所有三个步骤。例如,较小的代码库可能不需要打包或压缩,开发服务器可能为了性能会跳过打包和 / 或压缩。还可以添加额外的自定义步骤。

某些工具实现了多个构建步骤。值得注意的是,打包器通常实现所有三个步骤,单独一个打包器就足以构建简单的应用程序。复杂的应用程序可能需要专门的工具来执行每个构建步骤,以提供更大的功能集。

转译

转译通过将用现代 JavaScript 标准编写的 JavaScript 转换为旧版 JavaScript 标准来解决不受支持的语言特性问题。如今,ES6/ES2015 是一个常见的目标。

框架和工具也可能引入转译步骤。例如,JSX 语法必须转译为 JavaScript。如果一个库提供了 Babel 插件,这通常意味着它需要一个转译步骤。此外,像 TypeScript、CoffeeScript 和 Elm 这样的语言也必须转译为 JavaScript。

CommonJS 模块(CJS)也必须转译为浏览器兼容的模块系统。自从 2018 年浏览器广泛支持 ES6 模块(ESM)以来,通常建议转译为 ESM。由于 ESM 的导入和导出是静态定义的,因此它更容易优化和 tree-shake 清理。

目前常用的转译器是 Babel、SWC 和 TypeScript 编译器。

  • Babel (2014) 是标准的转译器:一个用 JavaScript 编写的单线程转译器。许多需要转译的框架和库都通过 Babel 插件进行转译,因此 Babel 是构建过程中不可或缺的一部分。然而,Babel 很难调试,常常令人困惑。

  • SWC (2020) 是一个用 Rust 编写的快速多线程转译器。据称其速度比 Babel 快 20 倍;因此,它被较新的框架和构建工具所使用。它支持转译 TypeScript 和 JSX。如果你的应用程序不需要 Babel,那么 SWC 是一个更好的选择。

  • TypeScript 编译器 (tsc) 也支持转译 TypeScript 和 JSX。它是 TypeScript 的参考实现,也是唯一的全功能 TypeScript 类型检查器。然而,它非常慢。尽管 TypeScript 应用程序必须通过 TypeScript 编译器进行类型检查,但在构建步骤中,使用替代转译器会更高效。

如果你的代码是纯 JavaScript 并且使用 ES6 模块,则也可以跳过转译步骤。

对部分未得到支持的语言特性的替代解决方案是使用 “polyfill”。Polyfill 在运行时执行,实现任何缺失的语言特性,然后执行主应用程序逻辑。然而,这增加了运行时成本,并且某些语言特性无法通过 Polyfill 实现。参见 core-js。

所有打包器本质上也是转译器,因为它们解析多个 JavaScript 源文件并生成新的打包 JavaScript 文件。在这样做时,它们可以选择在其生成的 JavaScript 文件中使用哪些语言特性。某些打包器还能够解析 TypeScript 和 JSX 源文件。如果你的应用程序有简单的转译需求,则可能不需要单独的转译器。

【早阅】Typescript:类型细化中的“as const”

打包

打包解决了需要进行大量网络请求和瀑布效应问题。打包器将多个 JavaScript 源文件连接成一个 JavaScript 输出文件,称为包,而不改变应用程序行为。包可以通过浏览器在一次往返网络请求中高效加载。

目前常用的打包器有 Webpack、Parcel、Rollup、esbuild 和 Turbopack。

加载器允许开发者在 JavaScript 文件中透明地导入静态资源,将所有源文件和静态资源组合成一个依赖图。使用 Gulp 时,每种类型的静态资源必须作为单独的任务进行构建。Webpack 还支持开箱即用的代码分割,简化了其设置和配置。

Webpack 运行缓慢,单线程,用 JavaScript 编写。它高度可配置,但其许多配置选项可能令人困惑。

  • Rollup (2016) 利用 ES6 模块的广泛浏览器支持及其启用的优化(如 tree shaking),生成的包大小比 Webpack 小得多,从而导致 Webpack 之后采用了类似的优化。Rollup 是一个单线程打包器,用 JavaScript 编写,性能略优于 Webpack。

  • Parcel (2018) 是一个低配置的打包器,旨在开箱即用,为构建过程的所有步骤和开发工具需求提供合理的默认配置。它是多线程的,比 Webpack 和 Rollup 快得多。Parcel 2 在底层使用 SWC。

  • Esbuild (2020) 是一个为并行性和最佳性能而设计的打包器,用 Go 编写。其性能比 Webpack、Rollup 和 Parcel 高出数十倍。Esbuild 实现了一个基本的转译器和压缩器。然而,它的功能比其他打包器少,提供了一个有限的插件 API,无法直接修改 AST。与其通过 esbuild 插件修改源文件,不如在传递给 esbuild 之前对文件进行转换。

  • Turbopack (2022) 是一个支持增量重建的快速 Rust 打包器。该项目由 Vercel 构建,由 Webpack 的创作者领导。目前处于测试阶段,可以在 Next.js 中选择使用。

如果你只有很少的模块或网络延迟很低(例如在本地主机上),跳过打包步骤是合理的。几个开发服务器也选择不在开发服务器中打包模块。

代码分割

默认情况下,客户端 React 应用程序被转换成一个包。对于具有许多页面和功能的大型应用程序,包可能非常大,抵消了打包的原始性能优势。

【第3296期】别再用花哨的技巧来编写“优雅”的代码了!

将包划分为几个较小的包,即代码分割,可以解决这个问题。常见的方法是将每个页面分成一个单独的包。使用 HTTP/2,共享依赖项也可以被分解成它们自己的包,以避免重复,成本很低。此外,大模块可能会分割成一个单独的包并按需懒加载。

代码分割后,每个包的文件大小大大减少,但现在需要额外的网络往返,可能会重新引入瀑布问题。代码分割是一种权衡。

由 Next.js 推广的文件系统路由器优化了代码分割的权衡。Next.js 为每个页面创建单独的包,仅在其包中包含该页面导入的代码。加载一个页面会并行预加载该页面使用的所有包。这优化了包大小而不会重新引入瀑布问题。文件系统路由器通过为每个页面创建一个入口点(pages/**/*.jsx)来实现这一点,而不是传统客户端 React 应用程序的单一入口点(index.jsx)。

Tree Shaking

一个包由多个模块组成,每个模块包含一个或多个导出。通常,一个包只使用其导入的模块的一部分导出。打包器可以通过一个称为 tree shake 的过程来移除模块的未使用导出。这优化了包大小,提高了加载和解析时间。

tree shaking 依赖于对源文件的静态分析,因此当静态分析变得更加困难时,tree shaking 的效率会受到影响。两个主要因素影响 tree shaking 的效率:

静态资源

静态资源,如 CSS、图像和字体,通常在打包步骤中添加到可分发文件中。它们也可能在压缩步骤中进行文件大小优化。

在 Webpack 之前,静态资源在构建管道中作为独立的构建任务与源代码分开构建。要加载静态资源,应用程序必须通过可分发文件中的最终路径引用它们。因此,通常会根据 URL 约定来仔细组织资产(例如 /assets/css/banner.jpg 和 /assets/fonts/Inter.woff2)。

Webpack 的 “加载器” 允许从 JavaScript 中导入静态资源,将代码和静态资源统一到一个依赖图中。在打包过程中,Webpack 将静态资源导入替换为其在可分发文件中的最终路径。这个功能使静态资源可以在源代码中与其关联的组件一起组织,并为静态分析创建了新的可能性,如检测不存在的资产。

需要注意的是,导入静态资源(非 JavaScript 或转译为 JavaScript 的文件)并不是 JavaScript 语言的一部分。它需要一个配置了对该资产类型支持的打包器。幸运的是,继 Webpack 之后的打包器也采用了 “加载器” 模式,使这个功能成为常态。

压缩

压缩解决了文件不必要过大的问题。压缩器在不影响文件行为的情况下减小文件大小。对于 JavaScript 代码和 CSS 资源,压缩器可以缩短变量名、消除空白和注释、消除死代码以及优化语言特性的使用。对于其他静态资源,压缩器可以执行文件大小优化。压缩器通常在构建过程的最后一步运行在包上。

【第2047期】如何使用AVIF:新一代图像压缩格式

目前常用的 JavaScript 压缩器有 Terser、esbuild 和 SWC。Terser 是从不维护的 uglify-es 分叉出来的。它用 JavaScript 编写,速度较慢。Esbuild 和 SWC 前面提到过,除了其他功能外,还实现了压缩器,并且比 Terser 快。

目前常用的 CSS 压缩器有 cssnano、csso 和 Lightning CSS。Cssnano 和 csso 是纯 CSS 压缩器,用 JavaScript 编写,速度较慢。Lightning CSS 用 Rust 编写,据称比 cssnano 快 100 倍。Lightning CSS 还支持 CSS 转换和打包。

开发人员工具

上述基本的前端构建管道足以创建优化的生产可分发文件。存在几类工具可以增强基本的构建管道并改进开发者体验。

元框架

前端领域以选择使用 “正确” 包的挑战而闻名。例如,列出的五个打包器中,应该选择哪一个?

元框架提供了一组精选的包,包括构建工具,它们协同工作并启用专门的应用程序范式。例如,Next.js 专注于服务器端渲染(SSR),Remix 专注于渐进增强。

元框架通常提供预配置的构建系统,免去了需要自己拼凑一个的麻烦。它们的构建系统同时为生产和开发服务器提供配置。

与元框架类似,构建工具如 Vite 提供预配置的构建系统,适用于生产和开发。不同的是,它们不强制要求特定的应用程序范式,适合一般的前端应用程序。

源映射

由构建管道生成的可分发文件对大多数人类来说是不可读的。这使得调试任何发生的错误变得困难,因为它们的回溯指向不可读的代码。

【第3164期】如何在线上使用 SourceMap

源映射通过将可分发文件中的代码映射回其在源代码中的原始位置来解决这个问题。浏览器和故障排除工具(例如 Sentry)使用源映射来恢复和显示原始源代码。在生产环境中,源映射通常隐藏在浏览器之外,仅上传到故障排除工具以避免公开源代码。

构建管道的每一步都可以生成源映射。如果使用多个构建工具构建管道,则源映射将形成一个链(例如 source.js -> transpiler.map -> bundler.map -> minifier.map)。要确定缩小代码对应的源代码,必须遍历源映射链。

然而,大多数工具无法解释源映射链;它们最多期望每个可分发文件有一个源映射。源映射链必须展平成一个单一的源映射。预配置的构建系统会解决这个问题(参见 Vite 的 combineSourcemaps 函数)。

热重载

开发服务器通常提供热重载功能,在源代码更改时自动重建新包并重新加载浏览器。尽管大大优于手动重建和重新加载,但仍然有些慢,所有客户端状态在重新加载时都会丢失。

【第562期】用 webpack 构建 node 后端代码,使其支持 js 新特性并实现热重载

热模块替换 改进了热重载,通过在运行的应用程序中替换更改的包进行就地更新。这样可以保留未更改模块的客户端状态,并减少代码更改和更新后的应用程序之间的延迟。

然而,每次代码更改都会触发所有导入它的包的重建。这导致相对于包大小的线性时间复杂度。因此,在大型应用程序中,热模块替换可能会由于重建成本的增加而变慢。

无包范式 目前由 Vite 倡导,通过不打包开发服务器来解决这一问题。相反,Vite 将每个源文件对应的 ESM 模块直接提供给浏览器。在这种范式中,每次代码更改只会触发前端的单个模块替换。结果是相对于应用程序大小几乎恒定的刷新时间复杂度。然而,如果你有很多模块,初始页面加载可能会更长。

单体代码库

在有多个团队或多个应用程序的组织中,前端可能会分成多个 JavaScript 包,但保存在一个仓库中。在这样的架构中,每个包都有自己的构建步骤,它们共同形成一个包的依赖图。应用程序位于依赖图的根部。

单体代码库工具协调依赖图的构建。它们通常提供增量重建、并行性和远程缓存等功能。通过这些功能,大型代码库可以享受小型代码库的构建时间。

更广泛的行业标准单体代码库工具如 Bazel 支持广泛的语言、复杂的构建图和封闭执行。然而,前端 JavaScript 是最难与这些工具完全集成的生态系统之一,目前几乎没有先例。

【第3280期】从 Lerna 到现代化:原生 Workspaces 和 Changesets 的高效协作

幸运的是,存在几种专门为前端设计的单体代码库工具。不幸的是,它们缺乏 Bazel 等的灵活性和鲁棒性,尤其是封闭执行。

目前常用的前端专用单体代码库工具是 Nx 和 Turborepo。Nx 更成熟,功能更多,而 Turborepo 是 Vercel 生态系统的一部分。过去,Lerna 是将多个 JavaScript 包链接在一起并将它们发布到 NPM 的标准工具。2022 年,Nx 团队接管了 Lerna,现在 Lerna 在底层使用 Nx 来驱动构建。

趋势

较新的构建工具使用编译语言编写,并强调性能。2019 年前端构建速度非常慢,但现代工具大大加快了速度。然而,现代工具的功能集较小,有时与库不兼容,因此遗留代码库通常无法轻松切换到它们。

服务器端渲染(SSR)在 Next.js 兴起后变得更加流行。SSR 对前端构建系统没有引入任何根本性的差异。SSR 应用程序也必须将 JavaScript 发送到浏览器,因此它们执行相同的构建步骤。

关于本文
译者:@飘飘
作者:@azuline
来源:https://sunsetglow.net/posts/frontend-build-systems.html

这期前端早读课
对你有帮助,帮”
“一下,
期待下一期,帮”
在看” 一下 。