Posted in

【第 3361 期】以服务器优先的 Web 组件、DSD、HTMX 和 Islands_AI阅读总结 — 包阅AI

包阅导读总结

1. 服务器优先、Web 组件、DSD、HTMX、Islands

2. 本文主要介绍了使用 DSD、HTMX 和 Islands 技术实现服务器优先的 Web 组件开发方法,包括组件的渲染、样式共享、架构实践等,还提供了示例和相关代码。

3.

– 以服务器优先的 Web 组件介绍

– 多种技术实现开发方法

– 前端早读课文章分享

– Islands 架构原理和实践

– 利用现有服务器框架升级原生组件

– 支持多种现代 Web 框架

– Islands Architecture 实践背景

– 回顾 Web 组件关键要点

– 展示基础卡片组件的实现

– 声明性 Shadow DOM(DSD)的优势

– 使用示例介绍 Web 组件

– 演示应用程序

– 通用方法

– 渲染 Web 组件

– 技巧分享:共享样式

– 问题与解决方案

– 客户端与服务器端实现

– 跟踪请求上下文

思维导图:

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

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

作者:Rob??Eisenberg

发布时间:2024/8/30 0:01

语言:中文

总字数:7588字

预计阅读时间:31分钟

评分:87分

标签:Web 组件,服务器端渲染,Declarative Shadow DOM,HTMX,Islands


以下为原文内容

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

前言

主要介绍了如何使用 Declarative Shadow DOM (DSD)、HTMX 和 Islands 技术实现服务器优先的 Web 组件开发方法。今日前端早读课文章由 @飘飘翻译分享。

正文从这开始~~

一种简单但强大的 Web 组件服务器渲染、声明式行为和 JavaScript 隔离的解决方案。

近年来,浏览器在将原生组件引入 HTML 方面取得了巨大的进展。到 2020 年,首批 Web 组件功能在所有主流浏览器中得到了普遍支持,自那以后,该功能的可用性一直在增长。特别是在今年早些时候,随着 Firefox 在 2 月推出了对流式声明性 Shadow DOM(DSD)的支持,这一特性终于达到了全平台的普遍支持。通过这一战略性的添加,HTML 标准解锁了许多新的强大服务器端功能。

【第2804期】Islands 架构原理和实践

在本文中,我们将探讨如何利用现有的服务器框架来实现原生组件的升级,而不需要添加大量的 JavaScript。尽管我将演示如何在Node.js、Express和Handlebars中使用这些技术,但几乎所有现代 Web 框架都支持我将要展示的核心概念和可扩展机制。因此,无论您使用的是 Node.js、Rails 还是 C# 和 .NET,您都可以将这些技术应用到您的技术栈中。

【第2802期】Islands Architecture(孤岛架构)在携程新版首页的实践

背景

在深入代码之前,我们不妨先回顾一下几个关键点。

正如前面提到的,Web 组件的首批功能在 2020 年初已被所有主流浏览器普遍支持。这包括几个核心能力:

  • 通过<template>元素实现的静态 HTML 和基础模板。

  • 通过customElements.define(...)API 定义新 HTML 标签的能力。

  • 由 Shadow DOM 和<slot>提供的 HTML 和 CSS 封装,以及 DOM 组合。

  • 通过穿透 Shadow DOM 的 CSS 属性实现的基本主题化。

通过将这些标准与一些较小的标准结合起来,任何人都可以在 Web 上创建完全本地化、可互操作的组件。然而,除了 CSS 属性之外,使用所有这些 API 都需要 JavaScript。例如,在创建一个简单的卡片组件时,注意在其中涉及了多少 JavaScript 代码:

 class UICard {
static #fragment = null;
#view = null;

constructor() {
super();
this.attachShadow({ mode: "open" });
}

connectedCallback() {
if (this.#view === null) {
this.#view = this.#createView();
this.shadowRoot.appendChild(this.#view);
}
}

#createView() {
if (UICard.#fragment === null) {
const template = document.getElementById("ui-card");
UICard.#fragment = document.adoptNode(template.content);
}

return UICard.#fragment.cloneNode(true);
}
}

