2627

philosophy

Ultra takes a non-prescriptive approach to web-app development. You can configure it to use most existing libraries that you are accustomed to — or you can write your own...

We want Ultra to do a few things, and do them well.


Give us ESM or give us death


Breakdown of a basic Ultra project

To follow along at home, run this command to quickly scaffold out a basic Ultra project.

deno run -A -r https://deno.land/x/ultra/create.ts

importMap.json

{
  "imports": {
    "react": "https://esm.sh/react@18.2.0",
    "react/": "https://esm.sh/react@18.2.0/",
    "react-dom": "https://esm.sh/react-dom@18.2.0",
    "react-dom/": "https://esm.sh/react-dom@18.2.0/",
    "ultra/": "https://deno.land/x/ultra@v2.0.0-alpha.6/"
  }
}

Atm, these are the only deps required to run an Ultra project. Simple, I like it.

server.tsx

import { serve } from "https://deno.land/std@0.153.0/http/server.ts";
import { createServer } from "ultra/server.ts";
import App from "./src/app.tsx";

const server = await createServer({
  importMapPath: import.meta.resolve("./importMap.json"),
  browserEntrypoint: import.meta.resolve("./client.tsx"),
});

server.get("*", async (context) => {
  /**
   * Render the request
   */
  const result = await server.render(<App />);

  return context.body(result, 200, {
    "content-type": "text/html",
  });
});

serve(server.fetch);

This file controls how your app will render on the server. It's using Deno's std http server, you can probably use another one if you want.

createServer kickstarts the Ultra renderer and static asset pipeline, it only needs your import map and client entry point.

You can also look at creating API routes here by following this example.

client.tsx

import { hydrateRoot } from "react-dom/client";
import App from "./src/app.tsx";

hydrateRoot(document, <App />);  

This should look familiar to most... This is your client entrypoint, and what is used for client rendering. It can be customised if needed.

src/

Put your source code here.

public/

Static files go here. When building for production, these files will be versioned.

deno.json

{
  "tasks": {
    "dev": "deno run -A --no-check --watch ./server.tsx",
    "build": "deno run -A ./build.ts",
    "start": "ULTRA_MODE=production deno run -A --no-remote ./server.js"
  },
  "compilerOptions": {
    "jsx": "react-jsxdev",
    "jsxImportSource": "react"
  },
  "importMap": "./importMap.json"
}

We use Deno's native task runner/config file.

Try running deno task dev: This will spin up a development server and watch for file changes.

We use the react-jsx and react compiler options so you don't need to import React from 'react' everywhere.