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

Fresh 1.4 – 页面加载更快、布局更灵活,还有更多

在本轮更新中,我们重点关注了整体开发体验,旨在简化在 Fresh 中使用共享布局、路由特定岛屿等功能的操作。

请记住:您可以通过运行 deno run -A -r https://fresh.deno.dev 创建一个新的 Fresh 项目,或者在项目文件夹中运行 deno run -A -r https://fresh.deno.dev/update . 更新现有的项目。

使用提前编译实现更快的页面加载

到目前为止,Fresh 一直都是动态编译资源。这种方式在过去一直很有效,因为它无需构建步骤即可实现极快的部署,但我们意识到,使用大型岛屿的即时(=JIT)渲染速度明显较慢。我们找到了一个预编译解决方案,可以将无服务器函数冷启动的资源服务速度提高约 45-60 倍,而对部署时间的影响极小。节省时间取决于岛屿的大小,但即使对于较小的岛屿,改进也是非常明显的。

以下是 Fresh 文档网站上不同技术的演示。搜索框是一个小岛屿,大小约为 30kB。

使用提前编译的资源,搜索框岛屿几乎瞬间恢复,而使用 JIT 编译则需要 4.28 秒。

在本地运行开发服务器时,Fresh 将始终使用 JIT 编译,以便服务器能够尽快响应 API,而无需等待资源编译完成。

您可以按照提前编译指南选择在部署时使用 AOT 编译。运行 deno task build 将创建一个 _fresh 文件夹,其中包含所有生成的资源。

自定义 html、head 和 body 标签

在以前的版本中,设置 <html> 标签的 lang 属性是一个棘手的问题。到目前为止,Fresh 在内部创建了外部 HTML 结构,直到 <body> 标签,您需要应用一些变通方法,例如创建自定义渲染函数来修改 lang 属性。

await start(manifest, {
  // Old way of setting the `lang` attribute,
  // requires a custom render function :(
  render: (ctx, render) => {
    ctx.lang = "de";
    render();
  },
});

经过一番思考,我们意识到,我们可以通过允许您自己渲染 HTML 文档来简化 Fresh。因此,我们正是这样做的。在 Fresh 1.4 中,您可以直接在服务器上设置 <html><head><body> 标签。

// routes/_app.tsx
import { AppProps } from "$fresh/server.ts";

export default function App({ Component }: AppProps) {
  return (
    <html lang="de">
      <head>
        <title>My Fresh App</title>
      </head>
      <body>
        <Component />
      </body>
    </html>
  );
}

为了使从旧版 Fresh 版本升级更容易,我们添加了一些逻辑来检测是否渲染了 <html> 标签。如果未渲染任何标签,我们回退到像过去 Fresh 版本一样使用内部模板包装 html。

布局

构建 Web 应用程序的一个常见方面是,许多布局部分在所有路由之间共享。例如,网站的页眉或页脚在所有路由中都是相同的组件。以前,您可以在 routes/_app.tsx 中实现这一点,但无法超越此范围。在您的应用程序中为一些子路由创建共享布局需要将代码提取到一个组件中,并将其手动导入所有路由。

在 Fresh 1.4 中,我们添加了对 _layout 文件的支持,可以将其描述为路由本地的应用包装器。它们可以放在任何路由文件夹中,Fresh 将检测所有匹配的布局,并将它们叠加在一起。

routes/
  _app.tsx
  _layout.tsx
  page.tsx      # Inherits _app and _layout

  sub-route/
    _layout.tsx # Inherits _app and _layout
    index.tsx   # Inherits _app, _layout and sub-route/_layout
    about.tsx   # Inherits _app, _layout and sub-route/_layout

_layout 文件看起来非常类似于路由文件或应用包装器。它使用 Component 属性来继续渲染更进一步的布局或最终的路由文件。

// routes/_layout.tsx
import { LayoutProps } from "$fresh/server.ts";

export default function MyLayout({ Component }: LayoutProps) {
  return (
    <div class="my-layout">
      <h2>This is rendered by a layout</h2>
      <Component />
    </div>
  );
}

