Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 88 additions & 77 deletions README.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,10 @@ async function run () {

const tbPkgContents = await readPackage({ cwd: __dirname })
const mineVersion = tbPkgContents?.['dependencies']?.['mine.css']
const uhtmlVersion = tbPkgContents?.['dependencies']?.['uhtml-isomorphic']
const fragtmlVersion = tbPkgContents?.['dependencies']?.['fragtml']
const highlightVersion = tbPkgContents?.['dependencies']?.['highlight.js']

if (!mineVersion || !uhtmlVersion || !highlightVersion) {
if (!mineVersion || !fragtmlVersion || !highlightVersion) {
console.error('Unable to resolve ejected dependency versions. Exiting...')
process.exit(1)
}
Expand All @@ -166,7 +166,7 @@ domstack eject actions:
- Write ${join(relativeSrc, targetGlobalStylePath)}
- Write ${join(relativeSrc, targetGlobalClientPath)}
- Add mine.css@${mineVersion} to ${relativePkg}
- Add uhtml-isomorphic@${uhtmlVersion} to ${relativePkg}
- Add fragtml@${fragtmlVersion} to ${relativePkg}
- Add highlight.js@${highlightVersion} to ${relativePkg}
`)
const answer = await askYesNo(rl, 'Continue?')
Expand All @@ -190,7 +190,7 @@ domstack eject actions:
{
dependencies: {
'mine.css': mineVersion,
'uhtml-isomorphic': uhtmlVersion,
fragtml: fragtmlVersion,
'highlight.js': highlightVersion,
},
})
Expand Down
8 changes: 4 additions & 4 deletions docs/v11-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This guide covers all breaking changes introduced in the `next` branch relative
9. [page.md Now Recognized](#9-pagemd-now-recognized)
10. [Web Worker Files Bundled Automatically](#10-web-worker-files-bundled-automatically)
11. [browserVars + esbuild define Conflict Now Throws](#11-browservars--esbuild-define-conflict-now-throws)
12. [Default Layout: uhtml-isomorphic → preact](#12-default-layout-uhtml-isomorphic--preact)
12. [Default Layout: uhtml-isomorphic → fragtml](#12-default-layout-uhtml-isomorphic--fragtml)
13. [Default siteName Changed](#13-default-sitename-changed)
Comment on lines 16 to 19
14. [Output File Changes](#14-output-file-changes)
15. [Watch Mode: Unhashed Filenames](#15-watch-mode-unhashed-filenames)
Expand Down Expand Up @@ -256,12 +256,12 @@ Previously this silently allowed both to coexist. Choose one approach:

---

## 12. Default Layout: uhtml-isomorphic → preact
## 12. Default Layout: uhtml-isomorphic → fragtml

The bundled default `root.layout.js` (used when `--eject` has not been run, or when using the default layout) was rewritten from `uhtml-isomorphic` to use `preact` + `preact-render-to-string`.
The bundled default `root.layout.js` (used when `--eject` has not been run, or when using the default layout) was rewritten from `uhtml-isomorphic` to use `fragtml`.

Comment on lines +259 to 262
- `uhtml-isomorphic` is **no longer a production dependency** of `@domstack/static`
- `preact` and `preact-render-to-string` are now production dependencies
- `fragtml` is now a production dependency
Comment on lines 263 to +264

**Action required:**
- If your layout files import `uhtml-isomorphic` and rely on it being hoisted from domstack's `node_modules`, you must now add it explicitly to your project:
Expand Down
121 changes: 121 additions & 0 deletions docs/v12-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Migration Guide: domstack v12

This guide covers breaking and notable changes when moving from domstack v11 to v12.

If you are migrating from `top-bun`, first follow the historical v11 guide at [v11-migration.md](v11-migration.md), then apply the v12 changes below.

## Table of Contents

1. [Default Layout Uses fragtml](#1-default-layout-uses-fragtml)
2. [Default Dependencies Changed](#2-default-dependencies-changed)
3. [JSX Runtime Is Opt-In](#3-jsx-runtime-is-opt-in)
4. [Preact Examples Stay Preact](#4-preact-examples-stay-preact)
5. [Development Server Uses @domstack/sync](#5-development-server-uses-domstacksync)
6. [Migration Checklist](#6-migration-checklist)

---

## 1. Default Layout Uses fragtml

The bundled default `root.layout.js` now uses [`fragtml`](https://github.com/bcomnes/fragtml#readme) for server-side HTML rendering.

If you rely on the bundled default layout, no action is required. If you previously ejected the default layout and want the v12 default style, update your layout imports and rendering code from Preact/HTM to `fragtml`.

```js
// Before
import { html } from 'htm/preact'
import { render } from 'preact-render-to-string'
```

```js
// After
import { html, raw, render } from 'fragtml'
```

Use `raw(htmlString)` when intentionally inserting already-rendered HTML, such as markdown output passed to a layout as `children`.

---

## 2. Default Dependencies Changed

The default template no longer includes Preact, HTM, or `preact-render-to-string`.

When you run `domstack --eject`, domstack adds:

- `mine.css`
- `fragtml`
- `highlight.js`

It does not add:

- `preact`
- `htm`
- `preact-render-to-string`

If your project uses any of those packages directly, keep them in your own `package.json`.

---

## 3. JSX Runtime Is Opt-In

Client `.jsx` and `.tsx` bundles are still supported through esbuild, but domstack no longer configures Preact as the default JSX runtime.

If your browser client code uses JSX or TSX, install the runtime you want and configure it with `esbuild.settings`.

For Preact:

```sh
npm install preact
```

```js
// src/esbuild.settings.js
export default async function esbuildSettingsOverride (esbuildSettings) {
esbuildSettings.jsx = 'automatic'
esbuildSettings.jsxImportSource = 'preact'

return esbuildSettings
}
```

For React:

```sh
npm install react react-dom
```

```js
// src/esbuild.settings.js
export default async function esbuildSettingsOverride (esbuildSettings) {
esbuildSettings.jsx = 'automatic'
esbuildSettings.jsxImportSource = 'react'

return esbuildSettings
}
```

---

## 4. Preact Examples Stay Preact

Examples that actually mount Preact in the browser still use Preact. Examples that only needed server-side HTML rendering now use `fragtml`.

This means Preact remains a good opt-in client runtime, but it is no longer the default server-side layout dependency.

---

## 5. Development Server Uses @domstack/sync

Watch/serve mode now uses [`@domstack/sync`](https://www.npmjs.com/package/@domstack/sync) for the local development server.

This provides live reload, CSS injection, ghost mode, and the UI panel. If you were relying on BrowserSync-specific behavior or output, update your expectations around logs, access URLs, and reload handling.

---

## 6. Migration Checklist

- [ ] If you use an ejected default layout, update it to `fragtml` or keep your existing layout dependencies explicitly.
- [ ] If you use `htm/preact` or `preact-render-to-string` in server-side layouts/pages, either keep those dependencies or migrate that code to `fragtml`.
- [ ] If you use `.jsx` or `.tsx` browser clients, add an `esbuild.settings` file that configures your JSX runtime.
- [ ] If you use Preact browser clients, keep `preact` in your project dependencies.
- [ ] If you rely on BrowserSync-specific dev-server behavior, test watch mode with `@domstack/sync`.
2 changes: 1 addition & 1 deletion examples/basic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Both global and page-specific CSS is demonstrated, showing how to scope styles a

This is one of several examples in the DOMStack repository. For more advanced features, check out the other examples like:
- css-modules
- preact
- fragtml
- tailwind
- and more...

Expand Down
4 changes: 1 addition & 3 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
},
"dependencies": {
"@domstack/static": "file:../../.",
"htm": "^3.1.1",
"preact": "^10.26.6",
"preact-render-to-string": "^6.5.13",
"fragtml": "^0.0.9",
"mine.css": "^9.0.1",
"highlight.js": "^11.9.0"
}
Expand Down
7 changes: 4 additions & 3 deletions examples/basic/src/js-page/loose-assets/page.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { html } from 'htm/preact'
import { html } from 'fragtml'
import type { HtmlResult } from 'fragtml/types.js'
import type { PageFunction } from '@domstack/static'

import sharedData from './shared-lib.js'
import sharedData from './shared-lib.ts'
import type { PageVars } from '../../layouts/root.layout.ts'

const JSPage: PageFunction<PageVars> = async () => {
const JSPage: PageFunction<PageVars, HtmlResult> = async () => {
return html`
<div>
<p>
Expand Down
9 changes: 5 additions & 4 deletions examples/basic/src/js-page/page.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/**
* @import { PageFunction } from '@domstack/static'
* @import { PageVars } from '../layouts/root.layout.js
* @import { HtmlResult } from 'fragtml/types.js'
*/
Comment on lines 1 to 5
import { html } from 'htm/preact'
import { html } from 'fragtml'

/**
* @type { PageFunction <PageVars> }
* @type { PageFunction <PageVars, HtmlResult> }
*/
export default async function JSPage ({
vars: {
Expand All @@ -26,7 +27,7 @@ export default async function JSPage ({
<ul>
<li>Access and use variables directly in your rendering logic</li>
<li>Generate dynamic content based on data or conditions</li>
<li>Use component-based architecture with Preact or other libraries</li>
<li>Use typed HTML templates or component libraries</li>
<li>Return either HTML strings or component objects</li>
</ul>
</section>
Expand All @@ -36,7 +37,7 @@ export default async function JSPage ({
<p>
Export a default function (async or sync) that returns a string or any
type that your layout can handle. In this example, we're using
<a href="https://github.com/developit/htm"><code>htm/preact</code></a> for JSX-like syntax.
<a href="https://github.com/bcomnes/fragtml"><code>fragtml</code></a> for typed, safe HTML templates.
</p>
<div class="code-example">
<pre><code>export default async function MyPage({ vars }) {
Expand Down
14 changes: 7 additions & 7 deletions examples/basic/src/layouts/child.layout.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { LayoutFunction } from '@domstack/static'
import { html } from 'htm/preact'
import { render } from 'preact-render-to-string'
import { html, raw, render } from 'fragtml'
import type { HtmlResult } from 'fragtml/types.js'

import defaultRootLayout from './root.layout.js'
import type { PageVars } from './root.layout.js'
import defaultRootLayout from './root.layout.ts'
import type { PageVars } from './root.layout.ts'

const articleLayout: LayoutFunction<PageVars> = (args) => {
const articleLayout: LayoutFunction<PageVars, string | HtmlResult, string> = (args) => {
const { children, ...rest } = args
const wrappedChildren = render(html`
<article class="bc-article h-entry" itemscope itemtype="http://schema.org/NewsArticle">
Expand All @@ -14,8 +14,8 @@ const articleLayout: LayoutFunction<PageVars> = (args) => {

<section class="e-content" itemprop="articleBody">
${typeof children === 'string'
? html`<div dangerouslySetInnerHTML=${{ __html: children }}></div>`
: children /* Support both preact and string children */
? html`<div>${raw(children)}</div>`
: children
}
</section>
</article>
Expand Down
45 changes: 19 additions & 26 deletions examples/basic/src/layouts/root.layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
//
// All other variables are set on a page level basis, either by hand or by data extraction from the page type.

import { html } from 'htm/preact'
import { render } from 'preact-render-to-string'
import { html, raw, render } from 'fragtml'
import type { HtmlResult } from 'fragtml/types.js'
import type { LayoutFunction } from '@domstack/static'

export interface PageVars {
Expand All @@ -17,7 +17,7 @@ export interface PageVars {
basePath?: string;
}

const RootLayout: LayoutFunction<PageVars> = async ({
const RootLayout: LayoutFunction<PageVars, string | HtmlResult, string> = async ({
vars: {
title,
siteName,
Expand All @@ -27,32 +27,25 @@ const RootLayout: LayoutFunction<PageVars> = async ({
styles,
children,
}) => {
return /* html */`
return render(html`
<!DOCTYPE html>
<html>
${render(html`
<head>
<meta charset="utf-8" />
<title>${siteName}${title ? ` | ${title}` : ''}</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
${scripts
? scripts.map(script => html`<script type='module' src="${script.startsWith('/') ? `${basePath ?? ''}${script}` : script}" />`)
: null}
${styles
? styles.map(style => html`<link rel="stylesheet" href="${style.startsWith('/') ? `${basePath ?? ''}${style}` : style}" />`)
: null}
</head>
`)}
${render(html`
<body className="safe-area-inset">
${typeof children === 'string'
? html`<main className="mine-layout app-main" dangerouslySetInnerHTML="${{ __html: children }}"/>`
: html`<main className="mine-layout app-main">${children}</main>`
}
</body>
`)}
<head>
<meta charset="utf-8" />
<title>${siteName}${title ? ` | ${title}` : ''}</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
${scripts
? scripts.map(script => html`<script type="module" src="${script.startsWith('/') ? `${basePath ?? ''}${script}` : script}"></script>`)
: null}
${styles
? styles.map(style => html`<link rel="stylesheet" href="${style.startsWith('/') ? `${basePath ?? ''}${style}` : style}" />`)
: null}
</head>
<body class="safe-area-inset">
<main class="mine-layout app-main">${typeof children === 'string' ? raw(children) : children}</main>
</body>
</html>
`
`)
}

export default RootLayout
3 changes: 1 addition & 2 deletions examples/basic/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"verbatimModuleSyntax": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
"lib": ["ES2024", "DOM"]
},
"include": [
"**/*",
Expand Down
6 changes: 2 additions & 4 deletions examples/blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
},
"dependencies": {
"@domstack/static": "file:../../.",
"htm": "^3.1.1",
"mine.css": "^10.0.0",
"preact": "^10.26.6",
"preact-render-to-string": "^6.5.13"
"fragtml": "^0.0.9",
"mine.css": "^10.0.0"
}
}
2 changes: 1 addition & 1 deletion examples/blog/src/about/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ first computer programmer.
Built with:

- [domstack](https://github.com/bcomnes/domstack) — the build system
- [preact](https://preactjs.com) — JSX rendering for layouts
- [fragtml](https://github.com/bcomnes/fragtml) — typed HTML rendering for layouts
- [mine.css](https://mine.css.bret.io) — baseline styles
- [esbuild](https://esbuild.github.io) — JS/CSS bundling

Expand Down
2 changes: 1 addition & 1 deletion examples/blog/src/blog/2024/hello-world/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tags:

Welcome to this blog. It's built with [domstack](https://github.com/bcomnes/domstack),
a static site generator that lets you write pages in TypeScript, Markdown, or plain HTML
and compose them with layouts written in JSX (via preact).
and compose them with typed HTML layouts written with fragtml.

## What makes this interesting

Expand Down
3 changes: 1 addition & 2 deletions examples/blog/src/blog/page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { html } from 'htm/preact'
import { render } from 'preact-render-to-string'
import { html, render } from 'fragtml'
import type { PageFunction } from '@domstack/static'
import type { GlobalData } from '../global.data.js'
import type { SiteVars } from '../global.vars.js'
Expand Down
3 changes: 1 addition & 2 deletions examples/blog/src/global.data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { html } from 'htm/preact'
import { render } from 'preact-render-to-string'
import { html, render } from 'fragtml'
import type { AsyncGlobalDataFunction } from '@domstack/static'

export interface BlogPost {
Expand Down
Loading