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
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ const TREES: Record<TreeName, NavItem[]> = {
title: 'Guides',
icon: 'tabler:book',
children: [
{ title: 'Themes', url: '/guides/styling/themes/', icon: 'tabler:palette' },
{
title: 'Themes',
url: '/guides/styling/themes/',
icon: 'tabler:palette',
},
{
title: 'Components',
icon: 'tabler:components',
Expand Down
6 changes: 1 addition & 5 deletions frontend/purestack.studio/guides/extending/_nav.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{
"sequence": [
"index.mdx",
"regor.mdx",
"plugins.mdx"
]
"sequence": ["index.mdx", "regor.mdx", "plugins.mdx"]
}
22 changes: 13 additions & 9 deletions frontend/purestack.studio/guides/getting-started/purestack-cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Add `content/about.mdx` with its own title and body. Then start the preview serv
yarn purestack serve --content ./content
```

Open the URL printed as `serving at` after the first build. With the default settings it is `http://127.0.0.1:4173/`. The server binds to `0.0.0.0` by default; pass `--host 127.0.0.1` if it should listen only on your machine. If `basePath` is set in `siteConfig.json`, the printed URL includes it.
Open the URL printed as `serving at` once shared setup finishes. Pages render as you open them. With the default settings the URL is `http://127.0.0.1:4173/`. The server binds to `0.0.0.0` by default; pass `--host 127.0.0.1` if it should listen only on your machine. If `basePath` is set in `siteConfig.json`, the printed URL includes it.

## Understand the content folder

Expand Down Expand Up @@ -97,7 +97,7 @@ Every build command requires `--content <dir>`. The path is resolved from the wo
| Command | Result | Output folder |
| --- | --- | --- |
| `build` | Generates the complete static site. | `outDir` |
| `serve` | Builds the site, starts an HTTP server, and watches content by default. | `outDir` |
| `serve` | Starts an HTTP server, renders pages on request, and watches content by default. | `outDir` |
| `publish` | Cleans and builds a release artifact. | `publishDir` |

Use `yarn purestack --help` or `yarn purestack help` to print the CLI's own usage summary. Options can be written as `--content ./content` or `--content=./content`.
Expand All @@ -118,19 +118,25 @@ yarn purestack build --content ./content --clean
```sh
yarn purestack serve --content ./content
yarn purestack serve --content ./content --host 127.0.0.1 --port 4300
yarn purestack serve --content ./content --clean --full-render
```

The server uses port `4173` and host `0.0.0.0` unless you override them. It serves files from `outDir`, checks watched content changes, and reloads open pages after changes. Content changes are handled incrementally where possible; a change to `siteConfig.json`, to `purestack.config.ts`, or to a file the config imports causes a full rebuild so the new settings and plugins are applied.
The server uses port `4173` and host `0.0.0.0` unless you override them. By default, startup prepares routes, navigation, and shared page sections. Each page renders on its first request and is cached for later requests; static assets copy when requested. Watched edits invalidate affected pages so they render again on the next request, and open pages reload automatically. Changes to `siteConfig.json`, `purestack.config.ts`, or a file the config imports refresh shared setup and invalidate cached pages.

Use `--full-render` when you need every page, asset, and the complete search index generated before browsing. It runs one full build at startup, and site requests wait for that build to finish. After startup, watched edits use the same incremental invalidation and rendering on request as the default mode.

When search is enabled, the default mode builds its search index when a Pagefind asset is requested and includes only pages opened in the current session. `--full-render` starts with a complete search index; later changes are handled incrementally. `build` and `publish` always index the complete site.

These options belong to `serve`:

| Option | Effect |
| --- | --- |
| `--host <host>` | Listen on a particular interface, for example `127.0.0.1`. |
| `--port <port>` | Listen on a different port. |
| `--clean` | Remove `outDir` before the initial build. |
| `--no-watch` | Keep the server running without watching the content directory. The initial build still runs. |
| `--no-reload` | Keep watching and rebuilding, but do not inject the browser live reload script. |
| `--clean` | Remove `outDir` before setup and when config changes reset shared setup. |
| `--full-render` | Build every page, asset, and the complete search index once at startup. Watched edits remain incremental. |
| `--no-watch` | Keep the server running without watching the content directory. Pages still render on request, or all at startup with `--full-render`. |
| `--no-reload` | Keep watching and updating the site, but do not inject the browser live reload script. |

Watching and browser reload are separate. For example, `--no-reload` is useful if another browser tool manages refreshes. The server displays page build errors as diagnostic pages so you can correct the source and continue editing.

Expand Down Expand Up @@ -169,10 +175,8 @@ The home page stays at `dist/site/index.html`, but links and the development ser
| --- | --- |
| `Missing required --content <dir> option` | Pass `--content` to `build`, `serve`, or `publish`. |
| `Missing required siteConfig.json` | Check the path passed to `--content` and the file's exact name. |
| `Unknown option` | Run `yarn purestack --help`; `--host`, `--port`, `--no-watch`, and `--no-reload` apply only to `serve`. |
| `Unknown option` | Run `yarn purestack --help`; `--host`, `--port`, `--no-watch`, `--no-reload`, and `--full-render` apply only to `serve`. |
| `Could not bundle …purestack.config.ts` | Check the imports in `purestack.config.ts` and the files it imports; the message names the import that failed. |
| `Duplicate content routes detected` | Find two files that map to the same URL, such as `guide/index.md` and `guide/guide.mdx`. |
| A page or asset returns 404 under a subpath | Check `basePath`, the URL printed by `serve`, and the path at which your host serves the output folder. |
| `publish` fails while `serve` displayed an error page | Fix the page or asset error shown by the development server, then rerun `publish`. |

The checked-in [sample content](https://github.com/PureStackStudio/PureStack/tree/main/packages/ts-ssg/sample-content) is a larger working site. PureStack Studio adds its own skin, components, styles, and templates through a [plugin](https://github.com/PureStackStudio/PureStack/blob/main/frontend/studioPlugin.ts) listed in its [`purestack.config.ts`](https://github.com/PureStackStudio/PureStack/blob/main/frontend/purestack.studio/purestack.config.ts); `yarn frontend` runs `purestack serve` on that site.
6 changes: 1 addition & 5 deletions frontend/purestack.studio/guides/publishing/_nav.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{
"sequence": [
"index.mdx",
"search-and-metadata.mdx",
"deployment.mdx"
]
"sequence": ["index.mdx", "search-and-metadata.mdx", "deployment.mdx"]
}
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "purestack-repo",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.18.1",
"type": "module",
"sideEffects": false,
Expand All @@ -25,9 +25,8 @@
"tsx": "^4.23.15",
"typescript": "7.0.2",
"typescript-parser": "npm:typescript@6.0.3",
"vite": "^8.3.1",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.18"
"vite": "^8.3.2",
"vitest": "^5.0.3"
},
"files": [],
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/purestack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purestack",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/purestack.mjs",
"types": "./dist/purestack.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/purestack/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const version: string = '1.1.3'
export const version: string = '1.1.4'

export * from '@purestack/ts-ssg'
2 changes: 1 addition & 1 deletion packages/ts-common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-common",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-common.mjs",
"types": "./dist/ts-common.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-components",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-components.mjs",
"types": "./dist/ts-components.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-css/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-css",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-css.mjs",
"types": "./dist/ts-css.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-html/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-html",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-html.mjs",
"types": "./dist/ts-html.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-minidom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-minidom",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-minidom.mjs",
"types": "./dist/ts-minidom.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-page-scripts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-page-scripts",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-page-scripts.mjs",
"types": "./dist/ts-page-scripts.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-render/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-render",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-render.mjs",
"types": "./dist/ts-render.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-ssg-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "purestack-component-navigation",
"displayName": "PureStack Component Tools",
"description": "Component navigation, prop IntelliSense, lit-html syntax highlighting, and template formatting for Markdown, Regor MDX, and TypeScript templates.",
"version": "1.1.3",
"version": "1.1.4",
"private": true,
"publisher": "purestack",
"license": "MIT",
Expand Down
12 changes: 8 additions & 4 deletions packages/ts-ssg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Flags:
- `--host 127.0.0.1` or `--host=127.0.0.1` (`serve`)
- `--no-watch` (`serve`)
- `--no-reload` (`serve`)
- `--full-render` (`serve`; render all pages and build the complete search index once at startup)

When the content directory holds a `purestack.config.ts`, every command loads its plugins; `serve` reloads it when it or a local file it imports changes. See [Plugins](#plugins).

Expand All @@ -80,6 +81,7 @@ Examples from this monorepo:
```bash
yarn tsx packages/ts-ssg/src/cli.ts build --content ./packages/ts-ssg/sample-content
yarn tsx packages/ts-ssg/src/cli.ts serve --content ./packages/ts-ssg/sample-content --port 4173
yarn frontend --clean --full-render
yarn tsx packages/ts-ssg/src/cli.ts publish --content ./packages/ts-ssg/sample-content
```

Expand Down Expand Up @@ -385,7 +387,7 @@ Each full build also runs, in order:
- `onStylesWritten(context, result)`
- `onBuildComplete(context, result)`

The dev server runs a full build when it starts, when `siteConfig.json` changes, and when `purestack.config.ts` or a file it imports changes; other edits re-render only the affected pages.
By default, the dev server prepares routes and shared navigation when it starts. Pages render on their first request and are cached until an edit affects them; static assets copy on request. Site or plugin config changes prepare a fresh request cache. Page hooks run only for pages that are requested; `onBuildComplete` belongs to full builds. With `--full-render` (or `startDevServer({ fullRender: true })`), startup runs one full build, including all pages, assets, and the complete search index. Site requests wait for that initial build to finish. After startup, watched changes use the same incremental invalidation and rendering on request as the default mode.

## Templates

Expand Down Expand Up @@ -457,10 +459,10 @@ In dev/watch mode:

- file changes apply incrementally when safe,
- a page affected by a shared change, such as a header, footer, or navigation edit, renders again on its next request, before it is served,
- site config changes trigger full rebuild,
- a change to `purestack.config.ts` or a local file it imports loads the config again and triggers a full rebuild,
- site config changes refresh shared state and invalidate cached pages,
- a change to `purestack.config.ts` or a local file it imports loads the config again and invalidates cached pages,
- plugin generated pages regenerate when content or assets change,
- lazy route render can happen on first request for missing HTML route,
- pages render only when requested, including after file edits,
- live reload is served over SSE (`/__ts-ssg/events`).
- dev server enables `writeErrorPages` automatically so template/MDX errors are visible immediately at the failing route.

Expand All @@ -479,6 +481,8 @@ Optional config:

When `basePath` is configured, the search runtime loads Pagefind from the public mount path while Pagefind indexing still reads the normal output directory.

In the default dev server mode, requesting a Pagefind asset builds the search index from pages rendered in the current session. Unopened pages are not compiled for search. Full builds index the complete site; `serve --full-render` starts with that complete index, then handles watched changes incrementally.

When disabled, stale `<outDir>/pagefind` output is removed. Build logs include indexed page count and total indexed byte size when indexing runs.

### Sitemap / Robots
Expand Down
2 changes: 1 addition & 1 deletion packages/ts-ssg/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-ssg",
"version": "1.1.3",
"version": "1.1.4",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-ssg.mjs",
"types": "./dist/ts-ssg.d.mts",
Expand Down
40 changes: 38 additions & 2 deletions packages/ts-ssg/src/build/incremental/change-applier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface IncrementalChangeApplierInput {
contentState: IncrementalContentState
scriptEntrypoints: ScriptEntrypointManager
persistManifest: () => Promise<void>
renderOnRequest: () => boolean
}

export class IncrementalChangeApplier {
Expand All @@ -56,7 +57,10 @@ export class IncrementalChangeApplier {
) {
const state = await this.readChangeState(filePath, relPath, result)
await this.applyChangeState(state)
if (isContentFile(relPath, state.ext) || state.ext === '.ts') {
if (
!this.input.renderOnRequest() &&
(isContentFile(relPath, state.ext) || state.ext === '.ts')
) {
await this.input.scriptEntrypoints.syncState({
result,
persist: true,
Expand Down Expand Up @@ -178,6 +182,16 @@ export class IncrementalChangeApplier {
assetEntry: AssetManifestEntry | undefined
}) {
const { relPath, ext, result, contentEntry, assetEntry } = input
if (this.input.renderOnRequest() && isContentFile(relPath, ext)) {
await this.input.contentState.refreshNavigation()
await this.input.contentState.removeContentEntryForDeletedSource(
relPath,
result,
)
this.input.scriptEntrypoints.removePage(relPath)
await this.input.persistManifest()
return
}
if (contentEntry && this.input.config.navigation.mode !== 'none') {
await this.rebuildNavigationForChange(relPath, ext, result, null)
this.input.scriptEntrypoints.removePage(relPath)
Expand Down Expand Up @@ -215,6 +229,17 @@ export class IncrementalChangeApplier {
result: IncrementalBuildResult
}) {
const { relPath, ext, signature, contentEntry, result } = input
if (this.input.renderOnRequest()) {
// A watcher event is an invalidation even if a full startup build
// recorded the new signature after rendering the old source.
if (this.input.config.navigation.mode !== 'none') {
await this.input.contentState.refreshNavigation()
} else {
await this.input.contentState.refreshContent()
}
this.input.contentState.markPagesDirty([relPath])
return
}
if (signatureEqual(contentEntry, signature)) return

if (this.input.config.navigation.mode !== 'none') {
Expand All @@ -241,7 +266,8 @@ export class IncrementalChangeApplier {
result: IncrementalBuildResult
}) {
const { relPath, ext, signature, assetEntry, result } = input
if (signatureEqual(assetEntry, signature)) return
if (!this.input.renderOnRequest() && signatureEqual(assetEntry, signature))
return

if (ext === '.ts') {
await this.handleScriptAssetChange(relPath, result)
Expand Down Expand Up @@ -277,6 +303,16 @@ export class IncrementalChangeApplier {
this.input.scriptEntrypoints.resolveImpactedEntryRelPaths(relPath)
if (impactedEntries.size === 0) return

if (this.input.renderOnRequest()) {
const pages =
this.input.scriptEntrypoints.getPageRelPathsForEntrypoints(
impactedEntries,
)
this.input.scriptEntrypoints.invalidateEntrypoints(impactedEntries)
this.input.contentState.markPagesDirty(pages)
return
}

const rebuiltEntries =
await this.input.scriptEntrypoints.rebuildEntrypoints(
impactedEntries,
Expand Down
Loading
Loading