Concepts

You'll learn how different concepts of Gustwind go together on this page. It's self-similar in many ways and if you are familiar with technologies such as HTML, Tailwind, or React, likely you'll get used to it fast.

At its core, the page engine is an interpolating template interpreter that allows data binding. That means there's special syntax for connecting data with your pages and components. The data binding itself happens at routing level and here we focus on the component and layout level.

Components🔗

To give you a simple example of a component, consider the following example for a link that is able to bold itself if it's matching to the path of the current page:

components/SiteLink.html

<a
  &visibleIf="(get props href)"
  &class="(concat underline ' ' (pick (equals (trim (get props href) /) (trim (get context url) /)) font-bold))"
  &href="(validateUrl (get props href))"
  &children="(render (get props children))"
/>

components/SiteLink.server.ts

import type { GlobalUtilities } from "../../types.ts";

const init: GlobalUtilities["init"] = function init({ matchRoute }) {
  async function validateUrl(url: string) {
    if (!url) {
      return;
    }

    // TODO: This would be a good spot to check the url doesn't 404
    // To keep this fast, some kind of local, time-based cache would
    // be good to have to avoid hitting the urls all the time.
    if (url.startsWith("http")) {
      return url;
    }

    const [urlRoot, anchor] = url.split("#");

    if (await matchRoute(urlRoot)) {
      return urlRoot === "/"
        ? url
        : `/${urlRoot}${anchor ? "#" + anchor : "/"}`;
    }

    throw new Error(
      `Failed to find matching url for "${url}"`,
    );
  }

  return { validateUrl };
};

export { init };

A navigation component built on top of SiteLink could look like this:

components/Navigation.html

<SiteLink href="modes">Modes</SiteLink>
<SiteLink href="configuration">Configuration</SiteLink>
<SiteLink href="routing">Routing</SiteLink>
<SiteLink href="concepts">Concepts</SiteLink>
<SiteLink href="templating">Templating</SiteLink>
<SiteLink href="deployment">Deployment</SiteLink>

Utilities🔗

The following example illustrates the usage of utilities:

layouts/blogIndex.html

<BaseLayout>
  <slot name="content">
    <div class="md:mx-auto px-4 md:px-0 w-full lg:max-w-3xl prose lg:prose-xl">
      <ul &foreach="(get context blogPosts)">
        <li class="inline">
          <SiteLink
            &children="(get props data.title)"
            &href="(urlJoin blog (get props data.slug))"
          />
        </li>
      </ul>
    </div>
  </slot>
</BaseLayout>

Layouts🔗

Gustwind layouts are technically components:

layouts/siteIndex.html

<BaseLayout>
  <slot name="content">
    <header class="bg-gradient-to-br from-purple-200 to-emerald-100 py-8">
      <div class="sm:mx-auto px-4 py-4 sm:py-8 max-w-3xl flex">
        <div class="flex flex-col gap-8">
          <Heading level="1" class="text-4xl md:text-8xl">
            <span class="whitespace-nowrap pr-4">🐳💨</span>
            <span>Gustwind</span>
          </Heading>
          <Heading level="2" class="text-xl md:text-4xl font-extralight">
            Deno powered website creator
          </Heading>
        </div>
      </div>
    </header>
    <div
      class="md:mx-auto my-8 px-4 md:px-0 w-full lg:max-w-3xl prose lg:prose-xl"
      &children="(get context readme.content)"
    ></div>
  </slot>
</BaseLayout>

The same idea can be used to implement an RSS feed.

layouts/rssPage.html

<?xml version="1.0" encoding="utf-8" ?>
<feed
  __reference="https://kevincox.ca/2022/05/06/rss-feed-best-practices/"
  xmlns="http://www.w3.org/2005/Atom"
>
  <title &children="(get context meta.siteName)"></title>
  <id &children="(get context meta.url)"></id>
  <link rel="alternate" &href="(get context meta.url)" />
  <link rel="self" &href="(urlJoin (get context meta.url) atom.xml)" />
  <updated &children="(dateToISO (get context meta.built))"></updated>
  <noop &foreach="(get context blogPosts)">
    <entry>
      <title &children="(get props data.title)"></title>
      <link
        rel="alternate"
        type="text/html"
        &href="(urlJoin (get context meta.url) blog (get props data.slug))"
      />
      <id &children="(get props data.slug)"></id>
      <published &children="(dateToISO (get props data.date))"></published>
      <content type="html" &children="(get props data.description)"></content>
    </entry>
  </noop>
</feed>

layouts/rssPage.server.ts

function init() {
  function dateToISO(date: string) {
    return (new Date(date)).toISOString();
  }

  return { dateToISO };
}

export { init };