D2-前端工程化历程与Turbopack概述-学习总结

51 阅读12分钟

分享主要内容

  • 讲了构建工程化的历史路程 原生编写->node.js-gulp->webpack->vite->turbopack

讲解了每个工程化架构解决的问题以及存在局限性

  • 从内部实现以及架构层面简要讲了turbopack为什么这么快的原因,提到Native Build函数级缓存
  • 未来前端的趋势,目的在于提效,Native Code与Monorepo可能是目前构建提效主流解决方案

学习心得

作者从工具的角度解释了前端工程化发展的历程

前端是与用户最贴近的开发岗位,我这边将从结合前端职能与用户体验解释前端工程化不断优化的原因

  1. Html + Css + Javascript

分享者提到一开始前端工作基本由服务端完成,页面要求更低。

自己的感想与补充:

  • 这和互联网前后端发展顺序有关,互联网从基本的0101到现在丰富交互可以看出,发展顺序是从底层逻辑到展现的,这也就意味着服务端发展成熟度是早于前端。此时初具雏形的前端页面复杂度较低,页面细节与交互点较少, 因此基本是写完就直接发上去
  • 随着用户体验要求越来越高,前端页面承载的逻辑也越来越多,样式交互也越来越复杂,文件也越来越大,由此引起加载过慢引起用户体验的问题
  • 另外由于很多平台是作为服务商面向各位用户,并不倾向于将前端逻辑资产共享,但因为前端这一职能特性使得文件不得不加载到浏览器本地,加上代码不规范也会存在敏感字段暴露的风险,前端代码是有比较强的安全机密诉求的
  1. Node.js

分享者提到

JS可以脱离浏览器运行,前端开发者可以利用js做更多处理性的事务

如语法糖,模块化,eslint检测等可以提升开发者效率与提升项目工程化的工具

自己的感想与补充:

Node.js出来之前正呈现前后端分离的趋势,随着用户要求的页面体验越来越高,前端被划分一个独立的岗位,接上面的点,出于用户体验考虑,需要减少代码体积,出于提供商自身保护权益的需要。前端文件需要被压缩,同时代码需要被加密,要实现这样的功能必定离不开服务端。但这件事情又确实属于前端职能范围,服务端不大想参与。

此时Node.js应运而生,前端开发者可以利用自己熟悉的语言推动前端工程化领域的发展(当然,这里node也作为webserver纵向延展了前端领域的覆盖面),基于此涌现了如代码压缩,混淆,BrowserSync等工程化插件

  1. Gulp

分享者提到

用户负责定义一系列构建任务,Gulp负责调度(任务依赖管理,并串行模型)执行

思路:专注做好任务调度,执行,能够嵌入许多构建插件

自此:前端有了JS闭环的一体化构建工具

问题:

  • 任务间互不相通。重复度高,性能差
  • 不同资源依然走各自的处理逻辑,没有形成统一的心智模型

自己感想与补充:

有了插件,集成化提效是一个很好的方案,不过这里有个疑惑点,重复度高,性能差这里不太清楚,都是利用插件,性能差如何体现?

  1. Webpack

分享者提到

通过各种loader读入原始文件,解析为AST,分析依赖,进一步找到更多依赖模块,如此递归,最后将资源整合打包

思路:

  • 定义主流程:遍历、转译AST,递归分析资源依赖,打包
  • 主流程之外,开放compile hooks,拓展插件
  • 内置devserver、hmr、sourcemap等工具,开箱即用

优点:1. 先发优势 2. 扩展性强 3. 生态完善,满足现有主流构建框架需求

缺点:1. 配置繁琐 2. 产出体积较大,分拆逻辑需自定义 3. 资料匮乏,学习难度较大 4. 大而全,强调兼容性,但也同时实现了许多并未成为规范的特性,导致不够规范

自己感想与补充:

以上提到了依赖模块,这里接上面Html + Css + Javascript那一章节讲的,不断复杂化的前端项目带来代码管理与变量污染等问题,由此模块化的需求出现了,如何解决?原生开发使用commonJs做模块化解决这一问题。同时react,vue,angular的出现也解决了UI复用的问题。

模块化复用, gulp能解决能任务流的问题,但是管道式设计架构要实现代码的模块化成本是比较高的, webpack的产生正是解决了这一问题。

而且随着各大SPA成为web开发的主流,由于其UI复用的特性,都开始拥抱webpack(vue-cli,create-react-app等脚手架插件都是基于webpack),并且它提供了在各个构建节点的插件嵌入能力,开发者可以进行压缩,混淆,编译等插件嵌入其中。这就进一步压缩了gulp的市场

同时在优化用户体验的这一进程上,webpack基于其模块化的特性新增split chunk这一插件,这样提升加载速度就从单一文件优化变成了逻辑的动态加载,只加载用户看到或是使用的逻辑,进一步提升了加载体验

  1. Vite

分享者提到

  • 传统大项目因为全量构建bundle的原因开发构建与线上部署速度很慢
  • esm推广很顺利,很多浏览器原生支持esm
  • 基于原生语言的,速度更快的工具(esbuild)已经逐步成熟
  • Snowpack已经证明这条路走得通

优势:1. 启动速度快的离谱 2. 借助ESBuild,开发阶段可以做到极快的单模块编译 3. 文档、开发体验都非常棒

问题:1. 开发模式下网络请求数非常多,可能导致性能问题 2. 生产模式与Webapck无本质区别 3. 封装的很好,复杂度依然高

自己感想与补充:

浏览器厂商可能也意识到现在模块化的普及,开始支持esm引入。由此出现snowpack与vite。

vite是使用esbuild对ts,tsx,jsx做转译的,这里预示了一部分发展趋势,那就是原生js或是node.js架构有其性能瓶颈。

  • 首先是语言方面,js内存管理遵循垃圾回收标准方式,众生平等,这也就意味着或多或少存在内存浪费的问题
  • 架构层面,Node.js是单线程的,虽然可以利用循环事件(Event Loop)来实现并发执行任务,但仍存在cpu无法充分利用的问题

此时渐渐的各大开发者开始使用native code做工程化优化,提升编译速度,如esbuild使用go开发将文件转化为js的速度提升数倍,基于Rust的SWC 在单线程上比 Babel 快 20 倍,在四核上快 70 倍。

注意:vite的工程化解决的对象是开发者,不是用户,但很重要,毕竟更快的开发环境编译使得前端开发更高效,也是工程化的核心目的之一

  1. Turbopack

分享者提到

Turbopack -据说很快

为什么能做到这么快:

  • 基于Tokio的调度系统
  • 函数级缓存
  • 延迟执行(turbopack-dev-server)的ModuleGraph的收集算法;

快主要有两点:

  • 延迟构建,函数级缓存
  • RUST Nativi build CPU与内存利用度更高

自己感想与补充:

Turbo是下一代前端开发工具链,使用rust编写,它主要分为3个部分:

  • Turbopack:增量的构建器(webpack的接班人)
  • Turborepo: 增量的构建系统
  • Turbo 引擎:底层增量编译与缓存引擎

Turbopack思考:

  1. Turborepo - 增量的构建系统

翻译自**Turborepo官网**

Turborepo重构了由Facebook和Google打造的构建技术以消除维护负担与开销

  1. 特性

  • 增量构建:构建一次就够痛苦的了,Turborepo将会缓存你每次构建的内容并跳过曾经计算过的东西
  • 内容感知哈希:监听你文件的内容,不需要花费多少时间便能找到什么内容需要被构建
  • 并行执行:在不浪费闲置CPU的情况下,以最大并行度使用计算机每个内核执行构建
  • 远程缓存:团队或是CI/CD共享一个远程缓存以带来更快的构建速度 切入点
  • 运行时0开销:Turborepo不会干扰你的运行时代码或是动到你的sourcemaps 没太懂,动到运行时代码会有什么影响呢?
  • 仅构建需要的模块:通过构建仅仅需要的模块来加速PaaS部署 切入点
  • 任务流:定义任务之间的关系,然后让Turborepo优化构建内容和构建时间 目前暂时找不到使用场景
  • 应用场景-Lerna: 加速Lerna打包构建速度
  • 浏览器辅助:开发浏览器插件用于监听哪些任务花费时间最长
  1. 核心概念

  1. Caching Tasks

