Create
To get started, you’ll need to have Node.js or another server side JavaScript runtime installed on your computer.
Run this command in your terminal and follow the instructions provided.
npm create domco@latest
This command creates files in your project’s directory instead of having to manually set up the plugin with Vite.
The following documentation covers the basics of creating a site and all of the features domco provides in addition to Vite. See the Vite documentation for more information and configuration options.
HTML
index
By default, your project is located in src
—this serves as the root directory of your Vite project. src/index.html
can be accessed at https://website.com/
, while src/nested/index.html
can be accessed at https://website.com/nested/
.
You can set
root
in yourvite.config
file to a different directory if you prefer.
To create another page, add another index.html
file in another directory within the root directory.
domco configures Vite to process each index.html
page in the root directory as a separate entry point automatically. Everything linked in these pages will be bundled and included in the output upon running vite build
.
For example, to add the /nested/
page, add src/nested/index.html
.
.
└── src/
├── index.html
└── nested/
└── index.html
Now you can navigate to nested with an anchor tag.
<a href="/nested/">Nested</a>
By default in Vite, you must use a trailing slash
/nested/
to navigate tosrc/nested/index.html
.
Styling
Styles can be linked to an HTML page using the link
tag within the head
tag. If you used the create-domco
script, this has been configured for you.
Notice that the
href
for these links are relative to the root directory—this tag will linksrc/style.css
.
<!-- index.html -->
<link rel="stylesheet" href="/style.css" />
Client Side JavaScript
domco doesn’t do anything extra on top of Vite involving client side JavaScript. To include JavaScript in a page that needs to be executed in the browser when a user navigates to the page, add a JavaScript or TypeScript file within your root. This file can be named whatever you want, in this case index.ts
.
.
└── src/
├── index.ts
├── index.html
└── nested/
└── index.html
// src/index.ts
console.log("Hello from the client!");
Then in the HTML file that you want to utilize the script in, add a script
tag with a src
attribute that links to the file relative to the root.
<!-- index.html -->
<script type="module" src="/index.ts"></script>
Now that this script is included in an entry point index.html
, the script, and anything that is imported, will be bundled and included in the final build.
Config
The most powerful feature domco provides is the ability to modify pages at build time in a variety of ways. To do this create a +config
file. This file exports a config
object to run build time modifications.
// src/+config.ts
import type { Config } from "domco";
export const config: Config = {
build: async ({ document }) => {
// modifies `./index.html` in the same directory.
},
layoutBuild: async ({ document }) => {
// modifies `./index.html` and all nested `./**/index.html` pages.
},
// wraps `./index.html` and all nested `./**/index.html` pages.
layout: await fs.readFile("src/layout.html", "utf-8"),
// specify parameters for dynamic routes.
params: [
{ slug: "first-post" },
{ slug: "second-post" },
{ slug: "third-post" },
],
};
+config
can be renamed if you prefer within your vite.config
:
// vite.config
import { defineConfig } from "vite";
import { domco } from "domco/plugin";
export default defineConfig({
plugins: [
domco({
configFileName: "customConfigFileName",
}),
],
});
build
When building a website, it’s often useful to render content with JavaScript instead of manually writing HTML. This can be accomplished through including client side JavaScript.
Rendering on the client has some drawbacks, especially if the content that is rendered is the same for every user. Browsers process HTML first before JavaScript, so if the JavaScript to render the HTML hasn’t run yet, users won’t see the resulting HTML until it has completed. This can lead to an undesirable experience if the user has to wait for the page to load, or the content they are viewing shifts after it loads.
domco uses jsdom to enable you to utilize the same code that is written on the client, to update the HTML at build time. This way, the code gets ran once, and users enjoy a “pre-rendered” result without having to load any JavaScript.
A build
function can be provided in your config
to modify the contents of the index.html
file in the same directory at build time. This function modifies the passed in window
object created from ./index.html
with jsdom.
// src/+config.ts
import type { Config } from "domco";
export const config: Config = {
build: async ({ document }) => {
const p = document.createElement("p");
p.textContent = "A server rendered paragraph.";
document.body.appendChild(p);
},
};
Any of the properties on window
are available through the first argument such as document
or HTMLElement
. Document methods or other rendering techniques can be utilized to create new content and make updates at build time. You can run the code contained in the build
function body on the client to have it rendered on the client instead.
This code is not included in your final bundle, the
build
function only modifies HTML. If you need to have client side interactivity, include a client side script.For example, running
document.addEventListener
in abuild
function, will not change the resulting HTML. Put this into a client side script.
Since this module runs during build time, server side JavaScript like NodeJS APIs can be utilized here. For example, you can use fs.readFile
in a build
function to read a markdown file, convert it to HTML, and then insert the HTML into the page using window
methods.
layoutBuild
A layoutBuild
function can also be created which modifies the current page and all nested pages.
For example, if you want the same build
function to modify every page, add a layoutBuild
to the config
object. This script now runs on src/index.html
and src/nested/index.html
.
layout
Layouts can be utilized to create a layout that wraps around the content of other pages. Layouts make it easier to apply markup, styles, and scripts to multiple pages without having to rewrite code. For example, if you have a navigation bar that renders on every page, you could put this HTML within a layout to render it on every page.
layout
can be set directly to a string. But, it’s often easier to create a separate file for your layout and use a file system module likenode:fs
to read the file instead. This provides some flexibility for you to store your layouts where you prefer.
Include a <slot></slot>
within the layout that designates where ./index.html
should be rendered. If you selected to include a layout when creating your project, this is already created for you.
- Layouts wrap all nested pages. For example,
src/layout.html
also wrapssrc/nested/index.html
. - Layouts can be created in any directory within your root, and will apply to all other nested
index.html
files within the directory it is created in.
Dynamic Routes
Generate pages dynamically using brackets as directory names.
.
└── src/
└── posts/
└── [slug]/
├── +config.ts
└── index.html
Then in +config
you can provide the possible parameters with the params
array. params
can also be calculated programmatically. Optionally, pass typeof params
to Config
to create a type for the params
object within BuildContext
.
// src/posts/[slug]/+config.ts
import type { Config } from "domco";
const params = [
{ slug: "first-post" },
{ slug: "second-post" },
{ slug: "third-post" },
] as const;
export const config: Config<typeof params> = {
params,
build: async ({ document }, { params }) => {
const h1 = document.querySelector("h1");
if (h1) h1.textContent = params.slug; // "first-post" | "second-post" | "third-post"
},
};
This configuration would generate the following file structure.
.
└── src/
└── posts/
├── first-post/
│ └── index.html
├── second-post/
│ └── index.html
└── third-post/
└── index.html
In the case of src/posts/[slug]/nested/[another]
, specify a key for each parameter: { slug: "slug", another: "another" }
.
Block
Sometimes you’ll need to reuse code in multiple build
functions that reside in different files. If the code that needs to be reused utilizes the window
object, for example document.querySelector
, then you’ll need to create a Block
since window
is not available to a server side JavaScript runtime by default.
To do this, provide window
as an argument to those imported functions. domco provides a type Block
to help with this. Blocks can be run on the client or the server by passing in the window
object from the build
function, or from the browser’s runtime.
// src/lib/blocks/myBlock.ts
import type { Block } from "domco";
export const functionThatUsesDocument: Block = async ({ document }) => {
document.querySelector("p");
...
}
And then in a +config
file you can utilize these modules.
// src/+config.ts
import type { Config } from "domco";
import { functionThatUsesDocument } from "$lib/blocks/myBlock.ts";
export const config: Config = {
build: async (window) => {
await functionThatUsesDocument(window);
},
};
domco also provides a helper function to run multiple blocks asynchronously—addBlocks
.
Components
One common use case for JavaScript framework components is to reuse chunks of HTML. One way to accomplish this on the server is by using the web platform’s custom elements within a Build
or Block
function.
// src/+config.ts
import type { Config } from "domco";
export const config: Config = {
build: ({ customElements, HTMLElement }) => {
customElements.define(
"my-custom-element",
class extends HTMLElement {
connectedCallback() {
this.innerHTML = /* html */ `
<div>My custom div.</div>
`;
}
},
);
},
};
Then, in your HTML, you can utilize this custom element declaratively.
<my-custom-element></my-custom-element>
These custom elements will not be interactive since they are only running on the server.
If you’re using VS Code as your editor, es6-string-html is a nice extension to get syntax highlighting for HTML within strings.
public
The public
directory is for housing static assets that you do not want modified in your final build, these will be copied into the output directory as they are. To reference these files just use /file
. For example, to reference public/image.png
, write /image.png
.
domco has configured
public
to exist outside ofsrc
, this can be changed to wherever you prefer in yourvite.config
.
lib
src/lib/
has been configured with the $lib/
alias for convenience. This is a good place to house shared code that will be imported in other places in your project.
Building for Production
Run the following command to execute vite build
and build your project, you will see the results outputted to the dist
folder.
npm run build
HTML Minification
domco minifies html during during build using html-minifier-terser. You can change the default settings in your vite.config
.
// vite.config
import { defineConfig } from "vite";
import { domco } from "domco/plugin";
export default defineConfig({
plugins: [
domco({
minifyHtmlOptions: {
removeComments: false,
},
}),
],
});
Deploy
domco generates a static site, which makes it easy to deploy on a variety of platforms.
Since domco is a Vite plugin, it can be deployed on many services with zero configuration. These services will run vite build
on a remote server for you and deploy your project to a content delivery network. This site is deployed on Vercel.
Check out the Vite’s documentation on deploying a static site to learn more.