跳到主要内容
Deno 2.4 现已推出,带来了 deno bundle、字节/文本导入、OTel 稳定版等功能
了解更多
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

在你的项目文件夹中。

以下是 1.6 版本的新功能概述。继续阅读以了解所有细节,以及 Fresh 下一个迭代的预期。

Fresh 1.6 概览

一流的 Tailwind CSS 插件

经过漫长的等待,Fresh 现在附带了一个真正的 Tailwind CSS 插件。它使用的是与您在 Node 中使用的完全相同的 npm 包。这意味着我们将 Twind 插件置于维护模式并将其标记为弃用。

Tailwind CSS 相较于 Twind 有许多优势,其中最重要的是 Tailwind CSS 正在积极维护,并支持更完善的编辑器体验。通过切换到 Tailwind CSS,您还将获得一些不错的性能优势,因为它只需要在部署时生成一次 CSS,而 Twind 则需要在每次请求时即时生成。

要从 Twind 切换到 Tailwind CSS,请遵循我们文档中的此指南。请注意,此插件要求为您的项目设置提前构建

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

感谢 Jason Gardner 原型化了最初的 tailwindcss 插件集成!

支持表单的部分内容渲染 (Partials)

我们扩展了部分内容渲染 (Partials) 的能力,使其适用于 <form> 元素。与现有的 <a> 元素的部分内容渲染 (Partials) 支持类似,当提交者的父元素具有“真值”的 f-client-nav 属性时,Fresh 将选择对表单进行部分内容渲染。即使提交按钮位于包含表单元素的外部,此功能也适用。

<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>

错误页面上的部分内容渲染 (Partials)

随着 Fresh 1.6 的发布,您现在也可以将部分内容渲染 (Partials) 用于错误页面。此前,当响应状态码不正常时,部分导航会出错。此限制已被移除,我们现在只检查响应内容类型是否为 HTML。

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

可关闭的错误覆盖层

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

改进的 Islands 打包策略

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

减少 Manifest 文件合并冲突

尽管我们长期目标是摆脱 manifest 文件,但我们认为可以改进当前在添加新路由或重命名路由时频繁遇到合并冲突的痛点。此前,标识符由 $<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 一直是我们希望改进的领域,但此前没有时间。通过本次发布,我们专门投入时间来扩展它,增加了一些用户一直要求的新功能。

来自插件的 Islands

其中最令人兴奋的功能之一是能够从插件添加 Islands。通过指定 Island 文件的路径,Fresh 将像处理放置在 islands/ 目录中的文件一样处理它们。

import { Plugin } from "$fresh/server.ts";
import * as path from "https://deno.land/std@0.208.0/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> 元素来添加额外的样式表或类似内容。

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 的其他部分来说为时已晚。因此,我们添加了一个类似于 vite 的新 configResolved 钩子,允许您获取完全解析的 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/foo/bar 下可用,而不是托管在 https://example.com/。现在,通过新的 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(用于异步路由/布局/应用包装器)

将来我们甚至可能将其进一步减少到只剩一种。请注意,我们仍然保留旧类型作为新类型的别名,以避免这是一个破坏性更改。

在简化核心的过程中,我们注意到我们的 props 类型定义也同样复杂,超出了应有的程度。

  • ErrorPageProps
  • AppProps
  • UnknownPageProps
  • LayoutProps

这四种类型已合并为单一类型:

  • PageProps

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

伴随着类型简化,我们确保 Fresh 上下文对象在任何地方都得以暴露。它现在也传递给中间件和路由。此外,我们现在在上下文中传递完全解析的 Fresh config,以便您可以根据它应用不同的逻辑。

展望未来?

与每个版本一样,我希望在此版本中包含更多内容。事实是,这些功能需要更多时间进行完善。好奇的 Deno 读者可能已经注意到最近 Deno 发布说明中的新的“预编译”JSX 转换。这是我在这个周期中花费了相当多时间研究的内容,也是我希望尽快引入 Fresh 的功能。它主要是 JSX 转译方式的性能变化,将使大多数 Fresh 站点的速度提升 2-4 倍。

我与 Bartek 合作的另一件事是 Deno 的 HMR。最初的原型也已在 Deno 1.38 中实现,但仍需要更多工作才能将其整合到 Fresh 中并使其流畅。

当然,关于部分内容渲染 (Partials) 还有更多工作要做!我非常希望能有一种恰当的方式来指定挂起状态、进行错误处理并暴露功能,以便您可以从事件监听器中调用它。我不知道这一切具体会是什么样子,但这确实是我希望在 Fresh 中能有一个好的解决方案的功能。

由于圣诞节假期,12 月的开发周期将比往常短一些。我们很可能会跳过一个功能发布,只在年底进行另一次补丁发布。当前的迭代计划可以在12 月迭代计划中查看。