Primate 是最后一个伟大的 Web 框架
Primate Is the Last Great Web Framework

原始链接: https://superarch.org/theanswerisc/primate-is-the-last-great-framework.html

JavaScript 生态系统已转向碎片化模式,开发人员必须手动将不同的库组合在一起,或者被迫选择僵化、特定于框架的元框架。这些元框架通常迫使开发人员只能选择单一的前端库(如 React 或 Svelte),并将其锁定在特定的运行时中,从而造成不必要的阻力和供应商锁定。 Primate 是一款全新的 Web 框架,旨在回归 Laravel 等传统后端框架那种连贯的一体化体验,同时拥抱现代模块化。它将后端视为主要且稳定的基础,允许前端框架、服务器运行时和编程语言独立替换。 使用 Primate,你可以在同一个应用模型中为不同的路由使用 React 或 Svelte,或者集成 Go 或 Python 等不同的后端语言。通过负责处理这些技术之间的“接缝”(如路由、会话处理和验证),Primate 提供了一个统一且稳定的环境。其目标是消除生态系统锁定,使开发人员能够构建灵活、高性能且能随技术演进而保持适应性的应用程序,同时维持一致且连贯的开发体验。

关于 Web 框架“Primate”的 Hacker News 讨论显示,社区对此持怀疑态度。评论者主要集中在以下几点: * **架构顾虑:** 有用户强烈反对该框架以路由器为中心的数据库管理方式,认为数据才应是应用设计的核心。 * **设计批评:** 用户指出了文档在可访问性方面的不足,特别提到浅色背景下浅灰色代码高亮难以辨认。作者对此表示认可并进行了修复。 * **概念混淆:** 参与者对文章标题提出质疑,讨论“Last”究竟是指“最新”还是某种最终形态;另有人指出,该框架所谓的“最后一个伟大的 Web 框架”这一概念,与传统的“中间件”极为相似。 总的来说,这场讨论凸显了该框架宏大的品牌定位与开发者在易用性和架构理念上的实际顾虑之间的分歧。
相关文章

原文

I'm the creator of Primate, so take the title with the appropriate grain of salt. By "great web framework", I mean something specific: a tool that takes ownership of the whole stack. Routing, rendering, data, validation, sessions, deployment targets, runtime support. The pieces should be designed to fit together.

I grew up building web apps with PHP frameworks: mostly Yii, some Laravel. What I loved about them was not PHP itself, but the feeling that the framework owned the stack. Databases, templates, routing, validation, patterns like MVC. You could swap pieces inside one coherent system. You could go to the framework's website and see what was officially supported. You knew what was familiar territory, and what was outside the map.

As JavaScript moved to the server, that idea mostly disappeared. The JavaScript ecosystem strongly prefers composition over cohesion. You choose a server framework, then a database client, then validation, then sessions, then a frontend, then a build tool, then glue it all together. Each piece may be excellent on its own, but nobody owns the seams.

A UNIX purist might say that is the right model: each tool should do one thing well. But web applications are not just pipelines of isolated tools. They are full of shared assumptions: request shapes, validation boundaries, session handling, rendering, routing, serialization, deployment targets. In practice, things often fail in subtle ways exactly where those tools meet.

Meta-frameworks improved the situation, but only by straitjacketing the world. Next gives you a coherent stack, but it is a React stack. If you discover Solid and want to try it, you do not simply switch the view layer. You move to a different meta-framework with different conventions, different routing details and different backend assumptions.

The same pattern repeats across the ecosystem: filesystem routes plus one blessed frontend, rebuilt again and again. React gets one meta-framework. Solid gets another. Svelte gets another. Vue gets another. Each one solves mostly the same backend problems, but in slightly different ways you have to relearn every time.

Runtime fragmentation made this worse. Node, Deno, and Bun are all capable JavaScript runtimes, but some frameworks are now effectively runtime-specific. That means your app can be tied not only to a frontend, but also to a runtime. And when a framework says it "supports" Deno or Bun, that often just means the runtime is compatible enough to run Node code. It does not necessarily mean the framework was designed to use each runtime's own APIs and execution model.

