Skip to content

fix(serve): honor build.outDir from vite.config.ts#703

Open
YevheniiKotyrlo wants to merge 1 commit into
onejs:mainfrom
YevheniiKotyrlo:fix/serve-honor-vite-outdir
Open

fix(serve): honor build.outDir from vite.config.ts#703
YevheniiKotyrlo wants to merge 1 commit into
onejs:mainfrom
YevheniiKotyrlo:fix/serve-honor-vite-outdir

Conversation

@YevheniiKotyrlo
Copy link
Copy Markdown
Contributor

@YevheniiKotyrlo YevheniiKotyrlo commented May 8, 2026

Summary

Fixes #701. one serve now honors build.outDir from vite.config.ts, matching what one build already does at packages/one/src/cli/build.ts:370.

Problem

// vite.config.ts
export default defineConfig({
  build: { outDir: 'build-out' },
  plugins: [one()],
})
one build       # writes to ./build-out/ as expected
one serve       # ENOENT: open './dist/buildInfo.json'  ← bug

packages/one/src/serve.ts:194-195 resolved outDir from CLI args only:

const outDir = args?.outDir || (FSExtra.existsSync('buildInfo.json') ? '.' : null) || 'dist'

The vite config's build.outDir was never consulted, so projects with a non-default outDir had to pass --outDir <dir> explicitly to every one serve invocation.

Fix

Layer the resolution to match the build command:

  1. --outDir CLI flag (highest precedence)
  2. cwd contains buildInfo.json'.' — preserves the "cd into output dir then run" UX even when vite.config sets a non-default outDir
  3. vite.config's build.outDir, read via a new loadViteBuildOutDir() helper in packages/one/src/vite/loadConfig.ts
  4. 'dist' fallback

Why a new helper instead of reusing loadUserOneOptions

The first cut of this PR called the existing loadUserOneOptions('serve', true) wrapper. That throws via getUserOneOptions when globalThis.__oneOptions is unset, and the One plugin only sets __oneOptions on the metro-CLI path (if (process.env.IS_VXRN_CLI)). On the vxrn-as-vite-plugin path (bundler: 'vite' in one() options) the throw fired every time, the catch swallowed it, and the resolution silently fell through to 'dist' — i.e., the first cut was a no-op for Vite-native projects.

The new loadViteBuildOutDir() calls Vite's own loadConfigFromFile directly, reads loaded.config.build.outDir, and never touches __oneOptions. Works for both bundler modes.

The vite.config load is wrapped in try/catch so deploy bundles that ship only dist/ (without vite.config.ts) keep working.

Test plan

Validated end-to-end on Windows MSVC + Bun 1.3.13 against the curex/one downstream consumer:

  • bunx tsc --noEmit clean.
  • Vitest unit test for loadViteBuildOutDir with two fixtures (with-outdir returning 'build-out'; without-outdir returning undefined). Passes 2/2 in 220ms.
  • Positive — vite.config has build: { outDir: 'build-out' }, no dist/, no --outDir: patched serve.mjs started on port 3000, curl http://localhost:3000/ returned HTTP 200. Resolution went through the new vite-config path.
  • Negative — same fixture, unpatched serve.mjs: failed with ENOENT: no such file or directory, open 'C:\…\dist\buildInfo.json' — the exact reported bug. Confirms the fix is load-bearing.
  • --outDir build-out flag wins (highest precedence): server starts on resolved outDir; verified by curl returning 200.
  • cd build-out && one serve UX preserved: server starts via the cwd-buildInfo.json branch.
  • Default project with no custom outDir: falls through to 'dist' correctly (verified by ENOENT against ./dist/buildInfo.json when dist/ is empty — the resolution chain reached the default as designed).

Notes

@YevheniiKotyrlo YevheniiKotyrlo force-pushed the fix/serve-honor-vite-outdir branch from 6ea7422 to 39bdca0 Compare May 8, 2026 21:52
`one serve` was resolving outDir from CLI args only, falling through to
the literal 'dist' string when no --outDir flag was passed. Projects
that set a custom `build.outDir` in vite.config.ts (e.g. 'build-out')
would build correctly via `one build` — which loads the vite config and
already respects `build.outDir` at cli/build.ts:370 — but `one serve`
would then crash with ENOENT looking for `./dist/buildInfo.json`
instead of `./build-out/buildInfo.json`.

Layer the resolution chain in serve.ts:startWorker to match the build
command:

  1. --outDir CLI flag (highest precedence)
  2. cwd has buildInfo.json — preserves the "cd into output dir then
     run" UX even when vite.config sets a non-default outDir
  3. vite.config's build.outDir — read via a new
     `loadViteBuildOutDir()` helper in `vite/loadConfig.ts`
  4. 'dist' fallback

Step 3 uses Vite's own `loadConfigFromFile` directly rather than the
existing `loadUserOneOptions` wrapper. The wrapper calls
`getUserOneOptions()` which throws when `globalThis.__oneOptions` is
unset, and the One plugin only sets that on the metro-CLI path
(`if (process.env.IS_VXRN_CLI)`). On the vxrn-as-vite-plugin path
(`bundler: 'vite'` in one()) the throw fires every time, the catch
swallows it, and the fallback to 'dist' happens — i.e., the wrapper
silently does nothing for projects that use Vite-native bundling.

The new helper bypasses that by calling `loadConfigFromFile` directly
and reading `loaded.config.build.outDir` from the resolved config —
no `__oneOptions` handshake needed.

The vite.config load is wrapped in try/catch so deploy bundles that
ship only `dist/` (without a vite.config.ts) keep working.

Includes a vitest unit test for `loadViteBuildOutDir` with two
fixtures (with-outdir / without-outdir) covering both branches.
The helper accepts an optional `configRoot` argument so tests can
target a fixture directory without `chdir`-ing the worker.

Validated end-to-end on Windows MSVC + Bun 1.3.13:
  - vite.config has `build: { outDir: 'build-out' }`, no dist/, no
    --outDir flag → patched serve resolves to 'build-out',
    `curl http://localhost:3000/` returns HTTP 200.
  - Same setup with the unpatched serve.mjs → ENOENT for
    `dist\buildInfo.json` (reproduces the issue).
  - --outDir flag override path: server starts on resolved outDir.
  - cwd-has-buildInfo path: server starts using cwd as outDir.
  - default fallback: resolves to 'dist' as before this PR.
@YevheniiKotyrlo YevheniiKotyrlo force-pushed the fix/serve-honor-vite-outdir branch from 39bdca0 to 9395993 Compare May 9, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(serve): one serve does not honor build.outDir from vite.config.ts

1 participant