但有时候您不希望继承布局,甚至不希望继承应用包装器,因此我们也添加了一种方法,让您可以选择不继承它们。

export const config: RouteConfig = {
  skipAppWrapper: true, // Disable rendering app wrapper
  skipInheritedLayouts: true, // Disable already inherited _layout templates
};

感谢Michael Gearhardt 启动了此功能的开发工作!

异步布局和异步应用包装器

在支持布局之后,我们开始思考,如果将它们与路由一样处理会怎么样?我们是否可以也允许使用异步布局组件?这将通过使路由组件和布局组件的行为相同来减少认知负荷。经过一番编码,我们发现可以做到。因此,此 Fresh 版本不仅为您带来了 _layout 组件,还带来了异步布局!

export default async function Layout(req: Request, ctx: LayoutContext) {
  const person = await fetchSomeData();

  return (
    <div>
      <h1>Hello {person.name}</h1>
      <ctx.Component />
    </div>
  );
}

既然这样,为什么不让应用包装器也变成异步的呢?在 Fresh 1.4 中,所有布局的行为都是相同的。唯一的特殊情况是路由,因为它们位于渲染链的末尾,因此没有 ctx.Component 属性。

使用 define 函数加快输入速度

随着异步路由组件的引入,我们收到了一些反馈,即由于需要使用太多关键字,函数定义变得有点“冗长”。

export default async function Page(req: Request, ctx: RouteContext) {
  // ...
}

我们也认同这些意见。我注意到,我总是需要花一些时间才能将这些代码键入。当然,您可以在编辑器中添加自定义代码段来创建这些样板,但这似乎更像是一种变通方法,而不是一个合适的解决方案。

因此,我们花了一些时间来回抛出了一些想法,直到我们想出了 define* 辅助函数的概念。它们不包含任何逻辑,但它们提供开箱即用的代码自动完成提示,无需您自己定义类型。

// Both `req` and `ctx` will have the correct type already
export default defineRoute(async (req, ctx) => {
  // ...
}

如果您查看这两个代码片段,似乎没有太大区别。但当您在编辑器中键入它们时,您会注意到后面的代码键入起来快得多。

以下 define 辅助函数可用

  • defineRoute 用于创建路由
  • defineLayout 用于创建布局
  • defineApp 用于创建应用包装器。

使用路由组组织代码

通常,routes/ 目录中的嵌套文件夹会直接映射到 URL。但是,对于更大的项目,经常会出现您想将文件分组,而不影响 URL 结构的情况。

路由组可以实现这一点。路由组是 routes/ 中的一个文件夹,其名称用括号括起来,例如 (my-group)。这还允许您为同一段上的路由创建不同的 _layout_middleware 文件。

routes/
  (marketing)/
    _layout.tsx
    about.tsx  # Maps to /about

  (blog)/
    _layout.tsx
    archive.tsx # Maps to /archive

共置岛屿、组件等

当路由组文件夹的名称以下划线开头时,例如 (_components),Fresh 将忽略该文件夹,并将其视为私有文件夹。这意味着您可以使用这些私有路由文件夹来存储与特定路由相关的组件。唯一特殊的名称是 (_islands),它指示 Fresh 将该文件夹中的所有文件视为岛屿。

routes/
  shop/
    (_components)/  # ignored by the router
      Section.tsx

    (_islands)/     # local islands folder
      Cart.tsx

    index.tsx

将这些功能结合在一起,您可以根据功能组织代码,并将所有相关组件、岛屿或其他任何内容放在共享文件夹中。

未来展望

还有更多正在开发的功能,但由于需要更多时间完善,所以还没有发布。特别是,我们正在彻底改造我们的插件系统,使其更易于理解且功能更强大。添加对视图过渡的支持的PR进展顺利,我们将利用它来探索如何在Fresh中添加类似SPA的客户端导航。另一个我们一直在研究的领域是样式解决方案,例如UnoCSS、直接使用tailwind以及其他解决方案。

与上个月一样,您可以关注本月的GitHub上的迭代计划

您知道吗? Deno 1.36刚刚发布。

请务必查看Deno 1.36的发布说明,其中包含改进的安全控制、测试、基准测试等。