|
|
||
|---|---|---|
| scripts | ||
| src | ||
| template | ||
| tests | ||
| .gitignore | ||
| CHANGELOG.md | ||
| package.json | ||
| README.md | ||
| tsconfig.build.json | ||
| tsconfig.json | ||
Introduction
A template for single-page applications distributed as Hono sub-app libraries. The generated package bundles a frontend shell (HTML, JS, CSS, PWA manifest, favicon) as static assets and exposes a createSpa() function that returns a Hono instance serving them. Intended to be consumed by a @temir.ra/create-hono-server backend, where it is mounted as a route group.
Table of Contents
Quick Start
bun create caches the template package - a newer published version will not be picked up automatically. Pin the version or clear the cache to use the latest.
# placeholder:
# <NEW_PACKAGE: <NEW_PACKAGE>
# <@_VERSION: <@_VERSION>
# identify the latest version of the template package as <@_VERSION.
bun info "@temir.ra/create-hono-spa" version
# create a new library from the template version
bun create --no-install --no-git "@temir.ra/hono-spa<@_VERSION>" <NEW_PACKAGE>
# or
# clear package manager cache to ensure the latest template version is used
bun pm cache rm
# create a new library from the latest template version
bun create --no-install --no-git "@temir.ra/hono-spa" <NEW_PACKAGE>
# dependencies must be installed manually
cd <NEW_PACKAGE>
bun install
AI Assistant Context
To generate an AI coding assistant context file for this project:
Generate an AI coding assistant context file. Analyze the project structure and source files, using this README as the primary reference for architecture and conventions. Give particular attention to: the SPA-as-library abstraction and how assets are served via import.meta.url without any file copying on the consuming server, the
./spaexport condition and createSpa() as the library's public API, and the base href patching mechanism that enables client-side routing regardless of mount path.
Documentation
The following sections explain the configurations and conventions baked into the generated package.
template/
The files in template/ were created from the @temir.ra/ts-lib@0.5.0 template and then updated to fit the needs of a Hono SPA template.
bun create --no-install --no-git --force "@temir.ra/ts-lib@0.5.0" "template/"
Official documentation provided by the Hono team is a great resource.
Architecture
The generated SPA package is a library - not a runnable server. It abstracts three things from the consuming server:
Asset serving - All frontend assets (HTML shell, JS bundle, CSS, PWA manifest, favicon) are resolved from the package directory via import.meta.url and served by the sub-app itself. The consuming server does not configure static file serving or copy any files.
Client-side routing - The /app/* wildcard returns index.html for every navigation path. The <base href> tag is patched at request time from basePath and path, so relative asset URLs resolve correctly wherever the sub-app is mounted.
PWA scaffold - The assets/ directory ships with the structure expected by browsers for PWA installation: manifest, favicon, and screenshots.
The consuming server treats the package as an opaque route group passed to endpointGroups. See @temir.ra/create-hono-server for the mount pattern.
src/spa.ts
Built-in routes:
| Path | Description |
|---|---|
/health |
Returns ok with status 200 |
/buildinfo |
Returns the contents of buildinfo.txt |
/app/* |
Serves index.html with runtime substitutions applied |
/<faviconPath> |
Serves favicon.svg as image/svg+xml |
/<webmanifestPath> |
Serves manifest.webmanifest as application/manifest+json |
/<indexJsPath> |
Serves dist/index.bundle.js |
/<indexCssPath> |
Serves dist/index.css |
/<siteCssPath> |
Serves dist/site.css |
Options:
type CreateSpaOptions = {
basePath?: string; // outer base path matching the server host's basePath
path?: string; // sub-path for this SPA within the server
metaDescriptionContent?: string; // replaces content="" in the description meta tag
faviconPath?: string; // serve path for the favicon (default: 'favicon.svg')
webmanifestPath?: string; // default: 'manifest.webmanifest'
vAppContainerId?: string; // id attribute of the root app container div
indexJsPath?: string; // default: 'index.js'
indexCssPath?: string; // default: 'index.css'
siteCssPath?: string; // default: 'site.css'
}
src/constants.ts
export const packageUrl = new URL('../', import.meta.url);
export const buildinfoUrl = new URL('buildinfo.txt', packageUrl);
export const distUrl = new URL('dist/', packageUrl);
export const indexJsUrl = new URL('index.bundle.js', distUrl);
export const assetsUrl = new URL('assets/@scope/package-name/', packageUrl);
export const indexHtmlUrl = new URL('index.html', assetsUrl);
// ...
Replace @scope/package-name with your actual package name when adapting the template. Update constants.ts and rename the assets/@scope/package-name/ directory to match.
assets/
The template provides a PWA shell scaffold:
assets/
└── @scope/
└── package-name/
├── index.html ← SPA entry point; base href patched at request time
├── favicon.svg
├── manifest.webmanifest
└── images/
├── mobile-screenshot.png ← used in manifest for PWA install UI
└── wide-screenshot.png
assets/ is included in the files field of package.json and published with the package. The naming convention (assets/@scope/package-name/) follows the asset resolution contract from @temir.ra/create-ts-lib - scoped directories prevent naming collisions when multiple SPA packages coexist in the same server.
DevOps
# remove dist/ and tsconfig.build.tsbuildinfo
bun run clean
# remove dist/ only
bun run clean:dist
# remove tsconfig.build.tsbuildinfo only
bun run clean:tsbuildinfo
# compile + bundle
bun run build
# create a new test hono spa in example/
bun run dist/cli.bundle.js -- example
Publish - see Publish.
Change Management
- Create a new branch for the change.
- Make the changes and commit.
- Bump the version in
package.json. - Add an entry for the new version in
CHANGELOG.md. - Pull request the branch.
- After merge, run
bun run build- ensures artifacts are current before publish. - Publish.
Publish
See the following sources to configure the target registry and authentication.
⚠️ Package Scope and the authentication for the target registry must be aligned.
npmjs.org
Publish to the public npm registry.
# authenticate
npm login
# publish
bun publish --registry https://registry.npmjs.org/ --access public
Custom registry
# placeholder:
# <SCOPE_WITHOUT_AT: <SCOPE_WITHOUT_AT>
# <REGISTRY_URL: <REGISTRY_URL>
# <BUN_PUBLISH_AUTH_TOKEN: <BUN_PUBLISH_AUTH_TOKEN>
~/.bunfig.toml or bunfig.toml:
[install.scopes]
"<SCOPE_WITHOUT_AT>" = { url = "<REGISTRY_URL>", token = "$BUN_PUBLISH_AUTH_TOKEN" }
# authenticate
$env:BUN_PUBLISH_AUTH_TOKEN = "<BUN_PUBLISH_AUTH_TOKEN>"
# publish
bun publish