0.0.4 • Published 2 years ago

mesmer v0.0.4

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Warning This project is currently in a very early stage of development and, as such, may change drastically at any given time.

Mesmer is a delightful static site generator based on React. It aims to be as simple as possible, letting you focus on your React components and written content, while taking care of all the hassles of JavaScript bundling and React server-side rendering.

Features

  • :wrench: No complicated configuration is required,
  • :atom_symbol: The API is designed with React and modern ES features in mind,
  • :link: Simple metadata system lets you share information across pages,
  • :memo: Out-of-the-box support for MDX documents,
  • :recycle: A programmatic interface for dynamic page generation,
  • :electric_plug: A plugin system letting you hook into the build process and add features like page previews or search index generation.

Getting Started

System Requirements

You should be running at least Node 16 on your system for everything to run properly.

Starting with Mesmer Starter

Mesmer Starter is a very simple example project that gets you running developmen server with a basic setup in a matter of minutes. If you just want to start hacking, clone the Mesmer Starter repository by running

git clone https://github.com/hacksparr0w/mesmer-starter.git

You can then use npm run dev or npm run build to start a development server or build the whole thing.

Starting from Scratch

If you prefer to start with minimal setup, you can easily do so by running

npm i mesmer react react-dom

See the documentation section below to find out how to point Mesmer to your ES modules for static site generation.

API

mesmer.json

mesmer.json is a configuration file that lets Mesmer know where to look for your ES modules. Each ES module you include in your project's configuration file should contain at least a default export pointing to a React component that shall be used in the rendering process. All possible module exports that can affect Mesmer's behavior will be discussed later.

An example mesmer.json file can look roughly as following

{
  "metadata": {
    "name": "Mesmer Starter",
    "githubUrl": "https://github.com/hacksparr0w/mesmer"
  },
  "build": {
    "baseUrl": "https://github.com/hacksparr0w/mesmer-starter",
    "pages": [
      "./src/page/*.jsx",
      "./src/page/post/*.mdx"
    ]
  }
}

Right away, you can notice that the configuration object contains metadata and pages properties. pages property is simply an array of glob patterns that specify paths to your ES modules. Each ES module matched by a glob pattern gets rendered into a single HTML page.

For now, all you need to know about the metadata property is that this property is related to Mesmer's metadata system. This object will be merged with other metadata across the whole project and will be passed to React components during rendering. The metadata configuration key is optional and its value is up to the user to be defined.

module.default

When rendering your ES modules into HTML documents, Mesmer expects each module to have a default export pointing to a React component. In practice your ES modules can look as the following

import React from "react";

export default () => (
  <h1>Welcome to Mesmer!</h1>
);

This is very natural, as it is what you would probably do anyway when building with React.

module.metadata

Let's now talk about Mesmer's metadata system. Each ES module passed to Mesmer can optionally export an object called metadata. During the rendering phase, Mesmer merges all of the exported metadata objects from each of your ES modules, combines them with some other metadata sources and produces a metadata of your whole application. The resultant combined metadata object is then passed to your React components as a metadata prop. In this way, it is possible for all parts of your application to communicate with one another.

Following is a table of metadata props passed down to your React components by Mesmer and their respective sources.

Below is a snippet taken from Mesmer Starter that utilizes the metadata system to render a list of all published blog posts on the front page

import React from "react";

import { Page, Navbar, Posts, PostCard } from "./component";

export const metadata = {
  title: "Welcome to Mesmer!"
};

export default ({ metadata: { pages, project: { name } } }) => {
  const posts = pages.filter(
    ({ documentFilePath }) => documentFilePath.includes("post")
  );

  return (
    <Page>
      <header>
        <Navbar projectName={name} />
        <h1>Welcome to Mesmer!</h1>
      </header>
      <main>
        <Posts>
          <h2>Posts</h2>
          {posts.map(post => {
            const { documentFilePath } = post;

            return (
              <PostCard key={documentFilePath} post={post} />
            );
          })}
        </Posts>
      </main>
      <Footer />
    </Page>
  );
};

module.template

By default, Mesmer renders the component specified by the module.default export. Most of the times though, you will want the componenet to be wrapped by some kind of HTML preamble including common tags like <head>, <body> and so on. This can be accomplished by using the module.template export.

Following is an example of using such a construct

// Contents of HtmlTemplate.jsx

import React from "react";

export const containerSelector = "#app";