(1)Missing the cache

  • turbo构建时会根据输入的内容计算生成对应的hash值
  • 检查在Turbo Cashe中是否存在对应同名的hash文件(e.g../node_modules/.cache/turbo/78awdk123)
  • 如果Turborepo没有找到任何匹配的文件,会执行该构建任务
  • 一次构建任务完成后,Turborepo将会其hash文件夹下存入所有输出内容

(2)Hitting the cache

  1. Remote Caching

这里提到可以通过将不同的代码内容生成的hash缓存文件由远程服务器管理,这样大家在本地或是线上构建时可以共享缓存,提升构建效率

疑问:大多数情况下每个人的代码每天都是增量的,大概率不会命中缓存,构建提速效果有待探究

turbo.build/repo/docs/c…

这边文章给了线上构建的切入点,但还是没有解决刚才提到的疑问

  1. Turbopack - 增量的构建器

  1. 特性

  • 增量设计架构: 构建一次就足够了,一旦Turbopack执行了一项任务,它就再也不会执行了
  • 支持友好:支持编译TypeScript, JSX, CSS, CSS Modules, WebAssembly等开箱即用的功能
  • 快如闪电的HMR: 大型的代码项目依然能保持快速的HMR
  • React原生支持
  • 同时构建多个环境
  • Next.js支持
  1. 为什么是Turbpack?

  • 团队迁移了很多原生构建工具至Rust/Go,如Babel Terser,给构建性能带来了提升,webpack同样应该被迁移
  • 本地构建 vs 原生ESM,ESM在开发环境下特别快,但是对于大型应用,ESM会带来大量的网络请求,而网络请求在浏览器下并发是有限制的。这会影响程序的启动时间。本地构建因为需要将所有文件构建好才能展示给用户,这是一直以来的痛点,turbopack解决了这个问题,因为它是用 Rust 编写的,并且跳过了仅生产所需的优化工作。
  • 增量计算:如何加快构建速度?无非有两种方式:做更少的工作或者并行执行更多的任务,turbo能够将代码缓存细化到函数粒度,并且由于Rust的特性支持并行计算,并且允许这些函数在多个内核间并行调用。Turbo引擎还缓存它调度的所有函数的结果,简而言之:以最快的速度完成最少的工作
  • 懒构建:仅构建展示的页面
  1. 核心概念

  • 函数级缓存:

  • api.ts与sdk.ts中有多个函数模块,Turbo引擎首次会将这两个文件的函数模块进行组装,最终打包成一个完整的bundle文件。
  • 在sdk.ts文件发生改变后,Turbo引擎会重新编译这个文件,而之前的api.ts不会被重新读写与编译而从缓存中去模块内容,这样就节省了多余的工作时间,而且项目越大,节省的时间越多(存量越大,重复编译的时间越长)
  • 按请求编译:
  • 页面级编译:

顾名思义,我们仅会编译请求页面中的内容,但仍会存在一些问题,如一个页面下部分模块未展示在页面中,它们仍然会被编译

  • 请求级编译

Turbopack足够智能以至于它能够做到只编译浏览器请求的代码,这是不是很像esm呢?但正如上面所提到的,esm会增加请求的次数,但turbopack能够在请求级编辑的基础上减少请求的次数。

  1. 结合团队:

从以上认知中可以结合团队的有几个点:

团队角度:目前巨石项目模块众多,涉及53w行代码,使用webpack的构建压力与日俱增。

目前turbopack官网仍是beta版,在脱离next后暂时无法使用,另外是turbopack本身的插件没有足够丰富的生态,这也在造就了一定的迁移风险。

turbopack提供了很好的思路,如函数级缓存、缓存共享、native build

切入点可能不仅仅在于接入这一构建工具,而是如官网所述

团队或是CI/CD共享一个远程缓存以带来更快的构建速度

通过构建仅仅需要的模块来加速PaaS部署