1
0
Fork 0
mackenzii/README.md
2024-11-04 19:25:14 -05:00

16 KiB

Project Mackenzii

This repo holds the various projects associated with @goldenwere/mackenzii, a template-based solution for inflating different types of content without the need for a server or database.

Local Development

These instructions will try to be as thorough as possible and/or link to necessary references as much as possible in order for those who may not be used to this sort of development environment.

Git

TBA

Node / NPM

Make sure you have node installed on your machine. The best way to manage node is to use nvm (windows version). This project uses .nvmrc to inform nvm of the targeted (ideal) version to use. Running nvm install {version-in-nvmrc} will install the appropriate version, and running nvm use in every terminal session will keep node in sync with .nvmrc (if the version is not installed, nvm will tell you what version to install). Once node is installed, run npm ci in a terminal to install the dependencies used by the project.

Site Content

You will need to create a site folder in /projects/sites/<site-name> (ignored by git), create a config.json file to define how the site generates pages, and have some sort of content in order for the site to generate. Before previewing the site, run npm run set-current -w=sites --site=<site-name> so that the site is linked to the frontend.

config.json

Without this file properly set up, your site will not function. There are a few key fields to define here. Consider the following example:

{
  "routes": {
    "/": {
      "template": "markdown",
      "content": "/content/home.md",
      "stylesheetUrls": [
        "/content/home.css"
      ],
      "id": "home",
      "fullTitle": "Home | My Site",
      "title": "Home"
    },
    "/about": {
      "template": "markdown",
      "content": "/content/about.md",
      "stylesheetUrls": [
        "/content/about.css"
      ],
      "id": "about",
      "fullTitle": "About | My Site",
      "title": "About"
    },
    "/gallery": {
      "template": "gallery-list",
      "config": "/content/gallery.json",
      "stylesheetUrls": [
        "/content/gallery.css"
      ],
      "id": "gallery",
      "title": "Gallery",
      "fullTitle": "Gallery | My Site",
      "view": {
        "title": "$ENTRY",
        "fullTitle": "$ENTRY | Gallery | My Site",
        "stylesheetUrls": [
          "/content/gallery.css"
        ]
      },
    }
  },
  "header": [
    {
      "path": "/",
      "displayName": "Home"
    },
    {
      "displayName": "Galleries",
      "children": [
        {
          "displayName": "Anthro",
          "path": "/anthro"
        }
      ]
    },
    {
      "path": "/about",
      "displayName": "About"
    },
    {
      "path": "https://example.org",
      "displayName": "Home Site",
      "target": "_blank"
    }
  ],
  "id": "my.example.org",
  "stylesheetUrls": [
    "/content/base.css"
  ],
  "tags": "/content/tags.json",
  "themes": {
    "dark": {
      "displayName": "Dark",
      "type": "dark",
      "url": "/content/dark.css"
    },
    "light": {
      "displayName": "Light",
      "type": "light",
      "url": "/content/light.css"
    }
  },
}
Routes

Routes is the most important field here. It defines what HTML pages get generated by the static site generator. It is a key-value object where the key represents the intended URL of the page; this must always start with "/" (a route keyed by just "/" will represent the home page, e.g.example.org/index.html otherwise shortened to example.org).

The template field determines which view template is used when generating the page. See templateType.d.ts for the full list of currently supported template types. The full config for routes is defined in routing.d.ts; at minimum, routes follow the SharedRouteDefinition. General templates will just generate a main page, while list-related templates like article-list and gallery-list have a list page (the main config) and a child "view" page (the view field in the main config) (e.g. if you have a gallery-list at /gallery, the main config will be for /gallery and the view config will be used for /gallery/view?id={entry}).

Depending on the template type, there may be config and/or content fields. Both of these are remote files that are fetched when the page first starts to load. content is used typically by generic templates that directly load some sort of content; markdown templates, for example, directly pull text from a markdown file, so when "template": "markdown", "content": "/content/path/to/markdown.md". config is used by more complex templates. List templates, for example, define a list of entries. In order to see what config you need, look for the corresponding template type name in the types folder for templates. Parsing these can be complex, so separate guides will be available for creating those templates.

