Simplistic static websites with Docking.js
Tags: tutorial, javascript, webdev, jamstack
Published: 2020-06-09 19:51:04


**Static websites**  are recently surging in popularity. Thanks to their very simple nature, they're not only easier to create but also cheaper to host, more secure, and have better SEO out-of-the-box when compared to dynamic or client-side rendered websites. Thus, it's only natural that we see more and more services, frameworks, and libraries dedicated to this sole purpose.

So, to not be left behind, I've recently created my own  **Static Site Generator**  (SSG) called  [Docking](https://github.com/Isotope-js/isotope/tree/master/packages/docking). It's a very basic, but capable SSG based upon Node.js and  [Isotope](https://github.com/Isotope-js/isotope)  - my own UI library.

In this tutorial, I'd like to walk you through the basics of creating a Docking-based static website of your own. Let's begin!

# Setup

Docking is extremely easy to set up. All you need to do is to create a new project (through  `npm init`  or any other package manager) and install several packages:

```bash
npm install @isotope/core @isotope/prototope @isotope/docking
```

By doing this we install not only Docking but also 2 other libraries -  **Isotope**  being the main UI library and  [Prototope](https://github.com/Isotope-js/isotope/tree/master/packages/prototope)  being a utility-based CSS-in-JS library to go along the two others.

Now, all we need to do is to create the correct  **folder structure**. Docking depends on a quite strict and opinionated, but also logical and understandable folder structure to allow you to quickly set up your project.

-   _assets_  - A folder for all kinds of static assets - CSS files, images, and whatever - which are then copied directly to the output.
-   _components_  - A folder containing Isotope components for use throughout your static website
-   _content_  - A folder for all your Markdown content, which is meant to be then processed directly to HTML output.
-   _template.html_  - A basic HTML template file contain basic elements like  `<head>`  and  `<body>`  that you'd like to include in every generated page.
-   _docking.json/.ts/.js_  - Docking configuration in a form of JSON, JavaScript, or TypeScript file.

With all that in place, you can also include the following commands in your  _package.json_  to ease out the process of calling Docking:

```json
{
  // ...
  "scripts": {
    "watch": "dck watch",
    "build": "dck build"
  }
}
```

And that's it - it's really that simple!

# Basic files

Docking requires you to have at least the  `template.html`  in place. However, it's a good practice to create the configuration file as well.

So, in our  `template.html`  file, we'll place a very basic responsive HTML boilerplate:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Docking.js example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body></body>
</html>
```

Docking will then automatically insert generated pages' content into the  `<body>`  tag of the template.

As for the config, you can leave it as an empty  `docking.json`  file:

```json
{}
```

The configuration file in Docking doesn't do much on its own. Here, you can configure the Prototope library with  `prototope`  property (which we'll talk about in a moment) and activate caching with  `cache: true`  to speed up  **incremental builds**. However, this file is meant mostly for you to store your custom config values for later reference throughout your website. We'll explore that a bit later.

# Content

Having set up all the basic files, we can finally start creating some actual content. Let's start by creating a simple  _index.md_  file inside our  _content_  folder.

```markdown
# Hello World

This is an example paragraph.
```

Docking maps Markdown files from the  _content_  folder directly to the output and so the  _index.md_  file becomes  _index.html_  in the end.

In this way, all the  **routing**  is handled in the simplest possible way. You don't need to do anything, but to structure your content in the content folder correctly. All the Markdown links are also  **automatically mapped**  to their HTML counterparts so that you not only have a functional website but also an easy to navigate Markdown database.

If you haven't already, you can now execute the  `npm run watch`  command, and see your  **live development server**  start running (most likely on  [3000](http://localhost:3000/)  port). The website should reload on any change, with only a  [few caveats](https://areknawo.com/docking/#docs%3Esetup).

![Basic Docking.js website](https://areknawo.com/content/images/2020/06/basic-content.png)

# Components

Now, although Markdown is pretty nice when working with content-heavy websites, we still need some way to make our content more  **interactive**  and  **visually pleasing**. For this purpose, Docking uses  **components**.

## Basics

Components in Docking are essentially the same as in  [pure Isotope](https://areknawo.com/isotope/#docs%3Ecomponents)  but placed within own, dedicated files. If you don't know how Isotope components work, you can either check out  [the docs](https://areknawo.com/isotope/#docs%3Ecomponents)  or  [the tutorial](https://areknawo.com/making-a-todo-app-in-isotope/)  I've written earlier, but I'm sure you'll catch up as we go on. That's because these components are essentially simple  **functions**  - there's no fancy syntax or anything else you'd have to learn.

To create a new component, you have to go the  _components_  directory, and there create either a JS/TS file or a directory with  _index_  JS/TS file inside it. Docking handles bundling and TypeScript compilation for you so you can go with either of the languages and even a modular directory if your component grows very big. Here, it's important to use a unique and understandable  **name**  for your file/directory, as it'll be later used to reference the component.

## Boilerplate

I'll go with a simple  `header.ts`  file for my component should be used as my website's header.

```typescript
import { ComponentFunction } from "@isotope/docking/lib/declarations";

const component: ComponentFunction = () => (parent) => {
  return parent.div();
};
const type = "static";

export { component, type };
```

Above you can see my component already set up. Let me walk you through all the basics.

First off, my actual component - a function - is stored within the  `component`  variable/constant. Because I'm using TypeScript, I take advantage of  `ComponentFunction`  type provided by Docking, to get  **intellisense**  and all the other perks.

The component's function is essentially an Isotope component that takes parameters i.e. it's a nested function. Because we don't plan to use any of Docking-provided arguments inside our component just yet, we use an empty arrow function (`() => {}`) to make our component work. Then, like with any Isotope component, we get access to the  **parent node**, which we then use to form our actual component. Currently, we only use a basic  `<div>`  tag.

After the component function, we create the  `type`  variable. This is a cool feature of Docking components. The component's type defines how the component will be used. It's a string and can be equal to one of 3 values:  `"static"`,  `"dynamic"`  or  `"universal"`. And so, if a component is  _"static"_, then the component's code is executed only during the  **page's build-time**, resulting in a "pre-rendered" HTML fragment. However, if the component is  _"dynamic"_, its code is executed only on the  **client-side**, and if it's  _"universal"_  - it's executed on both sides. I hope you get the idea. It essentially allows you to build both dynamic and static features into your website without much effort.

Lastly, we export both the  `component`  and  `type`  variables. The variable names here are important - you cannot use any different names (unless you use  `export ... as ...`). The  `type`  can be optionally omitted, resulting in the default use of  `"static"`. In this case, when you're only declaring the component function, you can use  `export default`  (although it's generally not recommended in JS as a whole). Overall, the above boilerplate is the cleanest solution.

## Prototope

Now, our component's boilerplate is working, but it's not doing much. An empty  `<div>`  tag is most likely not what we envisioned for our header. To fix this, we'll have to apply some styling. In Docking, you can style your elements in 2 different ways - either through standard CSS file which you can load from  `assets`  or through  **Prototope**.

[Prototope](https://areknawo.com/prototope)  is a  **utility-first CSS-in-JS**  library to go along with Isotope and Docking. It features a set of simple CSS utilities (inspired by  [Tailwind CSS](https://tailwindcss.com/)) which you can use right in your JavaScript/TypeScript Isotope application.

To get started with Prototope in Docking, all you need to do is to import the required CSS utilities from the library. You can then use them like any other  [Isotope directives](https://areknawo.com/isotope#docs%3Edirectives), by supplying them during the initiation of a new node (with e.g.  `.div()`) or with the  `.$()`  method.

```typescript
import {
  bgColor,
  fixed,
  h,
  left,
  top,
  w,
  flex,
  justifyCenter,
  itemsCenter,
  textXl,
} from "@isotope/prototope";
// ...
return parent
  .div([
    bgColor("light"),
    fixed,
    flex,
    justifyCenter,
    itemsCenter,
    textXl,
    h(16),
    w("full"),
    left(0),
    top(0),
  ])
  .text("Docking.js example");
// ...
```

Because of the nature of Prototope's CSS utils, the above snippet should be pretty self-explanatory. Utils' names clearly state what they do. And so, above we create a fixed header with flexbox-centered text. For more info about both  [Prototope](https://areknawo.com/prototope)  and  [Isotope](https://areknawo.com/isotope)  APIs, you might want to check out their respective docs.

## Usage

With our component ready, it's finally time to put it to a test. In Docking, components can be used both in all Markdown content files, as well as the  `template.html`  file itself. It's done through the  `{{ component }}{{ component }}`  syntax.

```markdown
{{ Header }}{{ Header }}

# Hello World

This is an example paragraph.
```

Inside the brackets, the component name should be equivalent to the name of the file/directory your component is located in. Keep in mind that it's  **case-insensitive**, so you can use any casing you want inside the brackets, but also can't differentiate components based on single letter casing when working in the  `components`  folder (it's an extremely bad practice anyway - why would you even do this?)

We can get back to preview our website.

![Docking component in use](https://areknawo.com/content/images/2020/06/component.png)

Alright, everything looks pretty nice. Except, that now our fixedly-positioned header covers the "Hello World" text. How do we fix this?

## Using components' arguments

Well, there are many ways in which we could do this, but the first thing that comes to my mind is to wrap our text content within a container and set its  `margin-top`  to equal the height of our header.

So, let's start by creating a new component. I'll call it  _Content_  and thus place it in a  _content.ts_  file in the  _components_  folder.

```typescript
import { mt } from "@isotope/prototope";
import { ComponentFunction } from "@isotope/docking/lib/declarations";

const component: ComponentFunction = (page, content, parse) => (parent) => {
  return parent.div([mt(16), parse(content)]);
};
const type = "static";

export { component, type };
```

Here, we make use of the arguments that Docking gives us. These are:

-   `page`  - a string corresponding to the name or "slug" of the page the component is used in (not required in our case)
-   `content`  - a string corresponding to the  **content**  that's passed to the component.
-   `parse`  - an Isotope directive that can  **parse the provided content**  as Markdown and use it as the component's children.

So, what we essentially do here is creating a container with  `margin-top`  Prototope util applied, that's meant to hold the parsed Markdown content. Keep in mind that both  `content`  and  `parse`  arguments are available only on the server-side to  `"static"`  and partially  `"universal"`  components.

Now, in our Markdown file, we can use our new component like this:

```markdown
{{ Header }}{{ Header }}

{{ Content }}

# Hello World

This is an example paragraph.

{{ Content }}
```

Everything that we pass between the two tags will be available to the component through the  `content`  argument. It's important to understand that you don't have to use only Markdown here. You can pass any kind of  **stringifiable data**  e.g. JSON object, to later use it to configure the component.

Let's check our website again.

![Both components work properly](https://areknawo.com/content/images/2020/06/component-full.png)

Everything works as intended - nice!

# Further configuration

With components, we've basically covered most of Docking's functionality. Because of their versatile nature, components can be used in many different ways. But still, we haven't discovered their true potential.

## Template modification

Let's get back to our  _Header_  component. It's pretty good as it is, but it currently works only for the  _index_  page. That's not the way headers usually work. We don't want to have to supply our component to each new page we create. Thus, we need to move it to our  _template.html_  file.

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Docking.js example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body>
    {{ Header }}{{ Header }}
  </body>
</html>
```

Now, the content of our page will still be appended after the  _Header_  component, but the component itself will be applied to all pages.

Sadly, because both  _template.html_  and the config file are loaded only once, to see the changes, we'll have to  **restart our development server**.

But, before we do that, there's still one more problem we'd like to solve. You can notice that our template has its  `<title>`  tag's value hard-coded. It's not an issue for us right now, but in the future we'll most likely want our pages to have unique titles for better SEO and user experience.

So, how to solve that? Well, of course - with components!

```typescript
import { ComponentFunction } from "@isotope/docking/lib/declarations";

const component: ComponentFunction = (page) => (parent) => {
  return parent.child("title", `Docking.js example - ${page}`);
};
const type = "static";

export { component, type };
```

Above you can see my new  _Title_  component that uses the  `page`  argument provided by Docking to create a dedicated  `<title>`  tag. And, because Isotope doesn't provide shortcut method for  `<title>`  like it does for e.g.  `<div>`, we have to use the  `child()`  method with the proper tag name instead.

Now, we replace the  `<title>`  tag in our  _template.html_  with the  `{{ Title }}{{ Title }}`  component and restart our server.

## More pages

When you view our website you should see that the title is now "Docking.js example - index". That means that our  _Title_  component is working. However, to truly test it, we'll have to have more than one page. And so, let's create a new one in the  _test.md_  file.

```markdown
{{ Content }}

[Go home](index.md)

{{ Content }}
```

You can also put a  _"Go to test"_  link in your  _index.md_  file.

![Our new test page](https://areknawo.com/content/images/2020/06/test-page.png)

Now you can verify that the routing works, that our pages' titles are appropriate and that the  _Header_  component appears in both pages.

## Config file

With all these changes, I hope you now see how quickly you can make a very functional website with Docking. But we still haven't touched on the config file much.

So, let's return to our  _docking.json_  and add a new field - say  `"title"`.

```json
{
  "title": "Docking.js example"
}
```

Previously I've used  _"Docking.js example"_  everywhere. It's not a good practice to have a repetitive values hard-coded all over your code. So, we can fix that by placing it right inside our  **config file**  for later reference. Remember that you have to restart your dev server for the config file to be reloaded.

To show you how you can  **reference**  your config properties anywhere in Docking, i.e.  _template.html_, Markdown files, and components, I'll use our  _Title_  component.

```typescript
import { ComponentFunction } from "@isotope/docking/lib/declarations";

const component: ComponentFunction = (page) => (parent) => {
  return parent.child("title", `config:title - ${page}`);
};
const type = "static";

export { component, type };
```

As you can see, in the place of our previously hard-coded  _"Docking.js example"_, I now placed the  `"config:title"`  string. Docking processes all  `config:`  **prefixes**  and replaces them with the correct config value all over your codebase. What follows the prefix is a path to the specific property - in our case "flat"  `title`, but it can also be nested through the use of dots (`.`) for a complex path (e.g.  `website.author.name`). It's also worth noting that although these can only have a form of a string when used in your components, you can still use  `JSON.parse()`  to get an actual object from the config reference.

Now, check your website if everything works as expected. You can also try replacing all the other hard-coded values with config references on your own.

## Building

When you decide that your website is finally ready, just run  `npm run build`  and have all the files ready for you in the  `dist`  folder.

# Summary

If you got through the whole article -  **congratulations**! You now have a pretty good understanding of how Docking.js works! There are still some more advanced aspects like static assets management, remote resources, and dynamic components that we haven't discussed in this blog post, but I'd say you know more than enough to create your first Docking.js-based website. If you're interested in the topic, definitely check out  [the repo](https://github.com/areknawo/simplistic-static-website-with-docking.js)  for this tutorial as well as the  [official docs](https://areknawo.com/docking)  and let me know in the  **comments**  if you have any questions or whether you want me to cover Docking.js, together with Isotope and Prototope more in-depth in the future articles.

Docking.js, as well as the entire  **Isotope ecosystem**, although pretty stable, is still in  **active development**. If you want to support the project, consider  **leaving a star**  under its  [GitHub repo](https://github.com/Isotope-js/isotope), opening an Issue for a new feature or fix, or maybe even contributing some code of your own. Any form of support is appreciated!

For more up-to-date content about Isotope ecosystem and web development as a whole, consider following me on  [Twitter](https://twitter.com/areknawo),  [Facebook](https://www.facebook.com/areknawoblog), or right here on Dev.to. Thanks for reading this piece and happy coding!