customElements.define("ui-card", UICard);

对于仅包含基础 HTML 和 CSS、没有实际行为的组件来说,这段样板代码过于繁琐。上述代码负责查找模板、克隆它、创建 Shadow DOM,并将克隆的模板附加到其中。因此,浏览器无法在 JavaScript 加载、解析并运行之前渲染此卡片。但是,如果可以完全在 HTML 中完成所有这些操作,而不需要任何 JavaScript,那会怎样呢?

声明性 Shadow DOM(DSD)的出现。通过 DSD,我们有了一种 HTML 优先的机制,可以声明组件的实例,以及其 Shadow DOM 内容和样式。整个卡片组件可以仅通过以下 HTML 实现:

 <ui-card>
<template shadowrootmode="open">
<style>
:host {
display: block;
contain: content;
box-sizing: border-box;
box-shadow: var(--shadow-raised);
border: var(--stroke-thicknessMinus1) var(--stroke-style) var(--color-layerBorder);
border-radius: var(--border-radius);
background: var(--background, transparent);
color: var(--foreground, var(--color-onLayerBase));
}
</style>

<slot></slot>
</template>

卡片内容在此处...
</ui-card>

就这么简单。将此 HTML 放入页面中,<ui-card>将自动渲染,附带一个自动附加的 Shadow root,完全封装的 HTML 和 CSS 以及基于插槽的内容组合。所有这些都是通过添加shadowrootmode属性实现的,该属性告诉浏览器不要创建<template>,而是附加一个 Shadow 根,并将模板元素的内容流式传输到刚刚创建的根元素中。此功能是在 HTML 解析器级别实现的。不需要 JavaScript!

【第2982期】使用示例介绍Web组件

乍一看,这真是太棒了。我们可以创建没有 JavaScript 的基础组件,封装的 HTML 和 CSS,基于插槽的内容组合等等。但是,如果页面上有 100 个卡片呢?HTML 和 CSS 将不再只位于组件定义的一个地方,而是被复制到卡片使用的每个地方。糟糕!

但这不是一个新问题。 它与 Web 本身一样古老,是创建 Web 框架的初衷之一。正如我们将在本文的其余部分看到的那样,我们可以使用所有标准的服务器端工具来解决这个问题,并进一步改进解决方案。

演示应用程序

