JSX Framework

A project to make easy to create a server side rendering (SSR) with only one command and a few files.

Features

  • Built on Deno 1.18.1 and above!
  • Folder based routes (a là Next.js)
  • API Routes

Simple installation

To create the folders and files automatically, you can run the command on the folder that you want the project to be:

deno run --allow-read --allow-net --allow-write --unstable https://raw.githubusercontent.com/imaginamundo/jsx-framework/v0.0.4/mod.js

After running, this command you create a Makefile for you, you can execute it by typping:

deno task start

Manual Installation

Create the folders to the directory:

project
 ┣ pages
 ┃ ┗ index.jsx
 ┗ import_map.json

The import_map.json will be where we will put our dependencies:

{
  "imports": {
    "jsx": "https://deno.land/x/jsx@v0.1.5/mod.ts"
  }
}

The index.jsx file will be an functional JSX component:

import { Fragment, h } from "jsx";

export default function () {
  return <p>Hello world!</p>;
}

After we create our folders and files, we just need to run the following command:

deno run --allow-read --allow-net --unstable --import-map=import_map.json https://raw.githubusercontent.com/imaginamundo/jsx-framework/v0.0.3/mod.js

Whe are running the main file of this repository to walk the folders and see which page to render. It will render on http://localhost:8080.

Documentation

Routing

Basics

We use the folder page to create ou routing system.

If you create an file index.jsx on the folder pages, it will create a route /.

You can put inside a folder to create the route, if you create the file world.jsx inside the folder pages/hello, it will give the route /hello/world.

URL Parameters

If you create a file inside pages starting with the character :, you will receive the name of the file as an variable.

The system uses URL Pattern to generate his routes, so anything that you would use on URL Pattern will work on the filename.

Creating the file :pokemon.jsx inside the folder pages, you will receive the pokemon variable inside.

You can get the variable with the parameter from the exported page:

import { Fragment, h } from "jsx";

export default function ({ url }) {
  const { pokemon } = url.pathname.groups;

  return <p>Pokemon name: {pokemon}</p>;
}

So if you enter on the page http://localhost:8080/bulbasaur, it will return a paragraph with the text Pokemon name: bulbasaur.

The url parameter on the page function will return the result from URL Pattern exec, that is:

{
  inputs: [ "http://localhost:8080/bulbasaur" ],
  protocol: { input: "http", groups: { "0": "http" } },
  username: { input: "", groups: { "0": "" } },
  password: { input: "", groups: { "0": "" } },
  hostname: { input: "localhost", groups: { "0": "localhost" } },
  port: { input: "8080", groups: { "0": "8080" } },
  pathname: { input: "/bulbasaur", groups: { pokemon: "bulbasaur" } },
  search: { input: "", groups: { "0": "" } },
  hash: { input: "", groups: { "0": "" } }
}

Static files

You can create static files by creating a folder called public at the root of your project. Every file on this folder will be a route for your server. If you create an file readme.txt inside that folder public, you can access this on your server via /readme.txt.

You can also create folders and the routing system will respect your route to serve your files. The file public/test/naruto.jpg can be seen at the route /test/naruto.jpg.

Custom html inside head tag

To add a custom tag on head html, you can export a function called head inside your page.

import { Fragment, h } from "jsx";

export default function () {
  return <p>Hello world!</p>;
}

export function head() {
  return <title>🌎</title>;
}

Request Object

Despite the url parameter that you have access inside the page function, you will also have access to the request object.

To see what the request object can give you, here is an example:

import { Fragment, h } from "jsx";

export default function ({ request, url }) {
  const { pokemon } = url.pathname.groups;

  console.log(request);

  return <p>Pokemon name: {pokemon}</p>;
}

On the console it will print (or something similar, depending from where you are doing the request):

Request {
  bodyUsed: false,
  headers: Headers {
  accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  "accept-encoding": "gzip, deflate",
  "accept-language": "en-CA,en-US;q=0.9,en;q=0.8",
  connection: "keep-alive",
  host: "localhost:8080",
  "upgrade-insecure-requests": "1",
  "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15...."
},
  method: "GET",
  redirect: "follow",
  url: "http://localhost:8080/bulbasaur"
}

IMPORTANT: The request object is the same as document on MDN Request, you can request the same information as documented there.

Fetching data on the server

You can fetch data on the simples way, just use fetch to get your resources an await it before responding the JSX:

import { Fragment, h } from "jsx";

export default async function () {
  const pokemon = await fetch("https://pokeapi.co/api/v2/pokemon/1")
    .then((res) => res.json());

  return <p>{pokemon.name}!</p>;
}

We have to use an async function and await the promise respond. This will retrieve the data from PokeApi, and use it inside the JSX.

Static pages

Static pages its a mode that will generate a HTML of that page. If you page make an request, it will only get that request at the first time the page loads, and after that will stream the html saved.

To create an static page you need to export a variable const staticPage = true; on the page you want that to happen.

import { Fragment, h } from "jsx";

export default function () {
  return <p>Hello Static!</p>;
}

export const staticPage = true;

This will generate an HTML of that page.

Creating an API (or responding something else than HTML)

We can respond anything else that we want. To do this, intead of returning JSX on the page function, we just return a new Request.

You can see how it works:

export default function () {
  const headers = new Headers();
  headers.append("Content-Type", "application/json");

  const body = { hello: "world!" };
  const init = {
    status: 200,
    headers,
  };

  return new Response(JSON.stringify(body), init);
}

As you see in the code above, it will return a JSON with the content { hello: 'world!' }.

You can see the documentation to the Response object on MDN.

Custom document

A custom document is the HTML where the pages will be located inside, by default we use a document like this:

import { Fragment, h } from "jsx";

export default function Document({ children, head = null }) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        {head}
        {script}
      </head>
      <body>
        {children}
      </body>
    </html>
  );
}

Variables:

child: It will render each route create inside the pages folder on this variable.

head: It will render the function head inside pages that are used to populate the head tag.

If you create a file _document.jsx inside the folder pages you can overwrite the default document that we use to render the pages.

Future

  • Tests, please 🥺;
  • Custom error page;
  • Explicit routes;

Acknowledgements

Authors