There is no reason for this. Frontends are ways to describe browser UI. Backends are ways to handle requests, data, and application logic. Runtimes are execution targets. These things should be allowed to vary independently.

You should be able to write one route with React, another with Svelte, another with Marko, and keep the same backend model.* You should be able to write backend routes in TypeScript, Go, Ruby, or Python, and compose them inside one app. You should be able to run the same app on Node, Deno, or Bun without changing a single line of code or using runtime-specific packages to bridge the gap.

And the framework should still provide official support for validation, database stores, sessions, i18n, routing, and rendering. That is what Primate is built to be.

Primate is a web framework that does not belong to one frontend, one backend language, or one runtime. It gives you one application model, then lets you choose the pieces route by route. Use React where React makes sense. Use Svelte where Svelte feels better. Use TypeScript for most routes, but Go, Ruby, or Python where their ecosystems or runtime characteristics fit better. Run on Node today, Deno tomorrow, Bun later.

Primate is not hype. It is not benchmark theater. The point is not novelty. The point is removing coupling, lock-in, and decisions other people make for you. You can still reach outside the official stack when you need to, but when you stay inside it, the seams are Primate's responsibility.

A web framework should own the stack without owning your choices.

Here is a tiny app that already shows the point: one backend model, multiple frontends, no framework switch.

Create a project:

mkdir primate-demo
cd primate-demo
npm init -y --init-type=module
npm install primate @primate/react @primate/svelte react react-dom svelte

Configure the frontends:

// config/app.ts
import react from "@primate/react";
import svelte from "@primate/svelte";
import config from "primate/config";

export default config({
  modules: [
    react(),
    svelte(),
  ],
});

Create a React route and collocated page:

// routes/index.ts
import response from "primate/response";
import route from "primate/route";

export default route({
  get() {
    return response.page({
      title: "One app",
      links: [
        { href: "/react", label: "React route" },
        { href: "/svelte", label: "Svelte route" },
      ],
    });
  },
});
// routes/index.tsx
import route from "./index";

export default function Index({ title, links }: typeof route.get.Page) {
  return (
    <main>
      <h1>{title}</h1>
      <p>Same app. Same routing model. Different frontends per route.</p>

      <ul>
        {links.map(link => (
          <li>
            <a href={link.href}>{link.label}</a>
          </li>
        ))}
      </ul>
    </main>
  );
}

Add another React route and page:

// routes/react.ts
import response from "primate/response";
import route from "primate/route";

export default route({
  get(request) {
    return response.page({
      from: request.query.try("from"),
    });
  },
});
// routes/react.tsx
import route from "./react";

export default function React({ from }: typeof route.get.Page) {
  return (
    <main>
      {from && <h1>Hello from {from}</h1>}
      <p>This route is rendered with React.</p>
      <a href="/svelte?from=react">Go to the Svelte route</a>
    </main>
  );
}

Add a Svelte route in the same app:

// routes/svelte.ts
import response from "primate/response";
import route from "primate/route";

export default route({
  get(request) {
    return response.page({
      from: request.query.try("from"),
      now: new Date().toISOString(),
    });
  },
});
<!-- routes/svelte.svelte -->
<script lang="ts">
  import route from "./svelte";

  const props: typeof route.get.Page = $props();
</script>

<main>
  {#if props.from}<h1>Hello from {props.from}</h1>{/if}
  <p>This route is rendered with Svelte.</p>
  <p>Server time: <code>{props.now}</code></p>
  <a href="/react?from=svelte">Go to the React route</a>
</main>

Run it:

Or, if you prefer Deno, use deno run -A npm:primate; for Bun, use bunx --bun primate.

This is the part that matters: the backend route model did not change. The app did not become a React app or a Svelte app. It stayed a Primate app, and each route chose the frontend that made sense for it.

* There are limits to this, even in Primate. Different frontend frameworks cannot currently share layouts, because a layout is itself rendered by one frontend. This may become possible later with web components.

联系我们 contact @ memedata.com