Skip to content
Merged
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
290 changes: 212 additions & 78 deletions README.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Agent Guidelines

- Write Markdown prose with one sentence per line so git diffs stay focused and readable.
- Never use inline type imports.
- Always favor `@import` syntax at the top of JavaScript files for JSDoc types.
- This repo does not require TypeScript declaration builds during normal development.
- Type builds are only needed during publish time or when debugging types.
- After running a type build, clean up the generated build files and do not leave them sitting around.
- Use the cleanup scripts in `package.json` for generated type build files.
- When handling PR review comments, validate that each comment is correct before making changes; maintainer comments are almost always valid, but review bot comments may be wrong, and after addressing a comment, always reply with what was done.
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
115 changes: 112 additions & 3 deletions docs/v12-migration.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# Migration Guide: domstack v12

This guide is a stub for breaking changes in the next major version of `@domstack/static`.
This guide covers breaking and notable changes when moving from domstack v11 to v12.

## Type exports moved to `@domstack/static/types.js`
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.

In v12, runtime values remain available from `@domstack/static`, but public types have moved to a dedicated type-only entry.
## Table of Contents

1. [Type exports moved to `@domstack/static/types.js`](#1-type-exports-moved-to-domstackstatictypesjs)
2. [Default Layout Uses fragtml](#2-default-layout-uses-fragtml)
3. [Keep Layout Dependencies Explicit](#3-keep-layout-dependencies-explicit)
4. [JSX Runtime Is Opt-In](#4-jsx-runtime-is-opt-in)
5. [Migration Checklist](#5-migration-checklist)

---

## 1. Type exports moved to `@domstack/static/types.js`

In v12, runtime values remain available from `@domstack/static`.
Public types have moved to a dedicated type-only entry.

```ts
// Before v12
Expand All @@ -13,3 +27,98 @@ import type { LayoutFunction, PageFunction, DomStackOpts } from '@domstack/stati
// v12+
import type { LayoutFunction, PageFunction, DomStackOpts } from '@domstack/static/types.js'
```

---

## 2. 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, make sure your pages and child layouts return compatible values.
The v12 default layout accepts HTML strings and `fragtml` template results.
It does not render Preact or HTM VNodes.

If you already ejected or provide your own root layout, you do not have to change that layout for v12.
Keep the dependencies that your layout imports in your own `package.json`.

If you want to update to v12 while keeping the v11 Preact default layout, run `domstack --eject` on v11 before upgrading.
Then keep `htm`, `preact`, and `preact-render-to-string` installed after the upgrade.

If you want to migrate an ejected Preact/HTM layout to 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.
Markdown output passed to a layout as `children` is one example.

---

## 3. Keep Layout Dependencies Explicit

DOMStack only installs dependencies for its bundled defaults.
Your project is responsible for any packages imported by pages, layouts, globals, or browser clients.

If your ejected layout or server-side pages import `htm/preact`, `preact`, or `preact-render-to-string`, keep those packages in your own `package.json`.
If you migrate those server-side templates to `fragtml`, replace those dependencies with `fragtml`.

---

## 4. JSX Runtime Is Opt-In

Client `.jsx` and `.tsx` bundles are still supported through esbuild.
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
}
```

---

## 5. Migration Checklist

- [ ] If you import public types from `@domstack/static`, update those imports to `@domstack/static/types.js`.
- [ ] If you rely on the bundled default layout, make sure pages and child layouts return HTML strings or `fragtml` template results, not Preact or HTM VNodes.
- [ ] If you want to keep the v11 Preact default layout, eject on v11 before upgrading to v12.
- [ ] If your ejected layout or server-side pages still import `htm/preact`, `preact`, or `preact-render-to-string`, keep those dependencies in your own `package.json`.
- [ ] If you want your ejected server-side layout to match the v12 default, migrate its templates to `fragtml` and install `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.
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/types.js'

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
11 changes: 6 additions & 5 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/types.js'
* @import { PageVars } from '../layouts/root.layout.js
* @import { PageVars } from '../layouts/root.layout.ts'
* @import { HtmlResult } from 'fragtml/types.js'
*/
Comment thread
Copilot marked this conversation as resolved.
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/types.js'
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/types.js'

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/types.js'
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/types.js'

export interface BlogPost {
Expand Down
Loading