自从我们发布 Fresh 1.2 以来,仅仅一个月过去了,我们已经带着另一个版本回来了!我们计划每月发布一次新的 Fresh 次要版本。

这个周期包含了许多来自社区的令人难以置信的 PR,这简直令人惊叹!文档已得到扩展和改进,修复了许多错误并添加了新功能。感谢所有帮助使此版本成为可能的人。但是说得够多了,让我们来看看我们对 Fresh 所做的所有改进。

异步路由组件

我们听到了很多反馈,将数据从路由处理函数传递到页面的组件需要一些烦人的样板代码。为了确保类型安全,您始终必须为组件的 props 创建一个类型接口,将其作为泛型传递给 Handlers 类型,并在组件定义中使用它。这是相当多的步骤!

// 之前的版本
interface Data {
  foo: number;
}

export const handler: Handlers<Data> = {
  async GET(req, ctx) {
    const value = await loadFooValue();
    ctx.render({ foo: value });
  },
};

export default function MyPage(props: PageProps<Data>) {
  return <p>foo is: {props.data.foo}</p>;
}

由于 GET 处理函数通常与它将渲染的组件高度耦合,因此明显的问题是为什么我们不将两者合并在一起?这正是我们所做的。在 Fresh 1.3 中,我们可以显著简化之前的片段:

export default async function MyPage(req: Request, ctx: RouteContext) {
  const value = await loadFooValue();
  return <p>foo is: {value}</p>;
}

由于处理函数和组件都在同一个函数中,因此无需声明中间接口来在两者之间传递数据; 您可以直接传递数据。

但别担心,没有必要重写所有路由。事实上,我们不喜欢自己重写代码。新方法只不过是一个附加选项,可以使简单的路由更容易编写。但我们并不强制使用异步路由。

当您响应其他 HTTP 方法时, POST 可能是您无论如何都需要一个处理函数:

export const handler: Handlers<{}> = {
    POST(req) {
      // ... do something here
    },
  };
  
export default async function MyPage(req: Request, ctx: RouteContext) {
  const value = await loadFooValue();
  return <p>foo is: {value}</p>;
}

此外,从处理函数和 ctx.render() 单独的组件函数渲染路由的现有方式将继续工作,并且可以单独测试渲染和数据检索机制。

从插件添加路由和/或中间件

插件在扩展 Fresh 的内置功能方面非常强大。使用 Fresh 1.3,他们可以注入虚拟路由和中间件。这对于添加开发特定路由或管理仪表板的插件特别有用。

function myPlugin() {
  return {
    name: "my-plugin",
    middlewares: [
      {
        middleware: { handler: () => new Response("Hello!") },
        path: "/hello",
      },
    ],
    routes: [
      {
        path: "/admin/hello",
        component: () => <p>Hello from /admin/hello</p>,
      },
    ],
  };
}

感谢 Reed von Redwitz 和 iccee0 的贡献。

默然 500 错误模板

尽管我们程序员试图解释每种情况,但经常会出现发生错误的意外情况。我们稍微简化了错误处理,当路由处理函数中发生错误时,Fresh 现在会自动渲染 _500.tsx 模板组件。

export const handler = (req: Request, ctx: HandlerContext): Response => {
  // Fresh 将会捕获这个异常并渲染 500 模板
  throw new Error("Catch me if you can");
};

感谢Kamil Ogórek添加这个。

错误边界

虽然比处理函数中的错误少见一些,但在渲染过程中也可能发生错误。出于这个原因,我们添加了对错误边界的基本支持。当 Preact 检测到具有方法或静态 componentDidCatch() getDerivedStateFromError 方法的类组件时,该组件将被视为错误边界。在渲染期间发生错误时,这些组件可以捕获错误并渲染默认 UI:

class ErrorBoundary extends Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };
  }

  render() {
    return this.state.error
      ? this.props.fallback(this.state.error)
      : this.props.children;
  }
}

// Usage:
<ErrorBoundary fallback={(error) => <p>Error happened: {error.message}</p>}>
  <SomeComponentThatThrows />
</ErrorBoundary>;

当您处理高度动态的数据并希望渲染默认 UI 而不是渲染 500 错误页面时,这非常有用。虽然 Fresh 1.3 中的更改为捕获渲染错误奠定了基础,但我们已经在考虑如何让 API 在 Fresh 本身中更加根深蒂固。

在同一文件中导出多个 island 组件

在以前的Fresh版本中,每个 island 都应该存在于自己的文件中,并通过导出 default 导出。每个 island 文件都被视为自己的入口点,并以单独的 JavaScript 文件的形式运送到浏览器。此限制已被删除,您现在可以根据需要在单个文件中导出任意数量的 island 组件。

// ./islands/MyIsland.tsx
// Export multiple islands in Fresh 1.3
export default function SayHello() {
  // ...island code here
}

export function OtherIsland() {
  // ...island code here
}

export function AndAnotherIsland() {
  // ...island code here
}

将 island 分组到一个文件中可以减少网站必须发出的请求数量,甚至可以使某些网站更快一些!请注意,虽然将所有 island 放在同一个文件中很诱人,但该模式可能会产生相反的效果。一个好的经验法则是,对在同一页面上一起使用的 island 进行分组可以为您提供最高的性能。

感谢Reed von Redwitz的贡献。

新鲜 linting 规则

提供反馈的最佳方式是在编辑器中。我们开始跟踪常见的意外错误,例如命名处理函数导出 handlers 而不是 handler 将其集成到我们的 linter 中。每当我们检测到错误时,您都会在编辑器中获得这些波浪线。

screenshot of lint

造成混淆的一个常见来源是,当某些内容配置错误或完全错误时,Fresh 通常会向用户显示神秘的错误消息。我们检查了常见的错误,并重写了错误消息,使其更具可读性。虽然我们认为我们已经覆盖了很多的区域,但可能在更多情况下我们还可以做得更好。因此,如果您遇到任何让您感到困惑的错误消息,请联系我们!

新的 linting 规则适用于新的 Fresh 项目。要将它们与现有项目一起使用,只需将其放入您的 deno.json 文件中:

{
  "lint": {
    "rules": {
      "tags": ["fresh", "recommended"]
    }
  }
}

支持 Deno.serve

在最近的Deno 1.35.0版本中, Deno.serve API被标记为稳定。我们在 Fresh 中效仿,在 1.3 版中,我们将在可用 Deno.serve 时使用。这个新的 API 不仅速度更快,而且比以前的 std/http serve API简单得多 。

感谢Lino Le Van的贡献。

更多改善

我们提供了许多小的改进,例如能够从每个处理函数或中间件访问路由参数。BigInt 值现在可以作为参数传递到 island 组件。新建模板现在看起来好多了,还有更多!

Screenshot