跳至主要内容
Deno 2 终于来了 🎉️
了解更多
Fresh lemon

Fresh 1.6:Tailwind CSS 插件、简化的类型定义等等

在本轮迭代中,我们致力于实现一流的 Tailwind CSS 支持,并扩展了插件 API。除此之外,我们还花时间对各种开发人员体验进行了改进。

记住:您可以通过运行以下命令启动新的 Fresh 项目

deno run -Ar https://fresh.deno.dev

或者通过在项目文件夹中运行以下命令更新现有项目

deno run -Ar https://fresh.deno.dev/update

以下是 Fresh 1.6 中新增功能的概述。继续阅读以了解所有详细信息,以及您对 Fresh 的下一个迭代的期待。

Fresh 1.6 一览

  • 🌊 一流的 Tailwind CSS 插件 我们正在从 Twind 迁移到 Tailwind CSS。它具有更好的编辑器集成,并将 CSS 生成从渲染路径中移出。
  • 📨 带有表单的局部组件 局部组件现在支持 Form 元素。
  • 🧯 错误页面的局部组件 局部组件现在可以用来渲染错误页面。
  • 🕵️‍♀️ 可关闭的错误叠加层 开发过程中显示的自定义错误叠加层现在可以关闭,以便您可以看到项目的 500 页面。
  • ⚡️ 改进的岛屿捆绑策略 岛屿现在以更智能的方式捆绑在一起,这会导致更少的生成文件,并允许您将相关的岛屿分组在一起。
  • ⚔️ 减少清单合并冲突 清单文件中的标识符现在是从文件名派生的,而不是递增的数字。 这极大地减少了合并冲突的可能性。
  • 📦 支持预生成资产 Fresh 在遇到 _fresh/static 中的相同文件时,会优先提供生成的资产。
  • 🧩 插件 API 增强 插件 API 现在支持从插件添加岛屿,添加 <link> 元素,并且有一个新的 configResolved 钩子。
  • 🏁 更快的路由匹配 路由匹配性能得到了提升,尤其是在项目拥有大量静态文件的情况下。
  • 🛣️ 从子路径提供服务 Fresh 项目现在可以在子路径下提供服务,例如 https://example.com/foo/bar,而不是域名的根路径。
  • 🏆 简化的类型定义 不同的上下文类型的数量已从 6 个减少到 2 个,现在只有一个 props 类型,而不是四个。

一流的 Tailwind CSS 插件

这是一个期待已久的更新,Fresh 现在附带了合适的 Tailwind CSS 插件。它使用与 Node 中相同的 npm 包。这意味着我们将 Twind 插件置于维护模式,并将其标记为已弃用。

Tailwind CSS 比 Twind 具有许多优势,其中最重要的一个优势是 Tailwind CSS 处于积极维护状态,并支持更完善的编辑器体验。切换到 Tailwind CSS 也会为您带来一些不错的性能优势,因为它只需要在部署时生成 CSS 一次,而 Twind 则是在每次请求时动态生成 CSS。

要从 Twind 切换到 Tailwind CSS,请按照我们文档中的本指南进行操作。请注意,此插件需要提前构建才能为您的项目设置。

Tailwind CSS vscode 扩展在 Fresh 项目中的实际应用。

感谢Jason Gardner 为初始的 tailwindcss 插件集成提供了原型!

带有表单的局部组件

我们已经扩展了局部组件的功能,使其能够与 <form> 元素一起使用。类似于现有的针对 <a> 元素的局部组件支持,Fresh 在提交者的父元素具有“真值” f-client-nav 属性时,将选择使用局部组件处理表单。即使提交按钮位于封闭表单元素之外,这也适用。

<form id="foo">
  <Partial name="slot-1">
    <input type="text" value={value} name="name" />
  </Partial>
</form>
<button
  type="submit"
  form="foo"
  formaction="/form"
  f-partial="/form"
  formmethod="POST"
>
  submit
</button>

错误页面的局部组件

使用 Fresh 1.6,您现在也可以对错误页面使用局部组件。以前,当响应状态码不是 ok 时,局部导航会出错。此限制已解除,我们只检查响应内容类型是否为 HTML。

在处理代码的这一部分时,我们还添加了对局部导航自动跟随重定向的支持。

可关闭的错误叠加层

在 Fresh 1.5 中,我们在开发过程中添加了一个错误叠加层,它显示有关错误发生位置的详细信息。问题是,它覆盖了用户的 _500.tsx 错误页面。我们修复了这个问题,现在错误叠加层是一个可以关闭的真正的错误叠加层。

改进的岛屿捆绑策略

在浏览器中,我们发现我们可以改进提供岛屿 JavaScript 代码的方式。以前,每个岛屿组件都会被移动到一个单独的包中,这会导致生成许多小于 1kb 的 .js 文件。新的策略遵循原始文件,让您对如何最好地为您的项目捆绑岛屿有更多控制权。通过尊重原始文件,您可以将相关的岛屿分组在一起并将它们包含在同一个包中。

减少清单合并冲突

虽然我们希望从长远来看消除清单,但我们认为可以改进当前频繁遇到的痛点,即在添加新路由或重命名路由时发生合并冲突。以前,标识符由 $<number> 组成,其中数字部分仅仅是递增的。新方法将文件名转换为有效的 JavaScript 标识符,并且只有在找到具有相同名称的现有标识符时才会附加一个数字。

感谢Reed von Redwitz 将此功能完善!

Screenshot of a fresh.gen.ts manifest file where import names are serialized file names instead of a `$` with an incremented number

支持预生成资产

