An unopinionated static site generator with a tiny api, that is friendly with a watched hot reloaded environment.
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.
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.
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.
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
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
.
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.
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();
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.
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.