Skip to content

Commit 18f6043

Browse files
fix: production build — convert require() to await import() in caching/runtime-injection
- getCachedSignalsRuntime and getCachedRouterScript now async (await import) - injectSignalsRuntime and injectRouterScript now async - All callers updated to await: signal-processing, serve-app, plugin, render - Fix layout file routing — exclude layouts/, components/, partials/ from page discovery - Tested: training app production build — 17 pages, 0 errors, 142ms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2a85122 commit 18f6043

7 files changed

Lines changed: 24 additions & 16 deletions

File tree

packages/bun-plugin/src/serve.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,18 @@ export async function serve(options: ServeOptions): Promise<void> {
8989
const stat = await fs.stat(pattern).catch(() => null)
9090

9191
if (stat?.isDirectory()) {
92+
// Directories to exclude from page routing — these contain non-page .stx files
93+
const excludeDirs = ['layouts', 'components', 'partials']
94+
if (layoutsDir) excludeDirs.push(layoutsDir.replace(/^.*\//, ''))
95+
if (componentsDir) excludeDirs.push(componentsDir.replace(/^.*\//, ''))
96+
if (partialsDir) excludeDirs.push(partialsDir.replace(/^.*\//, ''))
97+
9298
for (const ext of ['.stx', '.md', '.html']) {
9399
const glob = new Glob(`**/*${ext}`)
94100
const discovered = await Array.fromAsync(glob.scan({ cwd: pattern, followSymlinks: true }))
95-
files.push(...discovered.map(f => `${pattern}/${f}`.replace(/\/+/g, '/')))
101+
files.push(...discovered
102+
.filter(f => !excludeDirs.some(dir => f.startsWith(dir + '/')))
103+
.map(f => `${pattern}/${f}`.replace(/\/+/g, '/')))
96104
}
97105
}
98106
else if (pattern.includes('*')) {

packages/stx/src/caching.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ let _cachedSignalsRuntimeDev: string | null = null
8080
* Get the signals runtime with module-level caching.
8181
* The runtime is identical for every page and never changes during a dev session.
8282
*/
83-
export function getCachedSignalsRuntime(debug = false): string {
83+
export async function getCachedSignalsRuntime(debug = false): Promise<string> {
8484
if (debug) {
8585
// In debug mode, always regenerate to avoid stale runtime issues
86-
const { generateSignalsRuntimeDev } = require('./signals')
86+
const { generateSignalsRuntimeDev } = await import('./signals')
8787
return generateSignalsRuntimeDev()
8888
}
8989
if (_cachedSignalsRuntime === null) {
90-
const { generateSignalsRuntime } = require('./signals')
90+
const { generateSignalsRuntime } = await import('./signals')
9191
_cachedSignalsRuntime = generateSignalsRuntime()
9292
}
9393
return _cachedSignalsRuntime!
@@ -101,9 +101,9 @@ let _cachedRouterScript: string | null = null
101101
/**
102102
* Get the router script with module-level caching.
103103
*/
104-
export function getCachedRouterScript(): string {
104+
export async function getCachedRouterScript(): Promise<string> {
105105
if (_cachedRouterScript === null) {
106-
const { getRouterScript } = require('stx-router')
106+
const { getRouterScript } = await import('stx-router')
107107
_cachedRouterScript = getRouterScript()
108108
}
109109
return _cachedRouterScript!

packages/stx/src/dev-server/serve-app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ export async function serveApp(appDir: string = '.', options: DevServerOptions =
312312

313313
// Inject SPA router (skip when using shell — router goes in shell composition)
314314
if (!shell) {
315-
output = injectRouterScript(output)
315+
output = await injectRouterScript(output)
316316
}
317317

318318
// Re-inject client scripts before </body>

packages/stx/src/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export const plugin: BunPlugin = {
181181
}
182182

183183
// Inject SPA router for full-page templates (has </body> guard built in)
184-
output = injectRouterScript(output)
184+
output = await injectRouterScript(output)
185185

186186
// Track dependencies for this file
187187
dependencies.forEach(dep => allDependencies.add(dep))

packages/stx/src/render.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ async function renderTemplateString(
349349
}
350350

351351
// Inject SPA router script for client-side navigation
352-
output = injectRouterScript(output)
352+
output = await injectRouterScript(output)
353353

354354
// Optionally inject Crosswind CSS from Tailwind utility classes
355355
if (renderOptions.injectCSS) {

packages/stx/src/runtime-injection.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ import '@stacksjs/browser'
106106
* Inject STX signals runtime into the template.
107107
* The runtime provides client-side reactivity.
108108
*/
109-
export function injectSignalsRuntime(template: string, options: StxOptions): string {
109+
export async function injectSignalsRuntime(template: string, options: StxOptions): Promise<string> {
110110
// Don't inject if actual signals runtime is already present
111111
// Check for the runtime's unique signature (state, derived, effect assignment),
112112
// not just any 'window.stx =' which could be scope registration from includes
@@ -130,8 +130,8 @@ export function injectSignalsRuntime(template: string, options: StxOptions): str
130130
}
131131

132132
// Use cached runtime (identical for every page, never changes during dev session)
133-
const { getCachedSignalsRuntime } = require('./caching')
134-
const runtime = getCachedSignalsRuntime(options.debug)
133+
const { getCachedSignalsRuntime } = await import('./caching')
134+
const runtime = await getCachedSignalsRuntime(options.debug)
135135
const runtimeScript = `<script data-stx-scoped>${runtime}</script>`
136136

137137
// Inject before the first <script in the ENTIRE document, not just <head>.
@@ -163,7 +163,7 @@ export function injectSignalsRuntime(template: string, options: StxOptions): str
163163
* The router is provided by the canonical router in packages/router/src/client.ts.
164164
* It guards against double-initialization so it's safe to inject alongside @stxRouter.
165165
*/
166-
export function injectRouterScript(template: string, options?: StxOptions): string {
166+
export async function injectRouterScript(template: string, options?: StxOptions): Promise<string> {
167167
// Only inject into full HTML pages (not template fragments or components)
168168
if (!template.includes('</body>')) {
169169
return template
@@ -190,10 +190,10 @@ export function injectRouterScript(template: string, options?: StxOptions): stri
190190
}
191191

192192
// Build mode: emit a placeholder reference instead of inlining the full router script.
193-
const { getCachedRouterScript } = require('./caching')
193+
const { getCachedRouterScript } = await import('./caching')
194194
const routerScript = options?.buildMode === 'compile'
195195
? `<script src="/__stx/router.__STX_HASH__.js"></script>`
196-
: `<script>${getCachedRouterScript()}</script>`
196+
: `<script>${await getCachedRouterScript()}</script>`
197197

198198
// Use string concatenation to avoid $-interpretation in .replace()
199199
const bodyCloseIdx = template.lastIndexOf('</body>')

packages/stx/src/signal-processing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ export async function processSignals(template: string, options: StxOptions, file
535535
output = processedOutput
536536

537537
// Inject the signals runtime
538-
output = injectSignalsRuntime(output, options)
538+
output = await injectSignalsRuntime(output, options)
539539

540540
// Inject browser runtime if needed (for auto-imports from @stacksjs/browser)
541541
output = injectBrowserRuntime(output)

0 commit comments

Comments
 (0)