AI时代已来,你准备好应对挑战了吗? 了解详情
写点什么

我们从 Vue 到 Alpine.js 的旅程

作者: Tim Kleyersburg

  • 2022-12-05
    北京
  • 本文字数:5111 字

    阅读完需:约 17 分钟

我们从Vue到Alpine.js的旅程

问题


在 2019 年底,我们为一位客户重发布了电子商务网站。这次重新发布的变动很大,不仅影响了整体设计和模板,还涉及了前端的架构。唯一没什么改动的就是后端。


客户的主要需求是:


  • 优化 PageSpeed 指标

  • 提高可用性,从而提高转化率


在数月的实施之后,客户和我们都对最终成果感到满意。我们在 Lighthouse 的全部四个类别中都达到了绿色评级,转化率也有了显著的提升。直到谷歌在 Lighthouse 6.0 更新中更改了性能评分的计算模式,让我们的评分从绿色降级为红色。


顺带一提,Lighthouse 在新标准中将重点转移到了前端内容上,在首字节时间(TTFB)以及如文件大小、CSS 优化、网页字体等会对总体网页性能有影响的内容之外,还囊括了“可交互时间(TTI)”以及“最大内容绘制(LCP)”指标。随着网页可交互性越来越强,其对性能的感知也越发重要。理论上来说,我们是支持谷歌将这些新指标纳入评分标准的,尽管谷歌在展示“优秀范例”时用的是几乎没有任何交互性的博客站点,这完全是在拿苹果和橘子在作比较。


在与客户的一次会议后,我们延后了针对 Lighthouse 新指标的优化工作。在分析了网站访客的常用设备后,我们很难再说服自己将大量时间花费在我们和所有竞争对手都要面临的问题上。


然而,随着在 2020 年底、2021 年初谷歌公布部分新指标将对搜索结果排名有影响后(是时候将页面体验引入谷歌搜索了),显然我们并不能再继续将这个问题推延了。在与客户的又一次商讨后,我们确定了我们所能提供显著竞争优势,并让最终用户感受到速度的提升。


分析过程


我们需要更多的数据。坦白来说,在这之前我们从来没怎么重视过更深层次的性能指标,而现在我们要开始赶进度了。我们通过谷歌 Chrome 浏览器和其内置的 Lighthouse 应用,外加开发者工具中的性能标签,三管齐下分析网站性能。


我们当前的设置


在重发布后,我们将前端架构完全推翻重写,用 Vue 2 作为 JavaScript 框架,TailwindCSS 为 CSS 框架。所有内容都由 Symfony Encore(Webpack)进行打包。


我们的站点没有用 SPA,而是将根实例捆绑到一个 div 元素 #app 上。借助无渲染组件(Vue.js 中的无渲染组件)让我们可以使用服务器端变量或是用 Twig 轻松编写大部分模板,而不需要编写任何 API。


<notepad-star  :product-id="{{ product_id }}"  :initial-star="{{ is_stared(product_id) ? 'true' : 'false' }}">  <div>    <button @click.prevent="toggle">Toggle</button>  </div></notepad-star>
复制代码


product_id 是服务器端变量,is_stared(product_id)是 Twig 函数,二者都是作为 props 传入 Vue 组件的。


问题分析


目前我们的流程大致是这样子的:


  1. 在 chrome 里生成性能报告

  2. 研究报告结果

  3. 改点东西

  4. 重新生成新报告以确定或者推翻我们假设


性能报告中最有用的部分是“评估脚本”,似乎浏览器在评估我们 JavaScript 包的时候要做不少事。



生产环境


我们的第一步是注释掉脚本标签,看看对指标会有什么影响。结果发现,效果相当显著。



注:这份报告是我们在开发环境中生成的,与实际生产环境大约有 10%-15% 的差异。


需要做什么


我们确定了以下几点亟需关注:


  1. 优化关键资源的预加载

  2. 最大限度地缩减阻断时间

  3. 优化交互时间

  4. 最大限度地减少主线程工作


Part 1:优化预加载


为追求简单快速简单的部署,我们没有对谷歌标签管理和我们的 CCM 进行完善的性能测试,这也导致了一些渲染阻塞。我们测试了预加载和预连接的各种不同组合,并最终得出了以下结果:


  • 预加载关键资源,如 CCM 脚本

  • 预连接 GTM

  • 预加载我们自己的关键资源,如网页字体或我们自己的主要 css、js 文件


