tiny ssg

An unopinionated static site generator with a tiny api. Friendly with a watched hot reloaded environment.

6
0
JavaScript

tiny-ssg

Build Status npm

An unopinionated static site generator with a tiny api, that is friendly with a
watched hot reloaded environment.

api

new SSG(opts = { dest: `${process.cwd()}/public` })

Creates a new SSG instance. opts.dest specifies the destination of the
generated html files. opts.dest defaults to the public dir under the current
working directory. It’s probably easier to just use the next api instead.

ssg = require('tiny-ssg/instance')

Returns a new SSG instance with defaults args. Possible to set the destination
by setting ssg.dest. This is more friendly with watch mode and
invalidate-module.

ssg.manifest(cb: () => Promise<Page[]> | Page[]): void

Specifies the pages to be built.

Page has the type:

type Page = {
  url: string,
  view: (meta: any, page: Page) => Promise<string> | string,
  meta?: any,
}

url specifies the destination where you would ultimately access the page when
served on your host. e.g. /, /posts/title-slug/, /posts/feed.xml.

view is a function whose return value will be written to disk at the
appropriate file for the url. The return value may be a Promise that resolves
into a string. It is passed the meta object and the Page object itself.

meta will be passed as the first argument to view. This could be anything that
the view would find useful.

The cb arg to manifest should return a list or a Promise that resolves to a
list of Page objects.

ssg.build(): Promise<void>

Executes the callback given in manifest() and builds the site according to the
returned Page objects.

There is no guaranteed order of executing the view functions or the
order of the writes. Views may be executed in parallel. For this reason, if your
views depend on the same resource, you need to write code that is friendly with
parallel access of resources. See the Recommended Utilities section.

If build() is ran again in the same process, it will rebuild the site with
these differences:

  • if the view of a Page returns the same string, it will not write out
  • if a url is no longer returned since the previous manifest, the page will be
    deleted from the destination directory

trivial example

Here’s a trivial example to demonstrate the API:

const ssg = require('tiny-ssg/instance');

ssg.manifest(() => {
  return [
    { url: '/', view: () => 'Hello World!' },
  ];
});

ssg.build();

This will write to Hello World! to public/index.html.

watch mode example

tiny-ssg is great with watchers like chokidar and tools like
invalidate-module:

// watch.js
const chokidar = require('chokidar');
const invalidate = require('invalidate-module');
const path = require('path');

const watcher = chokidar.watch('*.js').on('all', (event, filename) => {
  invalidate(path.resolve(filename));
  require('./build')();
});
// build.js
const ssg = require('tiny-ssg/instance');

ssg.manifest(() => {
  return [
    { url: '/', view: () => 'Hello World!' },
  ];
});

module.exports = () => ssg.build();

Now you if you execute node watch.js, you may freely update the view function
in build.js and it will rebuild the site.

markdown file example

Using marky-markdown, fs-extra, and async/await:

const fs = require('fs-extra');
const md = require('marky-markdown');
const ssg = require('tiny-ssg/instance');

ssg.manifest(() => {
  return [
    { url: '/', view: markdownView, meta: { file: 'test.md' } },
  ];
});

async function markdownView(meta) {
  const raw = await fs.readFile(meta.file);
  return md(raw);
}

ssg.build();

react example

Use ReactDOMServer.renderToStaticMarkup:

const React = require('react');
const ssg = require('tiny-ssg/instance');
const { renderToStaticMarkup } = require('react-dom/server');

ssg.manifest(() => {
  return [
    { url: '/', view: reactView },
  ];
});

function reactView() {
  const element = (
    <html>
      <head><title>Hello World!</title></head>
      <body><h1>Hello World!</h1></body>
    </html>
  );
  return `<!doctype html>${renderToStaticMarkup(element)}`
}

ssg.build();

It’s up to you on how you compose your React components. Stateless functional
components work well here.

how to make rebuilds fast

In watch mode, since the build process is ran all over again on build(), you
would need to prevent excessive calls to expensive operations for fast rebuilds.
For example, if your view reads from a file, then the read should be cached and
could be invalidated by the file’s mtime.

Views that perform async I/O will be executed concurrently. If multiple views
read from the same resource, you can use something like reuse-promise to
prevent parallel reads of the same resource.