我做了一些不好的事情,我很高兴分享它。
自我犯规的问题(许多人)
我曾经将我的个人网站运送到具有慷慨的免费计划的Vercel。但是,我一直想使用Cloudflare,特别是如果我打算在那里购买该域。
由于我的网站只是一堆静态内容,只有很少的交互式组件,因此我选择了Astro。而且,如果我需要添加一些花哨的东西,我就不会添加麻烦。例如,“共享”按钮是一个预先组件(即使它不一定是)。
但是,有时您真的希望该页面被渲染为服务器端。就我而言,/feed
页面是我发表的所有文章的列表,列表可能会变得很漫长(如果我实际上保证写更多书)。为了使读者过滤他们感兴趣的主题,我在每个帖子中添加了一个标签列表,在/feed
页面上列出了所有主题:
每个过滤器芯片可能是一个链接,添加了用于过滤的查询参数,例如?topic=web
。然后,在服务器上,我将读取参数并过滤feed:
/* Server only */
const topic = Astro.url.searchParams.get("topic");
// returns ALL articles
const feed = await getFeed();
const filteredFeed = topic
? feed.filter((post) => post.data.tags.includes(topic))
: feed;
// create a flat list of all unique tags used
const topics = [...new Set(feed.flatMap((post) => post.data.tags))];
没有客户端JavaScript,因此,由于查看过渡,访问者几乎没有注意到该页面是服务器端渲染的。
为不存在的问题带来解决方案;还有两个问题
当我从Vercel切换到Cloudflare时,我立即注意到了主要问题:我的图像未进行优化。 astro:assets使用Sharp提供了图像优化,并且CloudFlare适配器不支持它(maybe only for now?)。因此,我选择了:
- 每次手动优化图像;
- 请勿优化图像;
- 请勿使用SSR,在所有内容的构建时间构建页面,应用过滤客户端;
由于前两个选项不喜欢我,我想我可以用最后一个选择。
显然,我只能删除标签过滤,没有人会注意到。但是,嘿,我们在这里进行工程师,我们喜欢挑战。
第一个快速解决方案是通过JavaScript添加过滤,我们都做到了:
- 从服务器中获取文章和标签列表;
- 将其传递给跟踪选定芯片并渲染列表的组件;
- 对芯片选择反应以过滤列表;
对于任何网络框架来说,这似乎是一项琐碎的任务。对于Svelte,我什至可以避免运送大量JavaScript。但是,嘿,我们在这里进行工程师,我们喜欢挑战。
解决方案
我知道我只能渲染所有文章,然后隐藏那些与display: none
不匹配的过滤的文章。我也知道我可以使用:has CSS选择器进行切换。所以,我需要:
- 与每个标签相对应的
value
的复选框列表
<form>
<input type="checkbox" value="react" />
<input type="checkbox" value="tailwindcss" />
</form>
- 检查一个复选框时隐藏所有文章(应用过滤)
/* if any of the checkboxes is selected, hide all articles */
.feed:has(> form input:checked) .feed-entry {
display: none;
}
/* TODO: put display: block to those matching selected checkboxes */
- 显示匹配过滤器的文章
显示与过滤器相匹配的文章
这一点是最复杂的。由于标签和文章的列表是动态的,因此我不仅可以在CSS中列出所有标签,但我知道我需要这样的东西:
.feed:has(> form input:where(
/* list of all article tags */
[value=react]:checked,
[value=tailwindcss]:checked
) matching-article { display: block; }
我决定采用一种直接的方法:生成<style>
标签,每篇文章都呈现:
/* for each post, take it's slug as id */
const id = `entry-${post.slug}`;
/* form a CSS selector */
const css = /* CSS */ `
.feed:has( > form input:where(
${post.data.tags.map((tag) => `[value="${tag}"]:checked`).join(",")})
) #${id} { display: block; }`;
return (
<>
<style set:html={css} />
<li>
<a href={post.slug}>{post.title}</a>
</li>
</>
);
现在,每篇文章都包括UI和<style>
标签,当选择正确的复选框时,它可以看到它。
P.S。同样,任何网络框架都可以更好地解决这个问题。通常,您只需将代码较少,因为您只能为每篇文章重复使用HTML,而只需运送JS在运行时生成的模板即可。特别是对于此功能,可以完全忽略JS被禁用或无法加载的风险。
重置状态
当访问者选择多个芯片时,我们希望他们能够重置选择以再次查看所有文章。解决方案很简单:reset input/button:
<form>
/* style same way as the rest of the chips */
<input type="reset" />
<input type="checkbox" value="react" />
</form>
小雨
可以优化使用每篇文章的CSS运输,以减少代码。由于列表在构建时间充分渲染,我利用闪电快速lightningcss:
import { transform } from "lightningcss";
// in each article
const { code } = transform({
code: Buffer.from(/* CSS */ `
.feed:has( > form
input:where(${post.data.tags
.map((tag) => `[value="${tag}"]:checked`)
.join(",")}
)) #${id} { display: block; }
`),
minify: true,
});
return (
<>
<style set:html={code} />
也很高兴添加视图过渡,以平稳地出现和消失的列表项目。类似:
<form
onchange="document.startViewTransition()"
onreset="document.startViewTransition()"
>
这不起作用,因为在拍摄快照之前更改了列表项目?不确定。
概括
您可以在此部署上进行最终结果:
https://1f1e705a.alvechy.pages.dev/feed
正如我所说,这是一个挑战,而不是最终解决方案。你应该做什么?
- 船JavaScript;
- 利用服务器。例如,只有几点javaScript,您就可以作为用户滚动来实现feed的懒惰加载,而不是按照; ; 来运送巨大的HTML
- 船更圆角;