web.dev 工程博客 #1:我们如何构建网站和使用 Web 组件

这是 web.dev 工程博客中的第一篇博文。 在接下来的几个月里,我们希望分享从我们工作中得出的富有实用价值的分析洞见,因此请留意带有工程博客标签的帖子! 下面我们将介绍静态网站的构建流程,以及(可选!)网页组件背后的 JavaScript。

web.dev 提供关于打造现代 Web 体验的内容,并允许您衡量网站的性能。 精明用户可能已经意识到,我们的 Measure 页面只是 Lighthouse 的界面,它在 Chrome 的开发者工具中也可用。登录 web.dev 后,您可以定期对您的网站运行 Lighthouse 审核,以便了解网站的得分随时间的变化情况。 我稍后会再次访问“测量”页面,因为我们认为这个页面相当特殊。🎊

简介

从根本上说,web.dev 是一个由一系列 Markdown 文件生成的静态网站。我们之所以选择使用 Eleventy,是因为它是一款精美且可扩展的工具,可让您轻松地将 Markdown 转换为 HTML。

此外,我们还使用仅向支持 type="module" 的浏览器提供的新型 JavaScript 软件包,包括 asyncawait。此外,我们还希望能够使用那些受常规浏览器(而不是少数旧版浏览器)支持的功能。 因为我们的网站是静态网站,所以不需要 JavaScript 即可读取我们的内容。

构建流程(包括生成静态 HTML 以及将 JavaScript 与 Rollup 捆绑)完成后,可以使用一个简单的静态服务器托管 web.dev 以进行测试。 该网站几乎是完全静态的,但我们有一些特殊需求,但仍然可以受益于自定义 Node.js 服务器。 这些代码包括针对无效域名的重定向,以及用于解析用户首选语言的代码,以供即将推出的国际化功能使用。

静态生成

web.dev 上的每个网页都是用 Markdown 编写的。 所有页面均包含前端诉讼或调查,用于描述有关每个帖子的元数据。 这些元数据会被提取到每个网页的布局中,从而创建标题、标记等。 示例如下:

---
layout: post
title: What is network reliability and how do you measure it?
authors:
  - jeffposnick
date: 2018-11-05
description: |
  The modern web is enjoyed by a wide swath of people…
---

