A zero-config cli for building static websites.
MIT License
A zero-config cli for building static websites.
npx onlybuild
If this is your first time using onlybuild
, it is recommended that you use create-onlybuild-app
, which will walk you through setting up a project and automatically create the files needed to get started.
$ npx create-onlybuild-app@latest
After you run the above command, you'll see the following prompts:
What is your project named?
Would you like to use TypeScript?
How would you like to create pages?
Include prettier config?
Do you want to watch for changes?
Do you want to serve files locally?
And once you answer these questions, create-onlybuild-app
will automatically create the files needed to get started based on your answers.
It will also show the commands needed to enter the folder and build, watch (if enabled) or serve (if enabled) your static website.
https://github.com/neogeek/onlybuild/assets/6753/c15b6b96-7489-46c5-bc7e-a84be8fdc841
$ npm install onlybuild -g
$ onlybuild
$ npx onlybuild
$ npm install onlybuild --save-dev
{
...
"scripts": {
"build": "onlybuild"
},
...
}
$ npm run build
Usage: onlybuild <path> [options]
Options:
-h, --help Display this help message.
-v, --version Display the current installed version.
-o, --out Sets build directory. Default path is build/
-i, --ignore Sets ignore file path. Default path is .onlyignore
Create a new file index.mjs
with the following contents:
export default '<h1>Hello, world!</h1>';
Run npx onlybuild
from the directory the index.mjs
file is in.
That's it! You will now have a build/
directory with an index.html
file in it.
You can have the default export return a string.
export default '<h1>Hello, world!</h1>';
You can have the default export generate a string at runtime via a method.
const renderPage = () => '<h1>Hello, world!</h1>';
export default renderPage();
You can return asynchronously if you need to read a local file or call an external API.
import { readFile } from 'node:fs/promises';
const renderPage = async () => await readFile('index.html', 'utf8');
export default renderPage();
const comments = await fetch(
'https://jsonplaceholder.typicode.com/posts/1/comments'
).then(response => response.json());
export default `<div>${comments
.map(post => `<section><h2>${comments.name}</h2><p>${post.body}</section>`)
.join('\n')}</div>`;
You can run the contents through other libraries, for example, converting a Markdown file into HTML before returning it using libraries like Marked.
import { readFile } from 'node:fs/promises';
import { marked } from 'marked';
export default marked.parse(await readFile('index.md', 'utf8'));
This Markdown example parses code blocks and adds CSS classes before rendering the page to HTML.
import { readFile } from 'node:fs/promises';
import { Marked } from 'marked';
import { markedHighlight } from 'marked-highlight';
import hljs from 'highlight.js';
const marked = new Marked(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang, info) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);
export default marked.parse(await readFile('index.md', 'utf8'));
The onlybuild
library includes an optional `html` string template utility that can be used to add syntax highlighting and formatting to HTML, making it easier to author HTML in JavaScript.
import { html } from 'onlybuild';
export default html`<h1>Hello, world!</h1>`;
Install the lit-html and prettier plugin in VS Code to help format the HTML on save.
The onlybuild
library includes an optional `css` string template utility that can be used to add syntax highlighting and formatting to CSS, making it easier to author CSS in JavaScript.
import { css } from 'onlybuild';
const styles = css`
body {
color: red;
}
`;
export default html`<style>
${styles}
</style>`;
Install the prettier plugin in VS Code to help format the CSS on save.
When you run npx onlybuild
, all .mjs
files with a export default
that returns a string will be captured and written to the build/
directory. All other files will be copied with the same file structure to the build
directory unless included in the .onlyignore
file or in a default ignored directory, indicated by a leading _
character.
If the name of your .mjs
file is index.mjs
the output will be saved to index.html
, but if it's name is something-else.mjs
the output will be saved to something-else/index.mjs
.
See the example file structure below for a more comprehensive example that includes building files and copying static files.
├── _includes
│ └── head.mjs
├── about
│ └── index.mjs
├── blog
│ └── hello-world.mjs
├── css
│ └── styles.css
├── images
│ └── icon.png
└── index.mjs
├── index.html
├── css
│ └── styles.css
├── images
│ └── icon.png
├── about
│ └── index.html
└── blog
└── hello-world
└── index.html
If you want to ignore files from being generated into static files or copied into the build.
directory you can add them to an ignore file called .onlyignore
, which has a syntax similar to .gitignore
files.
As stated in the previous section, any files in a directory with a leading _
character will be automatically ignored. Example: _includes
or _data
.
*.md
screenshot.png
LICENSE
If you want to use React, instead of `html` string templates, you can do that by using react-dom/server
in a .jsx
or .tsx
file.
import React from 'react';
import { renderToString } from 'react-dom/server';
function Hello() {
return <h1>Hello, React!</h1>;
}
export default renderToString(<Hello />);
In order for .jsx
or .tsx
files to work properly you will need to add "type": "module"
to your package.json
.
{
...
"type": "module",
...
}
In order for TypeScript files to work properly you will need to add "type": "module"
to your package.json
.
{
...
"type": "module",
...
}
If you want to reformat the HTML files in the build directory, you can use Prettier after the build completes.
{
...
"scripts": {
"build": "onlybuild",
"format": "npx prettier --write \"build/**/*.html\""
},
...
}
If your build/
directory is in .gitignore
(which it probably should be) you will need to ignore the .gitignore
file by setting the --ignore-path
flag to something else. The file you set it to doesn't need to exist.
{
...
"scripts": {
"build": "onlybuild",
"format": "npx prettier --write --ignore-path .prettierignore \"build/**/*.html\""
},
...
}
If you want to automatically rebuild the project when files are updated you can use nodemon.
{
...
"scripts": {
"build": "onlybuild",
"watch": "npx nodemon --ext mjs,md,css --ignore ./build -x \"npm run build\""
},
...
}
Serving the files once the build is complete is easy using the NPM package http-server.
{
...
"scripts": {
"build": "onlybuild",
"serve": "npx http-server build"
},
...
}
[!NOTE] Each run (for
onlybuild
only) was repeated 5 times and the lowest/fastest time was selected. This result set was generated on a MacBook Air (M1, 2020), macOS Sonoma 14.4.1, 8 GB memory.
Times shown are in seconds. Lower is better.
Markdown Files | 250 | 500 | 1000 | 2000 | 4000 |
---|---|---|---|---|---|
onlybuild | 0.349 |
0.455 |
0.626 |
0.980 |
1.661 |
Hugo v0.101.0 | 0.071 |
0.110 |
0.171 |
0.352 |
0.684 |
Eleventy 1.0.1 | 0.584 |
0.683 |
0.914 |
1.250 |
1.938 |
Astro 1.0.1 | 2.270 |
3.172 |
5.098 |
9.791 |
22.907 |
Gatsby 4.19.0-cli | 14.462 |
15.722 |
17.967 |
22.356 |
29.059 |
See more benchmark data at https://www.zachleat.com/web/build-benchmark/
To run the benchmarks locally you have to install bc
. This can be done on macOS by using brew install bc
.
Once you have bc
installed, install NPM packages for the main repo, then navigate to the tests/benchmarks
directory, install NPM packages there as well, and then run ./bin/run.sh
to start the benchmark tests.
Run all tests via npm test
.
Be sure to review the Contributing Guidelines before logging an issue or making a pull request.
The goal of this project is to keep the features it offers to a minimum, allowing you, the developer, to forge your own path. If you have feature requests or bugs, please create an issue and tag them with the appropriate tag. If an issue already exists, vote for it with 👍.