我们需要一个简单的 Web 应用程序来帮助我们演示如何将所有组件结合在一起。因此,我使用 Star Wars API (https://swapi.dev/) 快速创建了一个小型列表 / 详细信息界面。

在屏幕左侧显示了 Star Wars 电影的列表。列表和各个项目都作为 Web 组件实现,并完全在服务器上渲染。屏幕右侧显示了当前在列表中选中的电影的详细信息视图。详细信息视图同样是作为 Web 组件实现,并在服务器上渲染。当列表选择发生变化时,列表组件 HTML 中的声明性 HTMX 属性会触发对服务器的 AJAX 请求,以获取关联的电影详细信息组件。

我把这种类型的 Web 组件称为 “服务器优先” 组件,因为所有渲染都在服务器上完成。没有用于渲染的客户端 JavaScript 代码。实际上,在这个解决方案中几乎不需要任何 JavaScript。正如我们稍后会看到的,我们只需要少量的代码来启用 HTMX (https://htmx.org/) 集成,并逐步增强列表的选择样式。

如果您想获取完整的代码并自己尝试,可以在 GitHub 仓库 (https://github.com/EisenbergEffect/server-first-web-components) 中找到。README 文件包含设置和运行应用程序的说明。

演示的结构保持相对简单和标准化。在根目录下有两个文件夹:clientserver。在client文件夹中,您会找到页面级 CSS、图片和 JavaScript。在server文件夹中,我将代码分为controllersviews。这里还有一些模拟数据以及我们稍后将详细讨论的一些核心基础设施代码。

通用方法

在这个演示中,我遵循了一种相当标准的 MVC 风格方法。服务器由两个控制器组成:

每个控制器根据需要调用 “后端”,构建视图模型,然后将数据传递给视图引擎,后者渲染 HTML。为了提高可操作性并启用架构的一些高级功能,使用了 Handlebars 的部分视图和 HTML 辅助函数。

这并没有什么特别之处。对于自 2000 年代初 Rails 出现以来使用 MVC 框架的开发人员来说,这一切都是标准操作。然而,细节才是关键……

渲染 Web 组件

在此架构中,Web 组件模板(视图)和样式,以及它们的数据输入,都是在服务器端完全定义的,而非客户端。为此,我们为每个组件使用自定义视图,就像在传统 MVC 架构中所做的一样。让我们来看看之前提到的<ui-card>的 Handlebars 服务器代码。

 <ui-card>
<template shadowrootmode="open">
{{{shared-styles "./ui-card.css"}}}
<slot></slot>
</template>

{{>@partial-block}}
</ui-card>

这段代码定义了我们的<ui-card>部分。它没有任何数据输入,但可以使用 Handlebars 的{{>@partial-block}}语法渲染子内容。另一个值得注意的地方是shared-styles自定义 HTML 辅助函数。稍后我们将详细介绍它。现在只需知道它会自动包含指定文件中的 CSS。

定义了基础卡片后,现在可以在服务器端构建更有趣和复杂的组件。这里有一个<structured-card>,它对如何在卡片形式中构建和样式化头部、主体和页脚内容有特定的意见。

 <structured-card>
<template shadowrootmode="open">
{{{shared-styles "./structured-card.css"}}}

{{#>ui-card}}
<div part="content">
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
{{/ui-card}}
</template>

{{>@partial-block}}
</structured-card>

我们遵循与<ui-card>相同的基本模式。主要区别在于<structured-card>实际上通过{{#>ui-card}}...{{/ui-card}}在其自己的 Shadow DOM 中组合<ui-card>(因为它本身是一个 Handlebars 部分块)。

这两个组件仍然是高度通用的。因此,现在让我们看看film-card,它只是一个标准的部分视图。它不定义 Web 组件,而是通过将其与电影数据合并来使用<structured-card>

 {{#>structured-card}}
<h3 slot="header">{{film.title}}</h3>
<span slot="footer">上映日期 {{film.release_date}}</span>
{{/structured-card}}

现在我们有了一个可以渲染电影卡片的部分视图,我们可以将这些卡片组合成一个列表。这里有一个稍微高级一点的<film-list>Web 组件:

 <film-list>
<template shadowrootmode="open">
{{{shared-styles "./film-list.css"}}}

<ul hx-boost="true" hx-target="global #film-detail">
{{#each films}}
<li>
<a href="/films/{{id}}">
{{>film-card film=.}}
</a>
</li>
{{/each}}
</ul>
</template>
</film-list>

在这里,我们可以看到<film-list>有一个films数组作为输入。然后它遍历数组中的每个film,为每个元素生成一个包含我们film-card的链接,并在内部使用<structured-card>标签渲染电影数据。

如果你已经开发了很长时间的 MVC 应用程序,您可能会发现这些分解和重新组合视图的常见模式。然而,您可能注意到了一些显著的变化。

  1. 首先,每个作为 Web 组件的片段都有一个根元素。该根元素是我们选择的自定义 HTML 标签,遵循平台的自定义元素命名规则(即名称必须包含连字符)。例如:<film-list><structured-card><ui-card>

  2. 其次,每个 Web 组件都包含一个带有shadowrootmode属性的<template>元素。这声明了我们的 Shadow DOM,并使我们能够提供在使用组件时提供将在其中渲染的特定 HTML。

  3. 第三,每个组件都使用一个自定义 Handlebars HTML 辅助函数shared-styles来将样式包含在 Shadow DOM 中,确保组件始终与其所需的样式一起发送到浏览器,并且这些样式是完全封装的。

  4. 最后,用于包裹其他内容的组件使用<slot>元素(Web 标准),结合 Handlebars 的特殊{{>@partial-block}}辅助函数,允许服务器视图引擎正确地将包裹的 HTML 作为自定义元素标签的子元素渲染。

因此,该模式大致如下所示:

 <tag-name>
<template shadowrootmode="open">
{{{shared-styles "./tag-name.css"}}}

组件 HTML 在此处。
如果需要渲染子内容,请在下面添加 <slot></slot> 和部分块帮助器。
</template>

{{>@partial-block}}
</tag-name>

这是使我们能够编写服务器端渲染的 Web 组件的基本步骤。希望通过上面的几个示例,您可以看到如何使用这些简单的步骤创建各种组件。接下来,让我们深入了解一下使样式和动态行为流畅运行的服务器和客户端基础架构的技术细节。

技巧分享:共享样式

当我们第一次查看手动 DSD 基于的<ui-card>组件时,我们内联了样式。提醒一下,它看起来是这样的:

 <ui-card>
<template shadowrootmode="open">
<style>
:host { ...host styles here... }
</style>

<slot></slot>
</template>

卡片内容在此处...
</ui-card>

这个 HTML 的一个大问题是,每次我们有一个卡片实例时,我们都必须重复样式。这意味着服务器为每个卡片实例发送 CSS,而不是仅发送一次,并在所有实例之间共享。如果您有 100 张卡片,那么您就有 100 份 CSS。这显然不是理想的情况(尽管通过 GZIP 压缩可以减轻一些成本)。

附注:目前,W3C/WHATWG 正在制定一项新的 Web 标准,以在 DSD 中声明性地共享样式,以及在同一文件中表示多个样式表。一旦标准工作完成并在浏览器中推出,以下所示的解决方案将不再需要。

然而,我们可以解决这个问题。这个难题有两个部分:

为了解决这个问题,我们将创建一个简单的共享样式协议。为了解释清楚,让我们看看当服务器需要呈现两个卡片组件时会发送什么:

 <ui-card>
<template shadowrootmode="open">
<style style-id="./ui-card.css">
:host { ...host styles here... }
</style>
<shared-styles style-id="./ui-card.css"></shared-styles>

<slot></slot>
</template>

卡片 1 内容在此处。
</ui-card>

<ui-card>
<template shadowrootmode="open">
<shared-styles style-id="./ui-card.css"></shared-styles>

<slot></slot>
</template>

卡片 2 内容在此处。
</ui-card>

注意,第一个卡片实例中有一个内联的<style>元素,带有一个特殊的属性:style-id。紧接着是一个特殊的自定义元素<shared-styles>,它也有一个引用相同style-id的属性。在卡片的第二个实例中,我们不再重复<style>元素。我们只保留了引用相同style-id<shared-styles>元素。

要使这个功能正常运行,首先需要在浏览器中实现<shared-styles>自定义元素。下面我们来看看代码:

 const lookup = new Map();

class SharedStyle extends HTMLElement {
connectedCallback() {
const id = this.getAttribute("style-id");
const root = this.getRootNode();
let styles = lookup.get(id);

if (styles) {
root.adoptedStyleSheets.push(styles);
} else {
styles = new CSSStyleSheet();
const element = root.getElementById(id);
styles.replaceSync(element.innerHTML);
lookup.set(id, styles);
}

this.remove();
}
}

customElements.define("shared-styles", SharedStyle);

当浏览器将 HTML 流式传输到 DSD 时,<shared-styles>元素将触发其connectedCallback()属性。此时,该元素将读取其自身的 style-id 属性,并使用它从缓存中查找样式。

如果缓存中已有该 id 的条目:

如果缓存中没有样式:

  • 首先,元素构造一个CSSStyleSheet实例。

  • 其次,它使用 id 定位包含的 DSD 内的<style>元素。

  • 第三,<style>元素的内容用于为CSSStyleSheet提供样式。

  • 第四,将样式表缓存。

  • 最后,<shared-styles>元素从 DSD 中移除自己。

实现中还有一些值得指出的其他细节:

  • 我们使用this.getRootNode()查找<shared-styles>元素所在的 Shadow 根。如果它不在 Shadow 根内,此 API 将返回document,后者也有一个adoptedStyleSheets集合。

  • 如果<shared-styles>是第一次看到特定style-id,则它不需要将样式推送到根的adoptedStyleSheets中,因为内联的<style>元素已经存在,满足了相同的目的。

现在我们已经实现了协议的客户端部分,需要一种方法让服务器生成此代码。这就是我们一直在使用的{{{shared-styles}}}Handlebars HTML 辅助程序的角色。让我们看看它的实现:

 helpers: {
"shared-styles": function(src, options) {
const context = getCurrentContext();
const stylesAlreadySent = context.get(src);
let html = "";

if (!stylesAlreadySent) {
const styles = loadStyleContent(src);
context.set(src, true)
html = `<style id="${src}">${styles}</style>`;
}

return html + `<shared-styles style-id="${src}"></shared-styles>`;
}
}

每当使用shared-styles辅助函数时,它会执行以下步骤:

  • 获取当前请求上下文(稍后会详细介绍)。

  • 检查请求上下文,查看样式源在此请求期间是否已被发出。

  • 如果之前已被请求,则仅返回带有src作为style-id<shared-styles>元素的 HTML。

  • 如果之前未被请求,则加载 CSS 并将其发送到一个<style>元素中,紧接着是<shared-styles>元素,两个元素都设置了相同的style-id

这个简单的 HTML 辅助工具允许我们在整个请求中跟踪对同一样式的请求,并根据当前请求状态发送正确的 HTML。

最后一个要求是跟踪请求上下文,使其在控制器、视图和异步函数之间可用,否则我们无法访问它。为此,我们将使用 Node.js 的async_hooks模块。然而,一旦标准化完成,AsyncContext (https://github.com/tc39/proposal-async-context) 将成为 JavaScript 的正式部分,这是解决这个难题的最佳方法。

以下是我们如何利用async_hooks

 import { AsyncLocalStorage } from "async_hooks";

const als = new AsyncLocalStorage();

export const getCurrentContext = () => als.getStore();
export const runInNewContext = (callback) => als.run(new Map(), callback);
export const contextMiddleware = (req, res, next) => runInNewContext(next);

AsyncLocalStorage类允许我们提供状态,在本例中是一个Map,它可供在回调中运行的任何内容中都是可用的。在上面的代码中,我们创建了一个简单的 Express 中间件函数,确保所有请求处理程序都在上下文中运行,并接收请求期间唯一的Map实例。然后,这可以通过我们的getCurrentContext()辅助函数在 HTTP 请求中的任何时候访问。因此,我们的shared-stylesHandlebars HTML 辅助函数能够在给定请求中跟踪它已发送到客户端的样式,即使它没有直接访问 Express 的请求对象。

随着服务器和客户端部分的就位,我们现在可以跨组件共享样式,而无需重复,并始终确保为给定请求向浏览器提供所需 CSS 的唯一副本。

技巧分享:使用 HTMX 处理常见行为

如果您开发过几个网站 / 应用程序,你可能会注意到它们有很多共同的需求。例如,进行基本的 HTTP 请求、更改 DOM 节点、历史记录 / 导航等。HTMX (https://htmx.org/) 是一个小型 JavaScript 库,它提供了一种声明性机制,用于将许多常见行为附加到 HTML 元素,而无需编写自定义 JavaScript 代码。它与 Web 组件非常契合,特别是当您专注于服务器端渲染时。

在我们的演示应用程序中,当单击<film-list>中的项目时,我们使用 HTMX 通过 AJAX 获取电影详细信息。为了了解它的设置,让我们再次看看<film-list>Web 组件的 HTML:

 <film-list>
<template shadowrootmode="open">
{{{shared-styles "./film-list.css"}}}

<ul hx-boost="true" hx-target="global #film-detail">
{{#each films}}
<li>
<a href="/films/{{id}}">
{{>film-card film=.}}
</a>
</li>
{{/each}}
</ul>
</template>
</film-list>

HTMX 可以通过其hx-前缀属性立即识别,这些属性添加了上述的常见行为。在这个示例中,我们使用hx-boost告诉 HTMX,任何子<a>应该动态地从服务器获取其href。然后我们使用hx-target告诉 HTMX 我们希望它将服务器响应的 HTML 放置在哪里。global修饰符告诉 HTMX 在主文档中查找目标,而不是 Shadow DOM。因此,HTMX 将在文档范围内执行 querySelector (‘#film-detail’)。一旦找到,服务器返回的 HTML 将被插入到该元素中。

这是网站中一种简单但常见的需求,HTMX 可以轻松实现,并且可以在服务器 HTML 中完全指定,无需担心自定义 JavaScript。HTMX 提供了一个强大的行为库,适用于各种场景。一定要查看它 (https://htmx.org/reference/)。

在继续之前,值得注意的是,有几个技巧可以让上述 HTMX 标记在 Web 组件中工作。因此,让我们快速讨论这些技巧。

首先,默认情况下,HTMX 在全局文档中搜索其hx-属性。由于我们的 Web 组件使用了 Shadow DOM,它不会找到它们。这不是问题,我们只需要调用htmx.process(shadowRoot)来启用它(稍后我们将讨论在哪里挂钩此功能)。

其次,当 HTMX 执行 AJAX 并处理要插入 DOM 的 HTML 时,它使用了一些无法处理 DSD 的旧浏览器 API。我希望 HTMX 很快会更新以使用最新标准,但与此同时,我们可以通过以下步骤非常轻松地解决这个问题:

这可以通过少量代码来实现,如下所示:

 function attachShadowRoots(root) {
root.querySelectorAll("template[shadowrootmode]").forEach(template => {
const mode = template.getAttribute("shadowrootmode");
const shadowRoot = template.parentNode.attachShadow({ mode });
shadowRoot.appendChild(template.content);
template.remove();
attachShadowRoots(shadowRoot);
});
}

new MutationObserver((records) => {
for (const record of records) {
for (const node of record.addedNodes) {
if (node instanceof HTMLElement) {
attachShadowRoots(node);
}
}
}
}).observe(document, { childList: true, subtree: true });

最后,HTMX 有一种非常特殊的管理历史记录的方式。它将上一页转换为 HTML 字符串并将其存储在本地存储中,然后在用户返回导航时,它会检索字符串,将其解析并推回 DOM。

这种方法是默认的,并且通常对于许多应用程序来说是不足够的,因此 HTMX 提供了各种配置挂钩来关闭或自定义它,这正是我们需要做的。否则,HTMX 将无法正确处理我们的 DSD。以下是我们需要采取的步骤:

就是这样!现在我们可以在 Shadow DOM 中使用任何 HTMX 行为,处理历史记录 / 导航,并确保 AJAX 的服务器 HTML 正确渲染其 DSD。

技巧分享:使用 Web 组件 Islands 处理自定义行为

虽然 HTMX 可以处理许多常见的行为场景,但我们通常仍然需要少量的自定义 JavaScript。事实上,至少需要启用 HTMX for Shadow DOM 的自定义 JavaScript。

得益于我们对自定义元素的使用,这非常简单。每当我们想要为组件添加自定义行为时,我们只需创建一个类,注册标签名称,并添加我们想要的任何 JS。例如,以下是我们如何编写一些代码来为一组自定义元素启用 HTMX。

 function defineHTMXComponent(tag) {
customElements.define(tag, class {
connectedCallback() {
htmx.process(this.shadowRoot);
}
});
}

使用这段简短的代码,我们可以做如下操作:

 ["film-list" /* other tags here */ ].forEach(defineHTMXComponent);

现在,对于我们的<film-list>,我们不想这样做,因为我们还想添加其他行为。但是,对于任何仅需启用 HTMX 的自定义元素,我们只需将其标签添加到此数组中。

让我们把注意力更多地放在<film-list>上,让我们看看如何设置一个小的 JavaScript “island”,以确保无论我们访问哪个路径,都能正确地应用样式:

 export class FilmList extends HTMLElement {
#links;

connectedCallback() {
this.#links = Array.from(this.shadowRoot.querySelectorAll("a"));

htmx.process(this.shadowRoot);
globalThis.addEventListener("htmx:pushedIntoHistory", this.#selectActiveLink);

this.#selectActiveLink();
}

#selectActiveLink = () => {
for (const link of this.#links) {
if (link.href.endsWith(location.pathname)) {
link.classList.add("active");
} else {
link.classList.remove("active");
}
}

localStorage.removeItem('htmx-history-cache');
}
}

customElements.define("film-list", FilmList);

现在我们可以看到一切都组合在一起。接下来会发生什么?

  • 当我们定义元素时,浏览器会将它在 DOM 中找到的任何匹配我们指定的 “film-list” 标签名的自定义元素 “升级”,并应用我们在类中指定的行为。

  • 接下来,浏览器将调用connectedCallback(),我们的代码会用来在其 Shadow root 上启用 HTMX,监听 HTMX 历史记录更改,并选择与当前位置匹配的链接。每当 HTMX 更改历史记录时,相同的活动链接代码也将运行。

  • 每当我们设置活动链接时,我们会记得清除 HTMX 历史缓存,以免将 HTML 存储在本地存储中。

这就是整个应用程序中的所有自定义 JavaScript。使用标准的自定义元素,我们能够定义小的 JavaScript Islands,仅在需要的地方应用自定义行为。常见的行为由 HTMX 处理,许多组件根本不需要 JavaScript。

总结

希望您已经看到了如何轻松地将 Web 组件与 DSD 一起进行服务器端渲染,利用声明性行为,并随着应用程序的演进逐步添加自定义 JavaScript “islands”。这些步骤本质上是:

  1. 使用您的服务器框架的视图引擎为每个 Web 组件创建部分视图。

  2. 选择唯一的自定义标签作为部分视图的根,并声明一个模板,使用shadowrootmode启用 DSD。提醒:您不必使用 Shadow DOM。仅拥有自定义标签就可以启用自定义元素 islands。只有当您想要 HTML 和 CSS 封装和组合时,才需要 Shadow DOM。

  3. 使用服务器框架的 HTML 帮助器机制为您的 DSD 组件提供共享样式。

  4. 在服务器 HTML 中根据需要利用 HTMX 属性处理常见行为,确保注册标签以在 Shadow DOM 中启用 HTMX。

  5. 当需要自定义代码时,只需定义一个与您的标签名称匹配的自定义元素即可启用您的 JavaScript Island。

我们不需要采用复杂的 JavaScript 框架来利用现代浏览器功能。利用任何成熟的 MVC 服务器框架中的模式,我们可以将代码库演进为使用 Web 组件、DSD 和

Islands。这使得我们在开发中可以保持敏捷,逐步采用和演进我们的应用程序,以响应最重要的事物:我们的客户。

不要忘记查看 GitHub 上的演示:https://github.com/EisenbergEffect/server-first-web-components。

干杯!

关于本文
译者:@飘飘
作者:@Rob Eisenberg
原文:https://tympanus.net/codrops/2024/08/20/server-first-web-components-with-dsd-htmx-and-islands

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