这些是我们用到的工具:


  • Lighthouse:直观展示哪些资源应该被预加载

  • Firefox:通过开发者工具可以找到被预加载,但在最初几秒内未被使用的字体


Part 2-4:优化其余部分


在优化预加载后,剩下可能对我们关键指标有影响的就只有我们 JavaScript 包中自己的资源了,其余指标也都或多或少跟这些资源挂钩。


找到问题


在开始优化之前,我们需要先从更深层次分析问题。如前文所述,我们对所有的 Vue 组件都应用了无渲染组件,并用 Vue 实例打包了整个网站。这种方式让我们可以很方便地进行全局状态管理,我们还可以通过添加额外的混合器来为网站增加交互性,比如:


export const searchOverlay = {  data() {    return {      showSearchOverlay: false,    }  },}
复制代码


全局状态示例 / 混合器提供功能性


Vue 的不同版本


Vue 有两个不同的版本:运行时构建,以及包含模板编译器的版本。运行时构建的文件大小相比来说要小很多,但只能用于单一文件的组件,因为这类组件会被包含在捆绑包中,因此不需要模板编译器。另一方面,模板编译器让我们可以从模板引擎(Twig)中生成模板,并插入到无渲染组件的默认槽中。


另外,由于我们需要将网站整体打包,Vue 需要对所有可见的 DOM 节点进行评估,而光是在主页上就有大约 4500 个节点。这也是为什么我们的脚本评估时间会是如此的长。


既然对根因有了更好的理解,我们可以开始着手评估问题缓解的方法了。


很可惜我们最终并没有找到能显著提升当前架构性能的方法,我们的模板架构和后端结构并不允许我们优雅地切换到运行时构建。


评估需求


下一步,我们开始整理当前网页上所提供的组件和交互功能,以从我们全新的解决方案中获得新的视角。


这些是我们目前已有组件的例子:


  • 实时搜索

  • 动态侧边栏导航

  • 弹出框菜单

  • 模态框


我们还有一些之前由混合器提供的小型函数。这些函数因为没有状态且可以简单直接地在任何地方触发,主要用于不需要单独组件即可实现的功能,如:


  • 动态更新产品类别

  • 打开发货模式

  • 展示或隐藏全局信息轮播图


这些功能都有一个共同点:需要组件间的交流。这些组件都不算复杂,主要用于提供互动性或防止网页重新加载。


我们希望且需要从新框架中获得的有:


  • 反应性,在数据发生变化后模板会重新渲染

  • 事件系统以方便组件间交流

  • 占用空间小


引入 Alpine.js


我们曾在其他项目中用 Alpine.js 来提供交互性,最终效果也很好。既然我们已经在项目中使用 TailwindCSS 了,Alpine.js 所声称的“类似 JavaScript 中的 TailwindCSS”说法很得我们心。我们并不确定 Alpine.js 是否能胜任如此大型的电子商务站点,因此我们需要建立一个概念验证,以测试它是否最难处理的部分。我们重新构建了如滑动导航、动态购物车以及主菜单等包含前文所提到需求的重要组件,如果我们能重新整合这些组件,那我们可以肯定地认为其他组件都没问题。在经过了大约一天左右的工作,我们收获了满意的成果。虽然重构过程并不是一帆风顺,但既然我们的大部分逻辑都是用 JavaScript 写的,从 Vue 到 Alpine.js 的转换都是很直接的。我们最终确定了以下的架构形式:


js/├── components/│   ├── cart.js│   ├── mobileMenu.js│   └── ...├── enums/│   ├── events.js│   └── ...├── helper/│   └── customEvent.js├── providers/│   ├── cart.js│   ├── googleTagManager.js│   └── ...└── stores/    ├── cart.js    ├── global.js    └── ...
复制代码


组件


组件是以窗口范围的函数所定义的,可以返回用于在 Alpines 的 x-data 属性中用于初始化组件的对象。


下面是一个简化的模态组件示例,请注意我们是怎么使用 customEvent 函数和“枚举(enums )”的。


import customEvent from '@/helper/customEvent'import { MODAL_OPEN, MODAL_OPENED, MODAL_CLOSE } from '@/enums/events'
window.modal = () => ({ open: false, init() { if (this.instantDisplay !== undefined) { this.open = true } }, close() { this.open = false customEvent(MODAL_CLOSE, this.name) }, wrapper: { async [`@${MODAL_OPEN}.window`](e) { if (modalToOpen !== e.payload.name) { return }
customEvent(MODAL_OPENED, this.name) this.open = true }, },})
复制代码