在许多情况下,您可能希望为部署生成资产文件。这些可能包括文件优化或生成 CSS(例如,用于 Tailwind CSS 插件)。其工作原理是,Fresh 会优先提供驻留在 <project>/_fresh/static 中的静态文件,而不是默认的 <project>/static 目录。

插件 API 增强

插件 API 是我们一直想要改进但没有时间进行的领域。随着此次发布,我们腾出了专门的时间来扩展它,加入了一些用户一直在要求的新功能。

来自插件的岛屿

其中最令人兴奋的功能之一是能够从插件添加岛屿。通过指定岛屿文件的路径,Fresh 将以与将它们放置在 islands/ 目录中相同的方式处理它们。

import { Plugin } from "$fresh/server.ts";
import * as path from "https://deno.land/[email protected]/path/mod.ts";

const __dirname = path.dirname(path.fromFileUrl(import.meta.url));

export default function myIslandPlugin(): Plugin {
  return {
    name: "my-island-plugin",
    islands: {
      baseLocation: import.meta.url,
      paths: [
        "./plugin-islands/MyIsland.tsx",
        "./plugin-islands/OtherPluginIsland.tsx",
      ],
    },
  };
}

感谢 Reed von Redwitz 帮助我们理清了所有细节。

插件现在可以添加 <link> -elements 来添加额外的样式表或类似内容。

import { Plugin } from "$fresh/server.ts";

function MyPlugin(): Plugin {
  return {
    name: "link-inject",
    render(ctx) {
      ctx.render();
      return {
        links: [{ rel: "stylesheet", href: "styles.css" }],
      };
    },
  };
}

感谢 Adam Gregory 为实现此功能而做出的努力!

新的 configResolved 钩子

通常情况下,您的插件需要根据 Fresh 配置应用不同的逻辑。我们已经将配置传递给 onBuildStart(config),但对于插件 API 的其他部分来说,这太晚了。因此,我们添加了一个新的 configResolved 钩子,类似于 vite,它允许您获取完全解析的 Fresh 配置。

import { Plugin, ResolvedFreshConfig } from "$fresh/server.ts";

function MyPlugin(): Plugin {
  let config: ResolvedFreshConfig;

  return {
    name: "my-cool-plugin",
    configResolved(resolvedConfig) {
      config = resolvedConfig;
    },
  };
}

更快的路由匹配

我们查看的另一个旨在让 Fresh 更快的领域是我们的路由器。我们注意到,在处理和匹配路由的方式上存在一些低效,尤其是在项目有许多静态文件时。我们添加了另一个优化,它检测路由是否没有动态部分,并将回退到简单的字符串比较。

总的来说,在应用所有优化之后,我们看到在 deno.com 上的路由匹配时间提高了 4-10 倍。

从子路径提供服务

并非每个项目都在根域地址上提供服务,用户的一个普遍需求是能够从子路径提供 Fresh。例如,它不应该托管在 https://example.com/ 上,而应该在 https://example.com/foo/bar 上提供。现在,使用新的 router.basePath 配置选项可以实现这一点。

// fresh.config.ts
import { defineConfig } from "$fresh/server.ts";

export default defineConfig({
  router: {
    basePath: "/foo/bar",
  },
});

再次感谢 Reed von Redwitz 将其推向完成!

简化的类型

我一直觉得我们可以在用户界面 API 的类型方面做得更好。例如,看看各种中间件和路由上下文类型。

  • MiddlewareHandlerContext
  • AppContext
  • ErrorHandlerContext
  • HandlerContext
  • LayoutContext
  • UnknownHandlerContext

需要了解很多类型。通过最近的代码清理和内部简化,我们意识到它们大多数都是相同的类型。因此,我们将上下文类型的数量减少到只有两个。

  • FreshContext
  • RouteContext(用于异步路由/布局/应用程序包装器)

将来,我们甚至可能将其进一步减少到只有一个。请注意,我们仍然保留旧类型作为新类型的别名,以避免造成重大更改。

在简化核心的过程中,我们注意到我们的道具类型也同样复杂,比应该的复杂得多。

  • ErrorPageProps
  • AppProps
  • UnknownPageProps
  • LayoutProps

这四种类型已简化为单一类型

  • PageProps

我们对目前的简化感到非常满意,因为它减轻了使用 Fresh 的心理负担。

除了类型简化之外,我们还确保 Fresh 上下文对象在任何地方都可用。它现在也被传递给中间件和路由。此外,我们现在在上下文中传递完全解析的 Fresh config,这样您就可以根据它应用不同的逻辑。

未来展望

与每次发布一样,我希望能够在这个版本中包含更多内容。事实是,这些功能需要更多时间来打磨。好奇的 Deno 阅读者可能已经注意到最近 Deno 版本说明中的 新的“预编译”JSX 转换。这是我在这个周期中花费了相当多时间的东西,也是我想要尽快带到 Fresh 的东西。它主要是对 JSX 转译方式的性能改进,将使大多数 Fresh 站点提速 2-4 倍。

我与 Bartek 一起完成的另一件事是 Deno 的 HMR。最初的原型也已在 Deno 1.38 中落地,但还需要做更多工作才能将其连接到 Fresh 中,并使其运行顺畅。

当然,还有更多工作要做,围绕着 Partial!我真的很想有一个合适的方法来指定待处理状态,进行错误处理,并公开功能,以便您可以在事件监听器中调用它。我不知道这最终会是什么样子,但这是我在 Fresh 中真正想要找到一个好的答案的东西。

由于圣诞节期间的假期,12 月的周期将比平时短一些。我们很可能会跳过一个功能发布,只在年底再发布另一个补丁版本。可以在 12 月迭代计划 中看到当前的迭代计划。