Skip to content

Commit fed5728

Browse files
committed
Convert to fragtml for base layout
1 parent f26556d commit fed5728

45 files changed

Lines changed: 507 additions & 376 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 88 additions & 77 deletions
Large diffs are not rendered by default.

bin.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ async function run () {
152152

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

158-
if (!mineVersion || !uhtmlVersion || !highlightVersion) {
158+
if (!mineVersion || !fragtmlVersion || !highlightVersion) {
159159
console.error('Unable to resolve ejected dependency versions. Exiting...')
160160
process.exit(1)
161161
}
@@ -166,7 +166,7 @@ domstack eject actions:
166166
- Write ${join(relativeSrc, targetGlobalStylePath)}
167167
- Write ${join(relativeSrc, targetGlobalClientPath)}
168168
- Add mine.css@${mineVersion} to ${relativePkg}
169-
- Add uhtml-isomorphic@${uhtmlVersion} to ${relativePkg}
169+
- Add fragtml@${fragtmlVersion} to ${relativePkg}
170170
- Add highlight.js@${highlightVersion} to ${relativePkg}
171171
`)
172172
const answer = await askYesNo(rl, 'Continue?')
@@ -190,7 +190,7 @@ domstack eject actions:
190190
{
191191
dependencies: {
192192
'mine.css': mineVersion,
193-
'uhtml-isomorphic': uhtmlVersion,
193+
fragtml: fragtmlVersion,
194194
'highlight.js': highlightVersion,
195195
},
196196
})

docs/v11-migration.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This guide covers all breaking changes introduced in the `next` branch relative
1515
9. [page.md Now Recognized](#9-pagemd-now-recognized)
1616
10. [Web Worker Files Bundled Automatically](#10-web-worker-files-bundled-automatically)
1717
11. [browserVars + esbuild define Conflict Now Throws](#11-browservars--esbuild-define-conflict-now-throws)
18-
12. [Default Layout: uhtml-isomorphic → preact](#12-default-layout-uhtml-isomorphic--preact)
18+
12. [Default Layout: uhtml-isomorphic → fragtml](#12-default-layout-uhtml-isomorphic--fragtml)
1919
13. [Default siteName Changed](#13-default-sitename-changed)
2020
14. [Output File Changes](#14-output-file-changes)
2121
15. [Watch Mode: Unhashed Filenames](#15-watch-mode-unhashed-filenames)
@@ -256,12 +256,12 @@ Previously this silently allowed both to coexist. Choose one approach:
256256

257257
---
258258

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

261-
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`.
261+
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`.
262262

263263
- `uhtml-isomorphic` is **no longer a production dependency** of `@domstack/static`
264-
- `preact` and `preact-render-to-string` are now production dependencies
264+
- `fragtml` is now a production dependency
265265

266266
**Action required:**
267267
- 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:

docs/v12-migration.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Migration Guide: domstack v12
2+
3+
This guide covers breaking and notable changes when moving from domstack v11 to v12.
4+
5+
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.
6+
7+
## Table of Contents
8+
9+
1. [Default Layout Uses fragtml](#1-default-layout-uses-fragtml)
10+
2. [Default Dependencies Changed](#2-default-dependencies-changed)
11+
3. [JSX Runtime Is Opt-In](#3-jsx-runtime-is-opt-in)
12+
4. [Preact Examples Stay Preact](#4-preact-examples-stay-preact)
13+
5. [Development Server Uses @domstack/sync](#5-development-server-uses-domstacksync)
14+
6. [Migration Checklist](#6-migration-checklist)
15+
16+
---
17+
18+
## 1. Default Layout Uses fragtml
19+
20+
The bundled default `root.layout.js` now uses [`fragtml`](https://github.com/bcomnes/fragtml#readme) for server-side HTML rendering.
21+
22+
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`.
23+
24+
```js
25+
// Before
26+
import { html } from 'htm/preact'
27+
import { render } from 'preact-render-to-string'
28+
```
29+
30+
```js
31+
// After
32+
import { html, raw, render } from 'fragtml'
33+
```
34+
35+
Use `raw(htmlString)` when intentionally inserting already-rendered HTML, such as markdown output passed to a layout as `children`.
36+
37+
---
38+
39+
## 2. Default Dependencies Changed
40+
41+
The default template no longer includes Preact, HTM, or `preact-render-to-string`.
42+
43+
When you run `domstack --eject`, domstack adds:
44+
45+
- `mine.css`
46+
- `fragtml`
47+
- `highlight.js`
48+
49+
It does not add:
50+
51+
- `preact`
52+
- `htm`
53+
- `preact-render-to-string`
54+
55+
If your project uses any of those packages directly, keep them in your own `package.json`.
56+
57+
---
58+
59+
## 3. JSX Runtime Is Opt-In
60+
61+
Client `.jsx` and `.tsx` bundles are still supported through esbuild, but domstack no longer configures Preact as the default JSX runtime.
62+
63+
If your browser client code uses JSX or TSX, install the runtime you want and configure it with `esbuild.settings`.
64+
65+
For Preact:
66+
67+
```sh
68+
npm install preact
69+
```
70+
71+
```js
72+
// src/esbuild.settings.js
73+
export default async function esbuildSettingsOverride (esbuildSettings) {
74+
esbuildSettings.jsx = 'automatic'
75+
esbuildSettings.jsxImportSource = 'preact'
76+
77+
return esbuildSettings
78+
}
79+
```
80+
81+
For React:
82+
83+
```sh
84+
npm install react react-dom
85+
```
86+
87+
```js
88+
// src/esbuild.settings.js
89+
export default async function esbuildSettingsOverride (esbuildSettings) {
90+
esbuildSettings.jsx = 'automatic'
91+
esbuildSettings.jsxImportSource = 'react'
92+
93+
return esbuildSettings
94+
}
95+
```
96+
97+
---
98+
99+
## 4. Preact Examples Stay Preact
100+
101+
Examples that actually mount Preact in the browser still use Preact. Examples that only needed server-side HTML rendering now use `fragtml`.
102+
103+
This means Preact remains a good opt-in client runtime, but it is no longer the default server-side layout dependency.
104+
105+
---
106+
107+
## 5. Development Server Uses @domstack/sync
108+
109+
Watch/serve mode now uses [`@domstack/sync`](https://www.npmjs.com/package/@domstack/sync) for the local development server.
110+
111+
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.
112+
113+
---
114+
115+
## 6. Migration Checklist
116+
117+
- [ ] If you use an ejected default layout, update it to `fragtml` or keep your existing layout dependencies explicitly.
118+
- [ ] 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`.
119+
- [ ] If you use `.jsx` or `.tsx` browser clients, add an `esbuild.settings` file that configures your JSX runtime.
120+
- [ ] If you use Preact browser clients, keep `preact` in your project dependencies.
121+
- [ ] If you rely on BrowserSync-specific dev-server behavior, test watch mode with `@domstack/sync`.

examples/basic/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Both global and page-specific CSS is demonstrated, showing how to scope styles a
8686

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

examples/basic/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
},
2121
"dependencies": {
2222
"@domstack/static": "file:../../.",
23-
"htm": "^3.1.1",
24-
"preact": "^10.26.6",
25-
"preact-render-to-string": "^6.5.13",
23+
"fragtml": "^0.0.9",
2624
"mine.css": "^9.0.1",
2725
"highlight.js": "^11.9.0"
2826
}

examples/basic/src/js-page/loose-assets/page.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { html } from 'htm/preact'
1+
import { html } from 'fragtml'
2+
import type { HtmlResult } from 'fragtml/types.js'
23
import type { PageFunction } from '@domstack/static'
34

4-
import sharedData from './shared-lib.js'
5+
import sharedData from './shared-lib.ts'
56
import type { PageVars } from '../../layouts/root.layout.ts'
67

7-
const JSPage: PageFunction<PageVars> = async () => {
8+
const JSPage: PageFunction<PageVars, HtmlResult> = async () => {
89
return html`
910
<div>
1011
<p>

examples/basic/src/js-page/page.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/**
22
* @import { PageFunction } from '@domstack/static'
33
* @import { PageVars } from '../layouts/root.layout.js
4+
* @import { HtmlResult } from 'fragtml/types.js'
45
*/
5-
import { html } from 'htm/preact'
6+
import { html } from 'fragtml'
67

78
/**
8-
* @type { PageFunction <PageVars> }
9+
* @type { PageFunction <PageVars, HtmlResult> }
910
*/
1011
export default async function JSPage ({
1112
vars: {
@@ -26,7 +27,7 @@ export default async function JSPage ({
2627
<ul>
2728
<li>Access and use variables directly in your rendering logic</li>
2829
<li>Generate dynamic content based on data or conditions</li>
29-
<li>Use component-based architecture with Preact or other libraries</li>
30+
<li>Use typed HTML templates or component libraries</li>
3031
<li>Return either HTML strings or component objects</li>
3132
</ul>
3233
</section>
@@ -36,7 +37,7 @@ export default async function JSPage ({
3637
<p>
3738
Export a default function (async or sync) that returns a string or any
3839
type that your layout can handle. In this example, we're using
39-
<a href="https://github.com/developit/htm"><code>htm/preact</code></a> for JSX-like syntax.
40+
<a href="https://github.com/bcomnes/fragtml"><code>fragtml</code></a> for typed, safe HTML templates.
4041
</p>
4142
<div class="code-example">
4243
<pre><code>export default async function MyPage({ vars }) {

examples/basic/src/layouts/child.layout.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { LayoutFunction } from '@domstack/static'
2-
import { html } from 'htm/preact'
3-
import { render } from 'preact-render-to-string'
2+
import { html, raw, render } from 'fragtml'
3+
import type { HtmlResult } from 'fragtml/types.js'
44

5-
import defaultRootLayout from './root.layout.js'
6-
import type { PageVars } from './root.layout.js'
5+
import defaultRootLayout from './root.layout.ts'
6+
import type { PageVars } from './root.layout.ts'
77

8-
const articleLayout: LayoutFunction<PageVars> = (args) => {
8+
const articleLayout: LayoutFunction<PageVars, string | HtmlResult, string> = (args) => {
99
const { children, ...rest } = args
1010
const wrappedChildren = render(html`
1111
<article class="bc-article h-entry" itemscope itemtype="http://schema.org/NewsArticle">
@@ -14,8 +14,8 @@ const articleLayout: LayoutFunction<PageVars> = (args) => {
1414
1515
<section class="e-content" itemprop="articleBody">
1616
${typeof children === 'string'
17-
? html`<div dangerouslySetInnerHTML=${{ __html: children }}></div>`
18-
: children /* Support both preact and string children */
17+
? html`<div>${raw(children)}</div>`
18+
: children
1919
}
2020
</section>
2121
</article>

examples/basic/src/layouts/root.layout.ts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
//
88
// All other variables are set on a page level basis, either by hand or by data extraction from the page type.
99

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

1414
export interface PageVars {
@@ -17,7 +17,7 @@ export interface PageVars {
1717
basePath?: string;
1818
}
1919

20-
const RootLayout: LayoutFunction<PageVars> = async ({
20+
const RootLayout: LayoutFunction<PageVars, string | HtmlResult, string> = async ({
2121
vars: {
2222
title,
2323
siteName,
@@ -27,32 +27,25 @@ const RootLayout: LayoutFunction<PageVars> = async ({
2727
styles,
2828
children,
2929
}) => {
30-
return /* html */`
30+
return render(html`
3131
<!DOCTYPE html>
3232
<html>
33-
${render(html`
34-
<head>
35-
<meta charset="utf-8" />
36-
<title>${siteName}${title ? ` | ${title}` : ''}</title>
37-
<meta name="viewport" content="width=device-width, user-scalable=no" />
38-
${scripts
39-
? scripts.map(script => html`<script type='module' src="${script.startsWith('/') ? `${basePath ?? ''}${script}` : script}" />`)
40-
: null}
41-
${styles
42-
? styles.map(style => html`<link rel="stylesheet" href="${style.startsWith('/') ? `${basePath ?? ''}${style}` : style}" />`)
43-
: null}
44-
</head>
45-
`)}
46-
${render(html`
47-
<body className="safe-area-inset">
48-
${typeof children === 'string'
49-
? html`<main className="mine-layout app-main" dangerouslySetInnerHTML="${{ __html: children }}"/>`
50-
: html`<main className="mine-layout app-main">${children}</main>`
51-
}
52-
</body>
53-
`)}
33+
<head>
34+
<meta charset="utf-8" />
35+
<title>${siteName}${title ? ` | ${title}` : ''}</title>
36+
<meta name="viewport" content="width=device-width, user-scalable=no" />
37+
${scripts
38+
? scripts.map(script => html`<script type="module" src="${script.startsWith('/') ? `${basePath ?? ''}${script}` : script}"></script>`)
39+
: null}
40+
${styles
41+
? styles.map(style => html`<link rel="stylesheet" href="${style.startsWith('/') ? `${basePath ?? ''}${style}` : style}" />`)
42+
: null}
43+
</head>
44+
<body class="safe-area-inset">
45+
<main class="mine-layout app-main">${typeof children === 'string' ? raw(children) : children}</main>
46+
</body>
5447
</html>
55-
`
48+
`)
5649
}
5750

5851
export default RootLayout

0 commit comments

Comments
 (0)