Young Jin Park
site of a student, hobby programmer, and tech geek

tags: blog code

Using Preact without build tools

One of the things I don't like the most about frameworks like React and Preact is that you lose some of the immediacy of web development through using clunky build tools. Before you can start programming, you have to write up a webpack configuration and have to deal with building the code every time. Sure, newer, streamlined CLI tools make it easier to deal with it, but it's still a compromise to one of native web development's greatest educational features.

While most of the JS community is used to using Webpack, it's not completely necessary. In fact, I would argue that it's a much better experience for new developers to write code for the browser directly. This article details exactly how to get Preact running without any build tools.

Getting Preact on the Browser

Modules?

ES6 presents a new way to define and import modules, which should allow proper dependency resolution in the near future. At least with webpack, the following code would work.

import { Component, h, render } from 'preact'

Unfortunately, importing from 'preact' doesn't work directly on the browser, since it doesn't know what the symbol preact points to. As of May 2020, only direct links to the module resources are accepted. Module resources are generally denoted by the suffix .mjs, although .js is sometimes still used.

import { Component, h, render } from 'https://unpkg.com/preact/dist/preact.mjs'

Even this has it's limits. Some .mjs files are written with import statements such as import defaultExport from 'module'. For example, importing from https://unpkg.com/preact/debug/dist/debug.mjs will not work on the browser, as it depends on preact and preact-devtools. By the way, I would strongly recommend having preact-debug if you're going to be developing with Preact.

Universal Module Definition

The best option, for now, are UMD files, which are compiled in such a way that it is compatible with both the backend and frontend. “Importing” a UMD module just consists of adding it to the html inside of a script tag, the good old way.

<script src="https://unpkg.com/preact/dist/preact.umd.js"></script>
<script src="https://unpkg.com/preact/devtools/dist/devtools.umd.js"></script>
<script src="https://unpkg.com/preact/debug/dist/debug.umd.js"></script>

One thing to pay attention to UMD files on the browser does not automatically resolve dependencies. You have to add them yourself and you have to do them in the right order.

Then, you replace the import lines with code like the following. Noticably, instead of the import symbol, we use window.[module name in camelcase].

var { Component, h, render } = window.preact
var { Router, route } = window.preactRouter

The Future

A pending WICG spec, if passed, will introduce a way to map symbols like 'preact' to a url, meaning that modules will finally begin to work properly on the browser. The proposal is extremely straightforward; read more here.

htm

htm is essentially native jsx. If you've written JSX, you can write using htm. There's only a few essential differences, and it's just as powerful with minimal performance penalty.

const html = window.htm.bind(h)

const App = function () {
	return html`<p> Hello </p>`
}

render(html`<${App} />`, document.body)

Wherever there would be JSX, you have a string literal with html. Wherever there used to be {}, you have ${}. There's even more syntactical features available to htm that make your life easier. Read more here.

Creating a custom theme in Hugo

I've spent some time creating this site's theme on Hugo, which was the first time I've used it. As it turns out, making a theme on Hugo is very easy. The documentation is pretty managable, but if you want a tutorial that takes you from the very beginning to the end, this is it.

Get Hugo

There are many ways to get Hugo. Check out their website and get it onto your computer.

Generate the Hugo site

Creating a Hugo site is easy, just run the following command after installing Hugo:

hugo new site [name]

After that, navigate into the base of the new Hugo folder. Then run

hugo new theme [theme_name]

This will create the theme under [name]/themes/[theme_name]. Technically, that is a theme, but clearly we would like to add more. To now run the server which updates live as we add changes, you just have to run

hugo server

to have an Hugo instance running at http://localhost:1313.

Hugo file structure

Before we start creating our theme, the most important thing is to understand the file structure of Hugo. In our generated site folder, we should have something that looks like the following:

- [name]
  - archetypes
  - content
  - data
  - layouts
  - public
  - resources
  - static
  - themes
    - [theme-name]
  - config.toml

content

Contains written information, such as a blog post written in markdown. As the name of the folder implies, this folder is concerned with the central content of your website.

archetypes

