Generate HTML on the server with Deno and JSX
Tags: javascript, html, webdev, deno
Published: 2021-04-05 11:39:27

When you want to generate html on the server you will need some template engine.
You can use, [EJS], [handlebars] or many others, but I prefer a template engine where I can create components instead of working with partials and layouts.

Luckily Deno has built-in support for JSX thanks to [swc]. JSX is a syntax extension to Javascript. This means that [JSX] will be translated to real javascript calls like React.createElement() (more on this below). This is nice, but Deno doesn't know about React so we need to do some more work.

First we will create a simple Deno application to get started.
Put this code in `main.js`:

```javascript
import { serve } from "https://deno.land/std@0.92.0/http/server.ts";

const server = serve({ port: 8000 });
const headers = new Headers();
headers.append("Content-Type", "text/html; charset=UTF-8");

for await (const req of server) {
  req.respond({
    status: 200,
    headers,
    body: `
      <!DOCTYPE html>
      <html>
        <head>
          <title>Hello</title>
          <link 
            href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" 
            rel="stylesheet">
        </head>
        <body>
          <h1 class="text-3xl m-2">Hello world</h1>
          <button 
            class="border bg-indigo-600 text-white px-2 py-1 rounded m-2">
            Useless button
          </button>
        </body>
      </html>`,
  });
}
```

You can start this with `deno run --allow-net ./main.js`. Now you can open your browser on `localhost:8000` and view the html page.

The goal is to replace this html template string with JSX components.

## React 16

It's time to create our first component `pages/home.jsx`.
For now this is a single component with the complete html.

```jsx
import React from "https://jspm.dev/react@16.14.0";

export default function () {
  return (
    <html>
      <head>
        <title>Hello</title>
        <link
          href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
          rel="stylesheet"
        />
      </head>
      <body>
        <h1 className="text-3xl m-2">Hello world</h1>
        <button
          className="border bg-indigo-600 text-white px-2 py-1 rounded m-2"
        >
          Useless button
        </button>
      </body>
    </html>
  );
}
```

Now it's time to use this component in main.js

```javascript
import { serve } from "https://deno.land/std@0.92.0/http/server.ts";
import ReactDOMServer from 'https://jspm.dev/react-dom@16.14.0/server';
import home from "./pages/home.jsx"

function render(jsx) {
  return ReactDOMServer.renderToString(jsx());
}

const server = serve({ port: 8000 });
const headers = new Headers();
headers.append("Content-Type", "text/html; charset=UTF-8");

for await (const req of server) {
  req.respond({
    status: 200,
    headers: headers,
    body: render(home),
  });
}
```

This includes a new render function that executes the JSX function and renders the result to a string.
Instead of [renderToString](https://reactjs.org/docs/react-dom-server.html#rendertostring) you can also use [renderToStaticMarkup](https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup).

We now have a working JSX example with React 16!

React 17 is at the moment of writing [not yet][react17-deno] supported. The problem is that in React 17 the JSX is translated to something new. It isn't React.createElement anymore to avoid the need for importing React.

I first tried to load React 16 from Skypack CDN, but that doesn't work because of [this issue][skypack-issue]

## Preact

It is also possible to use Preact instead of React.

Since JSX is translated to React.createElement() we have to replace `React` with some other class.
There are 2 ways of doing this.
 - with a JSX pragma
 - with tsconfig.json

```javascript
 /** @jsx h */
import { h } from "https://cdn.skypack.dev/preact";
```

The first line is the JSX pragma. This means to use `h` instead of `React`.
But you can also use `tsconfig.json` so you don't need the pragma everywhere.
You have to run Deno with the link to the config `deno run --config ./tsconfig.json ...`


```json
{
    "compilerOptions": {
        "jsx": "react",
        "jsxFactory": "h",
    }
}
```

The render function in `main.js` looks like this:

```javascript
import renderToString from "https://cdn.skypack.dev/preact-render-to-string@v5.1.12";

function render(jsx) {
  return renderToString(jsx());
}
```

## Result

In the final version I created extra components for the layout and for the button.

pages/home.jsx

```jsx
import React from "https://jspm.dev/react@16.14.0";
import Layout from "../components/layout.jsx";
import Button from "../components/button.jsx";

export default function () {
  return (
    <Layout title="Hello">
      <h1 className="text-3xl m-2">Hello world</h1>
      <Button>
        Useless button
      </Button>
    </Layout>
  );
}
```

components/button.jsx

```jsx
import React from "https://jspm.dev/react@16.14.0";

export default function ({ children }) {
  return (<button
    className="border bg-indigo-600 text-white px-2 py-1 rounded m-2"
  >
    {children}
  </button>);
}
```

components/layout.jsx

```jsx
import React from "https://jspm.dev/react@16.14.0";

export default function ({ children, title }) {
  return (
    <html>
      <head>
        <title>{title}</title>
        <link
          href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
          rel="stylesheet"
        />
      </head>
      <body>
        {children}
      </body>
    </html>
  );
}
```

I hope this will get you started with JSX in Deno. This is just a simple example and there is a lot to improve like using `deps.ts` and Typescript. But I try to keep this example focussed on JSX.

You can find all the code [here][code].

[EJS]:           https://ejs.co
[handlebars]:    https://handlebarsjs.com
[swc]:           https://swc.rs
[JSX]:           https://reactjs.org/docs/introducing-jsx.html
[react17-deno]:  https://github.com/denoland/deno/issues/8440
[skypack-issue]: https://github.com/skypackjs/skypack-cdn/issues/88
[code]:          https://github.com/roelandmoors/roelandmoors.github.io/tree/main/code/20210403