enum


并不是指实际意义上的枚举,只是个用来保存常量的辅助文件,方便我们在整体代码库中使用这些常量,而不用担心事件在重命名时会连锁搞崩掉别的东西。


const MODAL_OPEN = 'modal-open'const MODAL_OPENED = 'modal-opened'const MODAL_CLOSE = 'modal-close'
复制代码


helper


我们可以在任何地方导入 helper 函数且不会保留任何状态。这个是我们的 customEvent helper 函数:


export default function (name, payload = null, originalEvent = null) {  // 入参对象应包含以下:  // name: 'string',  // payload: 'object'  // originalEvent: 'this',或者其他你需要点击的目标
const customEvent = new CustomEvent(name, { detail: { payload: payload, originalEvent: originalEvent, }, })
window.dispatchEvent(customEvent)}
复制代码


这个简单的 helper 给我们带来极大的灵活性,让我们摆脱了定义无数个 Alpine 组件的烦恼,在包括 HTML 中等任何地方直接调用。其本质也不过是标准 CustomEvent API 的一部分,改造成可在窗口范围内使用的函数且能接收 onclick 属性入参。


<button type="button" onclick="customEvent('name', 'payload')"></button>
复制代码


内容提供器


内容提供器通过可复用功能提供数据,可以把它看作是客户端的 API 层。和 helper 函数一样,这些函数不应包含任何状态,且可被组件消耗的。


下面是实时搜索的内容提供器大致代码:


import customEvent from '@/helper/customEvent'import { SEARCH_GET } from '@/enums/events'
async function getResultFor(searchTerm) { let result = undefined
await fetch(`/search?q=${searchTerm}`) .then((response) => response.json()) .then((data) => { result = data })
customEvent(SEARCH_GET, result)
return result}
export { getResultFor }
复制代码


store


既然我们 JavaScript 框架选择依赖 Alpine.js 2.8,那么选择 Spruce 做全局状态管理也很合理。网站的每个部分都有一个 store,以下几行代码是我们用于管理大型菜单状态的:


Spruce.store('megamenu', {  activeId: null,  toggle(id) {    if (id === this.activeId) {      this.activeId = null      return    }    this.activeId = id  },})
复制代码


新旧指标的对比


在确定架构并顺利实施最复杂的组件后,我们很自信我们前进的道路一定是正确的。性能标准测试结果也很好,大部分的性能分类都有了 15-20 百分点的提升。


我们迫不及待地想实现所有组件以获得完整的指标结果,每次点击 Lighthouse 标签中“生成报告”按钮,都会让我们的心跳加速。如果不包含脚本的话,预计我们的网站是不可能达到 56 的评分,但这是我们现在的结果:



再次声明,这只是我们的开发环境,因此很多图中的“机会”并不适用于实际生产环境。


这次的结果让我们颇为满意,在最后的几项测试,并对代码进行清理后,我们开始准备下周一的版本发布。


上午 8 点 24 分,我们点下了“合并”按钮。在这之前我们进行了发布前的最后一次 Lighthouse 测试,性能评分当时下降到了 28,具体是什么原因造成的这次 10 分左右的下降我们并不清楚。部署工作顺利进行,网站运行正常,于是我们又进行了一次 Lighthouse 测试。这是测试结果:



在上线之后我们发现了一些小问题,在及时修复后我们成功将评分打上 62 分,真是太刺激了!当然,这并不会是我们旅途的终点,我们仅仅是为后续进一步改善用户界面体验打下了良好的基础。


荣誉提名:Debugbear


在这次重新部署中,我们需要一个能对指标进行监控的工具。在研究通过 CI/CD 管道、手动测试或是通过 Lighthouse 节点 CLI 运行脚本时,我们偶然发现了 Debugbear。


Debugbear 的服务可以检测网站的核心状态、运行 Lighthouse 测试并将测试结果与竞争对手或历史结果进行对比,从而提供对两次测试结果的深层次解读。它不仅帮我们更好地了解问题根因,还提升了我们对优化工作的信心。


可以说,Debugbear 的性价比非常高,再加上 Matt 人真的很好,当时我们的信用卡除了问题,他非常慷慨地延长了我们的试用期,让我们安心测试而不用担心最后期限。


写在最后


以上基本就是我们旅程的第一阶段了。


