Vinxi Toolkit

repository·main·Indexed 11 days ago

https://github.com/nksaraf/vinxi

A JavaScript toolkit for building full-stack applications and metaframeworks using Vite and Nitro. Vinxi uses a router-centric API to compose server and client environments, supporting router types such as static, http, spa, client, and custom for patterns like SSR and RSC.

Tokens
42.2K
Snippets
157
Records
184
Agent score
79%

What's inside Vinxi

  1. What is Vinxi?

    main

    Vinxi is a JavaScript toolkit designed for building full-stack applications and metaframeworks. It leverages Vite for bundling and development server capabilities, and Nitro as a universal production server.

    Its core primitive is the router, which allows you to define how different URL groups are handled by specifying a configuration object. This makes it ideal for building frameworks with specific opinions on SSR (Server-Side Rendering), SPA (Single Page Application), or RSC (React Server Components).

  2. What is the Manifest API and how does it work?

    main

    The Manifest API provides a data structure that allows different environments (server and client) to coordinate work. A manifest tells the runtime about the current router and other routers, including information produced by the bundler about which files are involved in each router and the assets they use.

    Behavior across environments:

    • Development: The manifest is generated on the fly (Vite-style).
    • Production: The manifest is generated by the bundler, saved to disk, and then read by the runtime from disk.

    The API remains consistent across server/client and development/production environments.

  3. Wrap Vinxi in a custom framework

    main

    Vinxi can be wrapped inside a custom function or framework to provide a pre-configured set of routers, middlewares, and settings. This allows you to create a high-level abstraction that end-users can then use to start their own development or build processes.

    // framework.ts
    import { createApp } from "vinxi";
    
    export function createFrameworkApp() {
      return createApp({
        routers: [
          { name: "public", type: "static", dir: "./public" },
          { name: "api", type: "http", handler: "./server.ts", target: "server" },
        ],
      });
    }
    
    // app.js
    import { createFrameworkApp } from "framework";
    export default createFrameworkApp();
    
    // bin/cli.mjs
    import { createFrameworkApp } from "framework";
    const app = createFrameworkApp();
    if (process.argv.includes("--dev")) {
      await app.dev();
    } else if (process.argv.includes("--build")) {
      await app.build();
    }
  4. Supported router types in Vinxi

    main

    Vinxi provides several built-in router types to handle different web patterns:

    • static: Used for serving uncompiled, static assets (requires a dir and base path).
    • http: Used for creating traditional web servers or API endpoints (requires a handler and base path).
    • spa: Used for building and serving Single Page Application (SPA) assets.
    • client: Used for building and serving Server-Side Rendering (SSR) application assets.
    • custom: Allows you to adapt Vinxi to specific, non-standard use cases.
  5. Use the Custom Router API

    main

    The Custom Router API allows you to define routers with specialized behavior or configurations that fall outside the predefined router types. This is intended for advanced use cases where you need to programmatically resolve router configurations or implement custom routing logic.

    {
      name: "customRouter",
      type: {
        resolveConfig: (router, app) => {
          // Custom configuration logic
        },
      },
      handler: "./app/customHandler.ts",
      target: "server",
    }
  6. Use the SPA Router for Single Page Applications

    main

    The spa router is used to specify a single entrypoint for serving Single Page Applications (SPAs). It allows you to define an HTML entry point and configure how the application is served under a specific base URL, including the ability to inject Vite plugins and define custom routing logic.

    {
      name: "spa",
      type: "spa",
      handler: "./index.html",
      plugins: () => [tsconfigPaths()],
    }
  7. How routers work in Vinxi

    main

    In Vinxi, a router is a specification defining how a group of URLs should be handled. You compose an application by providing an array of router configurations to the createApp function.

    Vinxi supports several router types:

    • static: Serves uncompiled, static assets from a directory.
    • http: Creates traditional web servers (useful for APIs).
    • spa: Builds and serves Single Page Application assets.
    • client: Builds and serves SSR application assets (typically targeting the browser).
    • custom: Allows for custom adaptation to specific use cases.

    Each router can have its own base path, handler (for non-static routers), and specific Vite plugins that apply only to that router.

    import { createApp } from "vinxi";
    
    export default createApp({
      routers: [
        {
          name: "public",
          type: "static",
          dir: "./public",
          base: "/",
        },
        {
          name: "api",
          type: "http",
          handler: "./app/api.ts",
          base: "/api",
          plugins: () => [
            // Vite plugins applying exclusively to `http` router
          ],
        },
      ],
    });
  8. Configure route dependencies using $ and $$ prefixes

    main

    When defining a Route object in a CompiledRouter, you can attach file dependencies using specific prefixes. This controls how the files are bundled and loaded:

    • $ prefix (Dynamic Import): The file is imported dynamically. This is the recommended way to achieve code-splitting, as the file will be bundled into a separate chunk. Use this for route handlers, components, or data loaders.
    • $$ prefix (Static Import): The file is imported statically. It will be included in the main bundle. Use this only for critical information required immediately for the application to function where network latency is unacceptable.

    Each dependency object requires a src field (the file path) and an optional pick array (to specify which named exports to use).

  9. Use the Client Router API

    main

    The client router acts as a wrapper for Vite's build processes and development server. It is designed to facilitate the integration of an http router within server-side rendering (SSR) applications by managing client-side assets and entry points.

    // Example configuration for a client router
    {
      name: "client",
      type: "client",
      handler: "./app/client.tsx",
      base: "/_build",
      plugins: () => [ reactRefresh() ],
    }
  10. Configure a Custom Router

    main

    When defining a custom router, you must provide specific properties that differ from standard routers. Most notably, the type property must be an object containing a resolveConfig function instead of a string literal, and the target must be explicitly set to 'server'.

    {
      name: "customRouter",
      type: {
        resolveConfig: (router, app) => {
          // Custom configuration logic
        },
      },
      handler: "./app/customHandler.ts",
      target: "server",
    }
  11. Serve static files from a directory

    main

    Once a static router is configured (e.g., pointing to ./public), any files placed in that directory will be accessible via the configured base path. For example, if dir is ./public and base is /, an index.html file in the public folder will be served at http://localhost:3000/.

    <!-- public/index.html -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Hello World</title>
      </head>
      <body>
        <h1>Hello World</h1>
      </body>
    </html>
  12. Configure the HTTP Router

    main

    The http router is a wrapper around a Nitro web server. It is used for server-side rendering (SSR), handling WebSockets, and creating custom API endpoints. When defining an http router, you must provide a unique name and a handler file which serves as the entry point for Nitro to handle HTTP requests.

    {
      name: "server",
      type: "http",
      handler: "./app/apiHandler.ts",
      base: "/api",
      worker: true,
      plugins: () => [ reactRefresh() ],
    }