@timber-js/app 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
package/src/cli.ts CHANGED
@@ -54,12 +54,19 @@ export function parseArgs(args: string[]): ParsedArgs {
54
54
 
55
55
  // ─── Command Implementations ─────────────────────────────────────────────────
56
56
 
57
+ /** @internal Dependency injection for testing. */
58
+ export interface ViteDeps {
59
+ createServer?: typeof import('vite').createServer;
60
+ createBuilder?: typeof import('vite').createBuilder;
61
+ preview?: typeof import('vite').preview;
62
+ }
63
+
57
64
  /**
58
65
  * Start the Vite dev server.
59
66
  * Middleware re-runs on file change via HMR wiring in timber-routing.
60
67
  */
61
- export async function runDev(options: CommandOptions): Promise<void> {
62
- const { createServer } = await import('vite');
68
+ export async function runDev(options: CommandOptions, _deps?: ViteDeps): Promise<void> {
69
+ const createServer = _deps?.createServer ?? (await import('vite')).createServer;
63
70
  const server = await createServer({
64
71
  configFile: options.config,
65
72
  });
@@ -72,8 +79,8 @@ export async function runDev(options: CommandOptions): Promise<void> {
72
79
  * Direct build() calls do NOT trigger the RSC plugin's multi-environment
73
80
  * pipeline — createBuilder/buildApp is required.
74
81
  */
75
- export async function runBuild(options: CommandOptions): Promise<void> {
76
- const { createBuilder } = await import('vite');
82
+ export async function runBuild(options: CommandOptions, _deps?: ViteDeps): Promise<void> {
83
+ const createBuilder = _deps?.createBuilder ?? (await import('vite')).createBuilder;
77
84
  const builder = await createBuilder({
78
85
  configFile: options.config,
79
86
  });
@@ -123,7 +130,7 @@ async function loadTimberConfig(
123
130
  * If the adapter provides a preview() method, it takes priority.
124
131
  * Otherwise falls back to Vite's built-in preview server.
125
132
  */
126
- export async function runPreview(options: CommandOptions): Promise<void> {
133
+ export async function runPreview(options: CommandOptions, _deps?: ViteDeps): Promise<void> {
127
134
  const { join } = await import('node:path');
128
135
 
129
136
  // Try to load timber config for adapter-specific preview
@@ -139,7 +146,7 @@ export async function runPreview(options: CommandOptions): Promise<void> {
139
146
  }
140
147
 
141
148
  // Fallback: Vite's built-in preview server
142
- const { preview } = await import('vite');
149
+ const preview = _deps?.preview ?? (await import('vite')).preview;
143
150
  const server = await preview({
144
151
  configFile: options.config,
145
152
  });
package/src/index.ts CHANGED
@@ -303,7 +303,7 @@ export function timber(config?: TimberUserConfig): PluginOption[] {
303
303
  // Also loads timber.config.ts and merges it into ctx.config (inline config wins).
304
304
  const rootSync: Plugin = {
305
305
  name: 'timber-root-sync',
306
- async config(userConfig) {
306
+ async config(userConfig, { command }) {
307
307
  // Load timber.config.ts early — before configResolved/buildStart — so
308
308
  // all plugins (including timber-mdx) see the merged config in their
309
309
  // buildStart hooks. The config hook runs once and supports async.
@@ -315,6 +315,29 @@ export function timber(config?: TimberUserConfig): PluginOption[] {
315
315
  ctx.clientJavascript = resolveClientJavascript(ctx.config);
316
316
  }
317
317
  ctx.timer.end('config-load');
318
+
319
+ // Force production JSX transform for builds.
320
+ //
321
+ // Vite determines dev vs prod JSX via `isProduction`, which checks
322
+ // `process.env.NODE_ENV === 'production'`. If the shell has
323
+ // NODE_ENV=development (common in dev toolchains), `vite build`
324
+ // respects that and emits jsxDEV calls with fileName/lineNumber
325
+ // args. This causes runtime crashes because the production React
326
+ // jsx-runtime doesn't export jsxDEV, and also leaks file paths
327
+ // into production bundles (security concern).
328
+ //
329
+ // We explicitly set `oxc.jsx.development: false` for builds so
330
+ // the client bundle always uses jsx/jsxs from react/jsx-runtime,
331
+ // regardless of the ambient NODE_ENV value.
332
+ if (command === 'build') {
333
+ return {
334
+ oxc: {
335
+ jsx: {
336
+ development: false,
337
+ },
338
+ },
339
+ };
340
+ }
318
341
  },
319
342
  configResolved(resolved) {
320
343
  ctx.root = resolved.root;