Stores templates that can be used to generate new files for content. For example, you could have a default.md file in the archetypes folder then generate a new document with a given name inside the content folder.

data

Stores extra data that can be used to supplement standalone pages, which then can be accessed within templates.

layouts

Provides extra layouts that are not provided with the theme, which is important when you're trying to make a custom page such as a portfolio page. An example using this feature will be given later on.

public

Hugo generates files into this folder, so you should basically never manually touch a file inside here.

static

Static contains files such as css, javascript, and certain images. The images that are put into this folder should be used within the website in general, not within a post.

config.toml

Familiarize yourself with Toml and look at the various options that are given within the official Hugo documentation here.

Add an example post

Personally, before moving too far ahead, I like to create an example post so that I could see the site coming together as I work. For this, we have to first move out of our theme, back into the root directory. We can create an example document through the following command:

hugo new content/posts/example.md

You can name this file whatever you want, it won't effect the result on the site. In our case, we'll call it example.md. The Hugo command will then automatically generate the following header, in which it generates a file based on the archetypes/default.md file. You should see something like the following:

---
title: "Example"
date: 2019-04-01T22:12:51-06:00
draft: true
---
  • title: this should be relative self-explanatory, it is the title that is displayed on the site.
  • date: this is the published date that can be seen on the post. By default, the posts are sorted by this date. If the date is in the future, the post won't be displayed.
  • draft: if the draft option is on, then the post won't get compiled and show on the site.

You can now add any markdown items below. I recommend copying in some kind of long-form text, since that more accurately shows what your site looks like.

---
title: "Example"
date: 2019-04-01T22:12:51-06:00
draft: true
---

# Markdown is fully supported

## So are a variety of other flavors

If you prefer certain flavors, you should check it
out on the official documentation.

Customize templates

There are only a few files that you need to create within your theme. First, create a folder structure like the following.

- [theme-name]
  - _default (dir)
    - baseof.html
    - list.html
    - single.html
  - partials (dir)
    - head.html
    - header.html
    - footer.html
  - index.html

First, let us edit the baseof.html file. Every layout, unless otherwise specified, will be “inserting” itself into the baseof.html template. A simple file would look like this.

<!DOCTYPE html>
<html lang="en">
    {{- partial "head.html" . -}}
    <body>
        {{- partial "header.html" . -}}
        {{- block "main" . -}}{{- end -}}
        {{- partial "footer.html" . -}}
    </body>
</html>

Edit the partials however you like them to look like in your theme. You can add more partials if you so like. In my case, I added a side partial. For the most part, the baseof.html file should be pretty simple and the complexity should be offloaded into partial files.

Here is an example head.html.

<head>
    <title> {{.Title}} </title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content="{{ .Description }}">
    <base href="{{ .Site.BaseURL }}">
    <link type="text/css" rel="stylesheet" href="{{ 'css/main.css' | absURL }}">
    <link type="text/css" rel="stylesheet" href="{{ 'css/syntax.css' | absURL }}">
    <link rel="canonical" href="{{ .Permalink }}">
</head>

Craft your list.html and single.html by using the variables described in the Hugo docs.

Add custom variables

There are a few ways to add custom variables. You can add site-wide custom variables through Params, where you put custom variables within your main config.toml under the params section. These then can be accessed within templates under the .Site.Params variable.

Another way to access variables are within the data folder, but an even cool way to use variables within templates is to add it into your content. You can add variables within your content front matter which you can then access within your templates using .Params. Note, this is different from .Site.Params.

Add custom pages

You can add more sections using the theme default pages by placing them within folders within the content folder.

However, if you want to have a new kind of layout, you can put new layout templates within the layouts folder within your main folder. This will essentially add on top of your theme. On your content front matter, you can declare which layout you're going to use or it will automatically find the necessary layout template.

For example, if you wanted to have a Portfolio within your website, you would have something that looks like the following

- [name]
  - content
    - portfolio
      - _index.md
  - layouts
    - portfolio
      - index.tmpl
> in portfolio.tmpl
+++
layout = 'index'
+++