最终的成果让我们对自己的决策充满了信心。Vue 并不适合我们的项目,老实说,当初选择 Vue 或许是因为它看起来不错,但它从来不是我们最好的选择。错处不在 Vue,Vue 是个很强的框架,我们也还在继续使用它,但现在我们有了另一个比 Vue 更合适的工具。


希望这篇文章能帮上你!如果有任何问题,欢迎在推特上联系我😊。


原文链接:

https://www.tim-kleyersburg.de/articles/from-vue-to-alpinejs


相关阅读:

Vue涉及国家安全漏洞?尤雨溪回应:前端框架没有渗透功能

尤雨溪:Vue 3 将成为新的默认版本

2022-12-05 19:1912730

评论

发布
暂无评论
发现更多内容

druid 源码阅读(八)Druid 回收连接

爱晒太阳的大白

5月月更

密码学系列之:PKI的证书格式表示X.509

程序那些事

Java 密码学 程序那些事 5月月更

Jmeter高手进阶-脚本增强

伤心的辣条

Python 程序人生 软件测试 IT 自动化测试

加盟共享洗车多少钱?投入大吗?

共享电单车厂家

加盟共享洗车 自助洗车加盟费用

Java中观察者模式与委托,还在傻傻分不清

华为云开发者联盟

Java 观察者模式 委托 事件执行者

弱网优化,GCC 动态带宽评估算法(内附详细公式)

融云 RongCloud

通信系统 链路 网络管理

金融任务实例实时、离线跑批Apache DolphinScheduler在新网银行的三大场景与五大优化

Apache DolphinScheduler

Apache 大数据 开源 DolphinScheduler workflow

钱卫宁:开源是培养数据库人才的关键|OceanBase 数据库大赛访谈

OceanBase 数据库

oceanbase 数据库大赛

互联网出海企业数据库选型问答实录

OceanBase 数据库

云数据库 oceanbase 互联网出海

龙蜥开发者说:我的操作系统之路,坚持从实践中来,到实践中去 | 第6期

OpenAnolis小助手

Linux 开源 操作系统 龙蜥社区 龙蜥开发者说

共享自助洗车多少钱一次?怎么收费

共享电单车厂家

自助洗车加盟 自助洗车多少钱一次 共享自助洗车多少钱 自助洗车怎么收费

6元自助洗车既能省钱还能赚钱?

共享电单车厂家

自助洗车加盟 6元自助洗车 车白兔自助洗车

自建Gitlab迁移工具使用指南

阿里云云效

云计算 阿里云 gitlab 代码迁移 代码库

银行RPA趋向主动触发流程,补足营销场景执行末端的渠道协同能力

易观分析

银行 市场营销

堡垒机4a认证是什么意思?是指哪4a?

行云管家

云计算 网络安全 堡垒机 堡垒机认证

前端工程化之FaaS SSR方案​

百度Geek说

前端

解锁户外降温黑科技,图拉斯新品发布会完美收官

Geek_2d6073

一图详解java-class类文件原理

华为云开发者联盟

Java JVM class 类文件

Hoo研究院 | 币圈后浪—PRISM

区块链前沿News

Hoo

Redis io多线程

C++后台开发

redis 后端开发 Linux服务器开发 C++后台开发 单线程

【云计算】云计算四个必学知识看这里!

行云管家

云计算 云服务 企业上云

爱番番微前端框架落地实践

百度Geek说

前端

大前端技术的边界在哪里?

博文视点Broadview

重磅发布 | Serverless 应用中心:Serverless 应用全生命周期管理平台

阿里巴巴云原生

阿里云 Serverless 云原生 应用中心

GaussDB(DWS) NOT IN优化技术解密:排他分析场景400倍性能提升

华为云开发者联盟

数据库 GaussDB(DWS) 排他分析 NOT IN

Nebula Graph|如何打造多版本文档中心

NebulaGraph

数据库 图数据库 NebulaGraph

对话ACE第三期有奖调研

OceanBase 数据库

数据库 对话ACE Oracle ACE

CRM系统帮助企业有影响力的营销

低代码小观

CRM 客户关系管理 企业管理系统 CRM系统 客户关系管理系统

宜搭小技巧|海量数据管理难?这招帮你事半功倍

一只大光圈

钉钉宜搭

为什么越来越多人选择自助式洗车

共享电单车厂家

自助洗车加盟 车白兔自助洗车 自助式洗车

LSM树读写放大问题及KV分离技术解析

移动云大数据

HBase LSM树

我们从Vue到Alpine.js的旅程_大前端_InfoQ精选文章