export default ({
  metadata: {
    build: { clientBundleFilePath },
    page: { title }
  },
  children
}) => (
  <html>
    <head>
      <meta charSet="UTF-8" />
      <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>{title}</title>
      <link rel="stylesheet" href={highlightTheme} />
    </head>
    <body>
      <div id={containerSelector.slice(1)}>
        {children}
      </div>
    </body>
    <script src={clientBundleFilePath} />
    <script dangerouslySetInnerHTML={{ __html: `
      window.Prism = window.Prism || {};
      window.Prism.manual = true;
    `}} />
  </html>
);
// Contents of index.jsx

import React from "react";

import * as HtmlTemplate from "./HtmlTemplate";

export const metadata = {
  title: "Welcome to Mesmer!"
};

export const template = HtmlTemplate;

export default () => (
  <h1>Welcome to Mesmer!</h1>
);

There are some important things worth noticing. Firstly, the module where the HtmlTemplate is to be used has to import it using the star import notation

import * as HtmlTemplate from "./HtmlTemplate";

This is an important detail, as the HtmlTemplate module might itself export some directives influencing the rendering behavior. Mesmer uses ES modules as its elementary API primitive, not React components. This architectural decision was made so that Mesmer can easily support assets like MDX documents that compile into a single ES module.

The HtmlTemplate itself is actually pretty straightforward. It includes some headers linking to local stylesheets and declares a document <title>, referencing the metadata declared in its child component. The child component is rendered into an element and is passed to the template into its <body>. To make your React application hydrate on the client-side, you need to include your bundled application code. This is done with the following line of code

<script src={clientBundleFilePath} />

The clientBundleFilePath is a property found in the metadata.build object.

Your bundled application code includes a client-side rendering procedure that needs to know where exactly in the template it should hydrate your uppermost React component to. This information is passed by utilizing the module.containerSelector export as you can see in the previous code snippet.

module.containerSelector

As stated above, the module.containerSelector is utilized by template modules to tell the client renderer where in the template should the uppermost React component be rendered.

module.parent

module.parent export is very similar to the module.template export, but works a bit different. It is mainly used in MDX files, where you cannot explicitly affect the generated module.default export. When Mesmer encounters a module with module.parent export, it will use the parent module's React component as the uppermost component. The child component will be turned into an element and will get passed in the children prop to the parent. Both of these components will additionally receive the same metadata prop.

The module.parent export should point to an ES module, not a React component. If a parent module has module.template export, it will be used preferentially before its child's module.template export.

Note We use the term partials to refer to template and parent components to differentiate them from regular components, as they are not meant to be used on their own. It is a good practice to create a separate directory for modules with this type of components in your project.

Here's a simple example taken from Mesmer Starter where a module.parent export is used to specify a parent React component a blog post should be rendered as part of

// Contents of first-post.mdx

import * as Post from "./Post";

export const parent = Post;

export const metadata = {
  title: "First blog post",
  subtitle: "In this blog post, you'll find out about some cool things you can do with Mesmer.",
  publishedOn: "12. 04. 2022",
  topic: "blogging",
  emoji: "waving-hand"
};

You can find this post in the `src/page/post` folder. Try running the Mesmer
dev server using `npm run dev`, modifying this blog post and see it being
rebuild on the fly!
// Contents of Post.jsx

import React from "react";

import {
  Container,
  Navbar,
  Page,
  PostContent,
  PostHeader
} from "./component";

import * as HtmlTemplate from "./HtmlTemplate";

export const template = HtmlTemplate;

export default ({
  metadata: {
    page: { title, publishedOn },
    project: { name, githubUrl }
  },
  children
}) => {
  useEffect(() => {
    Prism.highlightAll();
  }, []);

  return (
    <Page>
      <header>
        <Navbar projectName={name} githubUrl={githubUrl} />
      </header>
      <Main>
        <Container>
          <PostHeader>
            <h1>
              {title}
            </h1>
            <p>Published on {publishedOn}</p>
          </PostHeader>
          <PostContent>
            {children}
          </PostContent>
        </Container>
      </Main>
      <Footer />
    </Page>
  );
};

CLI

mesmer build

The build command looks for the mesmer.json configuration file in your current directory and uses it to build your project into a build folder.

mesmer serve

The serve command starts a local development server with live reload that listens for changes made to your source files.

Issues

Found bug or have an idea for a cool feature? Please, open an issue in our issue tracker.