The id of the page is used for identifying the page in code for caching purposes. title is a display-name for the route used in breadcrumb navigation/etc. fullTitle is the display-name for the page in the browser tab. stylesheetUrls is an array of strings linking to stylesheets that will be appended to the page. These are appended when navigating to the page and removed when navigating away from it.

scriptUrl allows loading an optional script file containing callbacks that are executed based on page events; these must be defined at the end of your script file in order to be used like so:

export const callbacks = {
  onPageClosed,
  onPageLoaded,
}

All callbacks are optional. onPageClosed is called when the user is leaving the page they are currently on, which is useful if you have made some sort of modification to the page that may unintentionally be transferred to the next. onPageLoaded is called when the current page route loads; each view template emits a loaded event when it is finished loading its config and executing any tasks related to loading the template. Note that this doesn't necessarily mean asyncronous data will be loaded into view when this executes, simply that the main view is marked ready and will have the main template structure in place.

Other Fields

id is used for identifying the site in browser cookies. This is important to allow for remembering certain things like if the user wanted to permanently hide gallery warnings or if the user picked a certain site theme.

header is an array of objects which define entries for site navigation. If you want to directly link a page, set the path of the page to the relative url of the page. You can optionally set target to set the <a> tag's target (e.g. opening in a new tab instead of same tab with _blank). If you want to add a drop-down of links, set children instead of path; children is recursive, in that it is an array of the same shape of objects found directly in header, which can either be links or a nested drop-down. Whether you're adding a drop-down of links or a direct link, remember to set displayName to set the link's text.

stylesheetUrls is the same as the stylesheetUrls found in the routes described above, with the exception that the root config.json stylesheetUrls are loaded/shared for every single page. themes is a special collection of stylesheets - it is a key-value object where the key is the id of the theme. displayName is what is displayed in the theme picker dropdown to the user; type describes what type of theme it is so that the frontend can default to a theme respecting the user's browser/system theme (see themes.d.ts for supported types); url is of course the url to the CSS file itself.

tags is used for definining filters based on tags assigned to entries in list-related templates. tags can either be a link to a JSON file or directly be embedded. In either case, it is an array of objects with the following fields: displayName (what the user sees on the list page's filters panel); category (optional, used for dividing related tags in the filters panel; uncategorized tags will appear at the top of the filters panel, and categorized tags will appear in subheadings); and finally tagId (identifies the tag used by list entries).

Other Site Content

You are free to structure your site's content however you want, so long as config.json is directly in the root of the main site folder, and so long as it is understood that all files in this folder are assumed to be located at /content/** on the web-host. For example, if you have a site in the /projects/sites/sites folder named my.example.org, this my.example.org becomes https://my.example.org/content when the site is built and pushed to the host, assuming you are using the default setup used by the development/build scripts described in the ReadMe. /projects/sites/sites/my.example.org/stylesheets becomes https://my.example.org/content/stylesheets, /projects/sites/sites/my.example.org/galleries/my-gallery.json becomes https://my.example.org/content/galleries/my-gallery.json, and so on.

Live Preview

You can run npm start in the terminal to spin up a server. Open the address shown the terminal in your browser to preview what your site looks like. It has hot reloading, which means you can make changes to site code and certain files that will be instantly reflected on the page you have open. Some more core files may cause the page to fully reload, while some simple changes like stylesheet/component changes may be reflected without full reloading. Note that fetched content (page markdown/config files) is not caught by hot reload, so you will need to reload manually in those cases.

Building

Running npm run build will build out the project. Be sure that you had previously set npm run set-current -w=sites --site=<site-name> before building so that the site builder can build from config.json. When the build command is executed, vite-ssg will build the frontend into the /projects/frontend/dist folder. The build process will ensure that the previously set site is copied into the generated dist directory. The dist directory contains your static site's files.

Background

As an artist, I wanted a way to share my art online without restrictions regarding content or niche. I also wanted a way to show a list of my games all in one spot for my developer alias. On top of that, I needed a way to showcase a portfolio of all my development projects for work/job searching. And finally, I wanted a landing website that simply linked to all other profiles I have and provide a sort of bio. These are several different websites, but all were similar in function: they were websites which all statically displayed dynamic content (static meaning that there is no server-side processing or reaction to user input). With the individual sites needs, the idea of creating templates for these goals came to be. To determine how the sites should be set up, I broke down the needs these sites shared:

  1. it needed to be hosted in a cost-effective way
  2. it needed to be easy to manage the content without rebuilding the full site
  3. it needed to keep the content separate from source code in order to: 3a. make the templates re-usable under different aliases (developer versus personal) 3b. avoid any personal information being permanently committed in version control that could change or need to be removed

With those needs in mind, after several iterations, this project's main architecture came to be. The result is a website generator that takes a configuration file, builds static routes from it, and the resulting static pages fetch the content and inflate it into the templates. It is essentially a static site generator with client-side rendering for the content, where the content is database-less flatfiles associated with the routes that were generated. Other static site generators require that your content be part of the build process, which could mean committed all content as part of source code and requires rebuilding the full site just to make a small change in text on a page. This retains the flexibility of dynamically-changing content while still providing a static site that can be hosted without any strange routing problems that you may get with vanilla frameworks that are solely client-side rendered single-page apps (e.g. the "#" present in URLs that's bad for SEO, or the need to redirect to index.html if said "#" is deactivated). The router will still perform as if it were a normal client-sided single-page app, retaining shared content such as site navigation and global styles between pages (instead of flashing white). But direct navigation to a given URL will work as well without any need to redirect all requests to HTML or any need to use "#" routing.

In less technical terms: the site feels like a modern website but works more classically and without any need for a server, database, or other costly solution for hosting. This means you can just build the files and dump them on a free/cheap static host, and you can update content (add new images to a gallery, fix a type on a single About Me page, etc.) without rebuilding the whole site.

This is a very niche project which probably won't be useful to many, but for those who want the benefits of modern web frameworks without the downsides, for those who want the main site to just be built once in a blue moon and can just change some content whenever they need to, and for those who don't want to pay a lot or be limited by third party hosts/builders/services for a simple website, this project is made for that.

Architecture

This repo is set up to be a monorepo to help manage different projects under the mackenzii umbrella. The repo is divided into the following:

  • libs: libraries which are package-like and meant to be consumed in a project
    • types: shared types between the remaining libs/projects
  • projects: end-products which are app-like meant to be built and deployed
    • frontend: the actual static website generator
    • sites: a placeholder for testing and/or managing multiple websites with the same static website generator

/projects/frontend

The frontend is the main project which builds your website from your content config. The frontend is designed to work with dynamic content, i.e. it will not bake your content directly into the resulting HTML so that you can manage content separately. The types of pages that are generated are defined by page templates and are implemented by views (/projects/frontend/src/views).

/projects/sites

This package helps manage multiple websites in one place. The ./sites folder is hidden by default for the sake of the main mackenzii repo and is meant for maintaining dynamic content outside of source control. It is recommended to sync these sites directly with /content on whatever webhost is being used to host the site, such that:

  1. deployment of the /projects/frontend project should ignore these sites' /content by excluding /content from the final build and ensuring the deployment API can leave /content unmodified remotely
  2. syncing of the sites should ignore all other files besides /content by syncing directly with the /content folder

Note that some hosts, such as surge.sh, do not allow for management of individual files and instead require that all files be pushed in one go. In that case, make sure the /content folder is present when the /projects/frontend project is built before syncing, and sync content changes with the /projects/frontend build instead.

/projects/cms

In order to manage larger galleries, Mackenzii has a pre-configured Strapi package that can be used locally in combination with the scripts under the sites project which will pull data from Strapi into a format usable by Mackenzii. Note that this configured Strapi is not meant to be hosted on a production server. If you do, make sure you do not commit any environment secrets to git (they should be gitignored) and read the docs on how to host Strapi. You will also want to point the bridge scripts to the hosted version instead of local, or consider repurposing them into a middleware server and have your Mackenzii point to the appropriate URLs instead of /content/**.