1.0.6 • Published 3 years ago

ssgo v1.0.6

Weekly downloads
12
License
ISC
Repository
-
Last release
3 years ago

ssgo

A minimalist, unconfigurable static site generator.

⚠️ This is a projet I maintain on my spare time and that I haven't fully tested yet. It most surely has unknown issues. It has to be used with care.


Installation

yarn add ssgo -D

Usage

At the root of your project, to generate a bundle:

npx ssgo

The site will be built and outputed inside of the dist directory.

The following options are accepted:

optiondescription
--watchwatch files and rebuild on change
--serveserve the bundle on localhost:3003

How does it work ?

ssgo assumes that your projet directory has the following structure:

/
├── pages/
├── components/
└── static/

The pages folder:

This is the only mandatory folder. ssgo needs it to work.

pages is the directory where lies the scrips that will generate your pages. For example, the content of pages for a static blog project could be:

└── pages/
    ├── templates
    |   ├── index.html
    |   ├── contact.html
    |   └── post.html
    ├── index.ts
    ├── contact.js
    └── blog-posts/
        └── post.js

ssgo will recursively search for .js files inside of pages directory, and execute the function it exports, giving it a single argument, the buildPage function.

Note: Nothing forces you to have multiple files inside of pages directory. One can have a single file, with as much data fetching as wanted and as much calls to buildPage as wanted.

The post.js (ts is also supported) file would look something like that:

const { fetchPostsFromApi } = require(api.ts);

module.exports = async (buildPage) => {
  // path must be relative to the root of the project directory
  const template = "/templates/post.html";

  // here, fetch all the datas you need to build your pages
  // from an API, the filesystem or even a database !
  const blogPosts = await fetchPostsFromApi();

  // each call to buildPage will parse the template file,
  // do the interpolation and evaluation work,
  // and create the built html file inside of dist directory
  for (let post of posts) {
    const postContext = {
      utils: {
        isMinPlural: (time) => time > 1,
        capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
      },
      post: post,
    };

    buildPage(template, postContext, {
      filename: post.slug,
      directory: "posts",
    });
  }
};

While the template it uses, post.html, would look like that:

<!-- here, let's assume the `post` object had a title, a description, a readTime, and a content. -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- see section `The attributes` to understand about eval: and static: -->
    <meta name="description" eval:content="description" />
    <link rel="stylesheet" static:href="../../static/stylesheets/style.scss" />

    <title>{post.title}</title>
  </head>
  <body>
    <!-- custom component given a `rt` prop: see `The components directory` section -->
    <my-header rt="post.readTime"></my-header>

    <h1>{{utils.capitalize(post.title)}}</h1>
    <small>Read time: {post.readTime} minute{utils.isMinPlural && 's'}</small>

    <h2>{post.description}</h2>

    <p
      eval:class="{ 'text-sm': post.content.lenght > 500, 'font-bold': post.content.lenght > 300 }"
    >
      {post.content}
    </p>
  </body>
</html>

ssgo supports TypeScript out of the box for pages/ scripts.

Templates paths given to the buildPage function will be assumed as relative to the root of your project directory.

The components directory:

components is the directory where lies your custom components. Following the same 'blog' example from before, we could have the following components directory:

└── components/
    ├── my-header.html
    ├── my-footer.html
    └── post-preview.html

ssgo will search for .html files at the root of this directory (not recursively yet), and make components usable inside of pages templates.

The post-preview.html file could look something like that:

<div eval:id="'post-preview__' + index ">
  <b class="post-preview__title">{post.title}</b>
  <i>{post.readTime} minute{plural && 's'} read</i>
</div>

And we could use it, for example in the index.html page template as following:

<!-- [...] -->

<div id="previews">
  <!-- see section `The attributes` to understand about 'for' and 'of' attributes -->
  <post-preview
    post="post"
    plural="post.readTime > 1"
    for="post"
    of="blogPosts"
  ></post-preview>
</div>

<!-- [...] -->

The static directory:

static is the directory where lies all the static ressources your site might need, like scripts, stylesheets, images... Following the same 'blog' example from before, we could have the following static directory:

└── static/
    ├── scripts/
    |   ├── index.ts
    |   ├── vendor.js
    |   └── why-not.coffee
    ├── stylesheets/
    |   └── style.scss
    └── media/
        └── images/
            └── logo.png

All this static ressources could be used inside of any page template as following:

<!-- [...] -->

<head>
  <meta charset="UTF-8" />

  <link rel="stylesheet" static:href="../../static/stylesheets/style.scss" />

  <script static:src="../../scripts/index.ts"></script>
  <script static:src="../../scripts/vendor.js"></script>
  <script static:src="../../scripts/why-not.coffee"></script>
</head>

<!-- [...] -->

<img static:src="../../media/images/logo.png" />

<!-- [...] -->

The static: attribute prefix will make these files to be resolved, minified and added to the bundle inside of the dist/static directory, thanks to the awesome Parcel bundler.

The attributes:

ssgo makes available the following attributes:

if

Evaluates the expression it is given and conditionally lets the node inside of the template. Example:

<!-- The template... -->
<p if="true">eeny</p>
<p if="false">meeny</p>
<p if="Math.random() > 0.5">miny</p>
<p if="anyVarInContext === true">moe</p>

<!-- Will output... -->
<p>eeny</p>

<!-- sometimes not ;) -->
<p>miny</p>

<!-- only if 'anyVarInContext' equals 'true' -->
<p>moe</p>

for / of

Iterates over the evaluation of what is given to of and creates the key given to for inside of the context. Example:

<!-- The template... -->
<p for="fruit" of="fruitsBasket">{index + 1} - {fruit.name}</p>

<!-- Will output... -->
<p>1 - Banana</p>
<p>2 - Apple</p>
<p>3 - Kiwi</p>

The index key, representing the actual item index, will also automatically be added to context during iteration. Caution, if an index already exists into your context, it will override the one produced by for / of.

for / of can be used on custom components as well.

eval: (prefix)

Evaluates the expression it is given and assign the following attribute's value the result. Example:

<!-- The template... -->
<p eval:foo="foo.toUpperCase()"></p>

<!-- Will output... -->
<p foo="HELLO WORLD"></p>

It can be used on every tag, and with every attribute, except on custom components. Props passed to custom components are already automatically evaluated.

It can also be paired with static: to allow resolving paths stored inside context variables. For example, eval:static:src="scriptPathStoredInContext", will first evaluate scriptPathStoredInContext and then resolve the path it contains.

static: (prefix)

Resolves the path it is given and adds it to files to be bundled. Example:

<!-- The template... -->
<link rel="stylesheet" static:href="../../static/style.scss" />

<!-- Will output... -->
<link rel="stylesheet" href="/static/style.css" />

What about runtime ?

ssgo is a static site generator, so it doesn't bother about runtime at all. But, is your app needs to make stuff modern frameworks allow you to do, you can use the excellent AlpineJS that will pair nicely with ssgo.

For example, you can eval expression to be given to alpine's x-data at build by doing eval:x-data="{ foo: foo, bar: bar }", that will output x-data="{ foo: 'hello', bar: 'world' }".

Remaining work

  • Support TS out of the box for pages/ files
  • Add a flag to allow minification of output HTML
  • Add a flag to serve only without rebuilding
  • Allowing nodes inside of custom-components
  • Parsing nested custom components inside of components/ folder
  • Improve path resolving for template files
  • Allow merging class and eval:class
  • Handle errors for two static files resolving to the same exact path
  • Handle errors for two buildPage calls to the same output file
  • Add caching for data given to buildPage, and rebuilt with the old data when file changes concerns a template / component file
1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

4 years ago

1.0.3

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago

0.1.6

4 years ago

0.1.4

4 years ago

0.1.5

4 years ago

0.1.3

4 years ago

0.1.2

4 years ago

0.1.1

4 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.3

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago