Redian新闻
>
我们从Vue到Alpine.js的旅程

我们从Vue到Alpine.js的旅程

公众号新闻

作者 | Tim Kleyersburg
译者 | 马可薇
策划 | Tina
问   题

在 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://twitter.com/timkley)上联系我😊。

原文链接:

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

声明:本文为InfoQ翻译,未经许可禁止转载。

今日好文推荐

后Kubernetes时代的未来?Wasmer 3.0 发布,可在浏览器外运行 WebAssembly

马斯克要求推特程序员写周报,具体到代码行数;刘强东称将末位淘汰部分京东高管;闰秒终于要被取消了!| Q资讯

阿里云李飞飞:用5年让PolarDB成为“国货之光”| 开源人说

谷歌计划裁员上万人:利用刚上线半年的新绩效系统解雇6%“排名垫底”员工

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
告别昨日,再次踏上诗意的旅程——CGTN纪录片《诗约万里》今天 Bellevue Square 庆祝农历新年,赏舞龙舞狮、武术歌舞乐器表演,看工艺品还有新年红包虾图版GTA!连抢3车1人中枪,警察追了4座城,Bellevue劫车案细节曝光基于 Spring Boot + Vue 实现的可视化拖拽编辑的大屏项目Htmx意外走红,我们从React“退回去”后:代码行数减少 67%,JS 依赖项从 255 下降到 9面对未知的环球远航,是一段怎样的旅程?从DeepFace到AdaFace,7篇论文讲清人脸识别突发!Bellevue Square停车场发生劫车枪击事件!!Vant 4.0正式发布,基于Vue 3的移动组件库日本野人从DeepFace到AdaFace,一文搞懂人脸识别!尤雨溪Vue 2022回顾及2023展望关于 “ React 和 Vue 该用哪个” 我真的栓 Q架子鼓小美女祝大家节日快乐中国高铁开到国外!让外国人也体验一把时速350公里的旅程从Kindle到Alexa,为何亚马逊总是“贱卖”其硬件产品?Bellevue女子尝试网红健康餐,干呕+剧烈腹痛,连胆囊都没保住...如何使用Vue测试库测试应用程序?从COBE到韦布望远镜,诺奖得主的脑洞孕育着天文新突破|2022腾讯科学WE大会​华人注意!Bellevue Square 发生劫车案!美食营、父母之夜、Bellevue最期待的小大师公益音乐会!老兵节东区亲子活动精选!我们如何使用 Next.js 将 React 加载时间缩短 70%这场爱与美的旅程,永不停歇我的诗与远方不要对我尖叫,日式烤肉,饺子馆!Bellevue市中心又喜提一波新店啦!OfferUp宣布裁员19%!总部位于Bellevue康奈尔大学如何塑造美国精英的思想和性格商科留学生转码必备:Leetcode到底该怎么刷?Vue 3是最佳选择吗? 耗时两周从Vue 2迁移到Svelte后:代码执行更快、体验更佳《思想的旅程》多维度实现突破,湘派电视理论片永远有惊喜生生不息,开启爱的旅程哈德森河边的一群中国老人西雅图看房日记|新装修的Bellevue学区房从Velodyne的没落,看激光雷达的「冰火两重天」Bellevue排第十五!全美讨糖最安全城市榜单出炉
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。