The modern web is enjoyed by a wide swath of [people](https://www.youtube.com/watch?v=dQw4w9WgXcQ), using a range of different devices and types of network connections.

Your creations can reach users all across the world...

通过此基本属性,我们可以定义作者、发布日期和标签等任意属性。Eleventy 在几乎所有插件、模板或其他需要智能工作的环境中,将前置内容作为数据公开,非常方便。该数据对象还包含 Eleventy 所描述的“数据级联”,即从各个页面、页面使用的布局以及分层文件夹结构中的数据中提取的各种数据。

每种唯一布局均可描述不同类型的内容,并可从其他布局继承。 在 web.dev 上,我们使用此功能来正确构建不同类型的内容(如帖子和 Codelab)的框架,同时仍然共享一个顶级 HTML 布局。

集合

Eleventy 提供了一种用于构建任意内容集合的程序化方法。这使我们能够构建分页支持并为帖子作者生成虚拟页面(磁盘上没有匹配的 Markdown 文件的页面)。例如,我们在构建作者页面时使用的是包含永久链接表达式的模板(这样模板会针对每个作者重新呈现)和后备集合

例如,这样即可生成一个简单的页面,其中包含 Addy 的所有帖子

限制

目前,我们无法轻松引入 Eleventy 的构建流程,因为它是声明式的,而非命令式的:您描述自己想要什么,而不是如何。 很难将 Eleventy 作为大型构建工具的一部分运行,因为只能通过其命令行界面调用它。

模板

web.dev 使用最初由 Mozilla 开发的 Nunjucks 模板系统。 Nunjucks 具有典型的模板功能(例如循环和条件),但也让我们能够定义用于生成更多 HTML 或调用其他逻辑的短代码

与构建静态内容网站的大多数团队一样,我们从小规模开始,然后逐步添加短代码(到目前为止已有大约 20 个)。 其中大部分只会生成更多 HTML(包括我们的自定义网络组件)。示例如下:

{% Aside %}
See how Asides work in the web.dev codebase
{% endAside %}

最终结果将如下所示:

但其实际创建的 HTML 如下所示:

<div class="aside color-state-info-text">
<p>See how Asides work in the web.dev codebase</p>
</div>

虽然这并不在本博文的讨论范围内,但 web.dev 还使用短代码作为一种元编程语言。短代码接受实参,其中一个实参是所含内容。 短代码不要求返回任何内容,因此可用于构建状态或触发某些其他行为。🤔💭

设计脚本

如前所述,由于 web.dev 是一个静态网站,因此无需 JavaScript 即可传送和使用,也可通过不支持 type="module" 或其他现代代码的旧版浏览器使用。 在我们致力于让每个人都能访问 web.dev 时,这是非常重要的一环。

不过,我们用于现代浏览器的代码由两个主要部分组成:

  1. 引导代码,其中包含全局状态、Analytics 和 SPA 路由的代码
  2. 用于逐步增强网站的网络组件的代码和 CSS

引导代码非常简单:web.dev 可以将新页面作为单页应用 (SPA) 加载,因此我们安装了一个全局监听器,监听本地 <a href="..."> 元素的点击。SPA 模型可帮助我们维护有关用户当前会话的全局状态,否则每个新网页加载都会触发调用 Firebase 来访问用户的登录状态。

我们还会根据您访问的网址,为网站指定几个不同的入口点,并使用动态 import() 加载正确的入口点。这可减少用户在通过代码增强网站前所需的字节数。

Web 组件

Web 组件是封装了 JavaScript 中提供的运行时功能的自定义元素,并通过 <web-codelab> 等自定义名称进行标识。该设计非常适合像 web.dev 这样的大部分静态网站:当网站的 HTML 更新时,浏览器会管理元素的生命周期,并在元素与网页附加或分离时正确通知所有元素。 而过时的浏览器会完全忽略 Web 组件,并渲染 DOM 中剩余的任何部分。

每个 Web 组件都是一个包含 connectedCallback()disconnectedCallback()attributeChangedCallback() 方法的类。web.dev 的自定义元素大多从 LitElement 继承,后者为复杂组件提供了简单的基础。

虽然 web.dev 在许多网页上都使用 Web 组件,但此 API 的必要性会高于衡量页面。 以下两个元素提供了您在此页面上看到的大部分功能:

<web-url-chooser-container></web-url-chooser-container>
<web-lighthouse-scores-container></web-lighthouse-scores-container>

这些元素会创建提供更多功能的其他元素。重要的是,这些元素只是常规 Markdown 源代码的一部分,我们的内容团队可以向任何网页添加扩展功能,而不只是 Measure 节点。

我们的 Web 组件最常使用 React 中的容器组件模型,不过该模型现在有些过时。 每个 -container 元素都会连接到我们的全局状态(由 unistore 提供),然后渲染一个视觉元素,而后者又会渲染具有样式或其他内置功能的实际 DOM 节点。

此图展示了全局状态与使用该状态的 HTML 元素之间的关系。
全局状态和 Web 组件

我们最复杂的 Web 组件旨在直观呈现全局操作和状态。例如,您可以通过 web.dev 审核自己喜欢的网站,然后离开“衡量”页面。 如果返回,您会看到任务仍在进行中。

我们的简单组件只是增强了原本静态的内容,或呈现出令人惊叹的视觉效果(例如,每个折线图都是自己的 <web-sparkline-chart>),而这与全球状态没有任何关系。

让我们来聊聊

web.dev 工程团队(RobEwaMichaelSam)将很快与您进行更深入的探讨。

我们衷心希望您在了解我们所做的工作的同时,能够给自己的项目带来一些启发。如果您有关于此博客的问题或主题请求,请在 Twitter 上与我们联系!