A zero-config cli for building static websites.
- 🪄 Can be used without installing it via
npx onlybuild
- 🐣 Small footprint when installed
- ⏱️ Fast build times, see benchmark data
- 🧰 Use with any framework or no framework at all
- 📦 Easily deploy built files to any static file service, ex: Google Cloud Bucket, AWS S3, GitHub Pages
- 📖 Easily import data from local JSON files or fetch remote API data
- Star this repo on GitHub for updates
- Follow me on Bluesky or Twitter
- Join the Discord
- Follow me on GitHub
- Automatic Installation
- Install
- Usage
- Quick Start Guide
- Getting Started
- File Structure
- Ignore Files
- React
- TypeScript
- Formatting Files
- Watching For Changes
- Local Server
- Examples
- Benchmark
- Testing
- Contributing
- Community Roadmap
- License
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.
demo.mp4
$ 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.
Files | Build Output |
---|---|
|
|
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"
},
...
}
- Hello, world! - A simple Hello, world example.
- Markdown - Get the contents of a local Markdown file and convert the contents to HTML.
- External API - Output data fetched from an external API.
`html`
String Template - Use the`html`
string template utility to add syntax highlighting to HTML.- Includes - An example that uses reusable includes for building multiple pages.
- React - An example using React to render HTML.
- TypeScript - A simple example using TypeScript.
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
.
- Tests are authored using the native Node.js test runner.
- Tests are run automatically via GitHub Actions on each new PR.
- For you add a new feature or fix a bug, please include the benchmark output in the PR along with your device stats.
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 👍.