mesmer v0.0.4
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.