kazupon / kazupon/vrowzer

Add vrowzer.build() for browser-side production builds

Open
#36 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
TypeScript
Stars
12
Forks
0
Avg merge
34m
Merged PRs (30d)
19

Description

## Summary

Add a runtime `vrowzer.build()` API that produces deployable production artifacts from the project files supplied to `ready()` and subsequently changed through `addFile()`, `updateFile()`, and `deleteFile()`.

This is a browser-side project build, not the host application's `vp run build`, prebundling Vrowzer's Service Worker, or the existing `test:e2e:build` workflow. The output should run on ordinary static hosting without Vrowzer's Service Worker or dev transform RPC.

## Agreed direction

- Start with **Vanilla JS applications only**. Vue, React, Svelte, and other framework build support are follow-up work, not initial completion requirements.
- Prepare a **dedicated, persistent build Web Worker for each runtime Vrowzer object** returned by `Vrowzer()`. This is separate from the dev-server Worker and is not one Worker per preview session or per build invocation.
- Load configuration and the build engine in advance where possible. For each `build()` call, send the input files to the build Worker, generate a new bundle, and send the result back to the main thread.
- Keep the build Worker and initialized runtime alive between normal builds. Create and close the bundler instance for each build.

The detailed options, return types, initialization timing, disposal API, and recovery behavior below are proposals, not finalized API specifications. Persistent Worker builds have not yet been implemented or validated end to end.

## Proposed initial scope

- A single HTML entry, defaulting to `/index.html`, with a configurable static HTML path, including nested entries. Emit browser ESM with static imports and string-literal dynamic imports/code splitting.
- JS/TS/JSX/TSX/JSON processing. Test syntax transforms with framework-independent fixtures; do not make a framework runtime part of the initial requirements. No type checking or declaration generation.
- HTML processing, plain CSS, CSS Modules, CSS imports/URLs, and CSS associated with asynchronously loaded JS chunks.
- Text and binary assets, static asset imports, `?raw`/`?url`, literal `new URL(..., import.meta.url)`, inlined/emitted assets, and `/public` files.
- Production configuration, `define`, `import.meta.env`, and production-compatible dependencies, including a small framework-independent CJS fixture.
- Root/path/relative output bases (`/`, `/app/`, `./` or empty), diagnostics, and representative JS sourcemap cases.
- Supported tsconfig settings and explicitly supplied `.env*` files from the virtual project. Respect client env prefixes; do not automatically collect host secrets or copy env files into the output.

Proposed exclusions are SSR, library/multi-environment/multi-entry builds, JS-only entries, watch/incremental builds, project Workers/SharedWorkers/WASM imports, `import.meta.glob`, and variable dynamic imports. Also defer CSS preprocessors/Lightning CSS/external PostCSS configuration, Terser/esbuild minification, manifests/license reports/chunk import maps, arbitrary output formats, ZIP/download UI, and a built-artifact preview API.

Initially use `cssMinify: false` explicitly and Rolldown/OXC for supported JS minification. This intentionally differs from Vite's default CSS compression. Unsupported settings or syntax must not silently produce invalid output; this does not require statically identifying every possible third-party plugin.

## Execution and state management

```text
Preparation:
Main thread -> start dedicated build Worker
-> load configuration source and initialize bundler/WASM
-> signal readiness and wait

Each build():
Main thread -> send current project snapshot and options
-> replace project files in the build Worker's virtual FS
-> resolve input-dependent settings and initialize build plugin state
-> create BuildEnvironment and rolldown(...) bundle
-> generate all chunks/assets
-> normalize results, close the bundle, clean up per-build data
-> return results to the main thread
-> wait for the next request in the same Worker
```

- Capture the input before the first asynchronous operation, including any wait for Worker preparation. Edits during a build affect the preview and the next build, not the running build.
- Initially send a complete snapshot for each build. Remove files absent from the new snapshot; do not leave deleted source files, packages, public files, or previous artifacts in the build FS. Do not export the dev FS wholesale or include Vrowzer's injected runtime/cache files.
- Preserve binary bytes and caller-owned buffers when cloning/transferring data. Collect every output file rather than just the first JS chunk.
- Keep dev configuration, plugins, FS, and HMR separate. Within the persistent build Worker, recreate/reset per-build plugin state, metadata, and input-dependent caches. Reimporting a cached config module or shallow-copying plugin objects is not sufficient.
- Preload static configuration, but resolve project-dependent settings after receiving files. Changed `.env*`, tsconfig, package metadata, base, or mode must not reuse stale resolved configuration.
- Build requests/results use message passing; plugin functions stay inside Worker modules. Reusing the Worker does not remove file-transfer costs or the memory retained while idle.
- Create a new `rolldown(...)` bundle for each build. Repeatedly calling `generate()` on an old bundle is not a rebuild of changed inputs. Close each bundle without terminating a healthy Worker.

## Proposed API behavior

Illustrative usage, not a finalized signature:

```ts
const vrowzer = Vrowzer()
await vrowzer.ready({ files })
const result = await vrowzer.build()
```

- Require successful `ready()`; mounting a preview should not be necessary. Build the project owned by that Vrowzer object, without session IDs, params, or iframe bootstrap code.
- Return a file map such as `Record` plus serializable warnings. Keys are output-root-relative paths. Include HTML, all JS/CSS chunks, assets, public files, and requested sourcemaps; do not expose raw Rust-backed Rolldown output objects.
- Reject output path escapes/collisions. Do not write artifacts into the dev FS or empty its `/dist` directory.
- Allow one active build per Vrowzer object, rejecting overlapping calls rather than queuing them. Resolve only complete results; return structured errors with useful file/plugin/location information.
- Reuse the Worker after a normal build error only when cleanup/reset succeeds. On timeout, fatal Worker failure, or unrecoverable cleanup, terminate only the build Worker and recreate it for the next explicit build request. Do not automatically retry the failed build or terminate the dev Worker.
- Define finite setup/build waits, request/response matching, rejection of late responses, and explicit Worker disposal. The current `unmount()` deliberately keeps shared Workers alive and is not an existing instance disposal API.

For configuration, the current proposal is to reuse the original `workerConfig` source, or the existing extraction result when applicable, rather than a resolved dev config. Do not introduce another host config file or copy the entire host `build` configuration. Consider build-specific `base`/`mode` arguments, defaulting to `/` and `production`, independently of the preview base. Final precedence and supported options still need confirmation.

## Implementation gaps

- Add the public API and main-thread/build-Worker messages, Vite build orchestration, real build plugin context, chunk metadata, and HTML/CSS/asset/import-analysis integration. Existing option types and a bare Rolldown call do not provide this pipeline.
- Remove development-only assumptions from the production path. Runtime/config prebundles and dependency manifests currently bake in development behavior; changing `NODE_ENV` later cannot restore production dependency sources already removed during prebundling. Validate raw CJS inputs versus separate production prebundles while preserving existing manifest and `ready({ files })` behavior.
- Fix the binary initialization/broadcast and manifest text-only limitations identified in the audit. Add current-project input tracking and safe snapshot/output serialization.
- Verify the selected Vite/Rolldown API compatibility and required native-plugin exports. Do not load additional build-only WASM synchronously through Service Worker or dev bootstrap imports.
- Implement Worker preparation, per-build cleanup, recovery, and explicit lifetime management. Measure cold preparation, repeated builds, and idle/peak memory.

Re-enabling the dependency optimizer, fully porting the SSR module runner, or porting Node filesystem watchers are **not prerequisites** for this initial client build.

Relevant existing code at the audit baseline:

- [Runtime Vrowzer API](https://github.com/kazupon/vrowzer/blob/dc4e98ec8ac01d431b0def70acfce512dd7be642/packages/vrowzer/src/index.ts)
- [Current transformer and experimental bundle helper](https://github.com/kazupon/vrowzer/blob/dc4e98ec8ac01d431b0def70acfce512dd7be642/packages/vite-dev-server/src/node/transformer.ts)
- [Partially ported build implementation](https://github.com/kazupon/vrowzer/blob/dc4e98ec8ac01d431b0def70acfce512dd7be642/packages/vite-dev-server/src/node/build.ts)

## Proposed acceptance criteria

- [ ] A Vanilla JS HTML application built through `vrowzer.build()` runs on a separate-origin static server, without Vrowzer SW control, dev RPC, HMR clients, or preview URLs.
- [ ] HTML/CSS/asset references work for root, pathname, relative bases, and a nested HTML entry. Cover CSS Modules, asynchronous CSS, public files, multiple chunks, sourcemaps, and exact binary bytes.
- [ ] Production flags, supplied config/env values, and a framework-independent production dependency fixture are correct; supported plugin hooks and emitted assets work.
- [ ] Add/update/delete operations and edits during a build follow snapshot semantics. Deleted files, previous config, and plugin caches do not leak into subsequent builds. A syntax error can be fixed and rebuilt.
- [ ] Normal repeated builds reuse one dedicated Worker without accumulating bundles, snapshots, listeners, or timers. Timeout recovery and explicit disposal release the appropriate resources.
- [ ] Existing preview/HMR and multiple preview sessions survive successful builds, ordinary failures, and build-Worker termination. Related unit/integration tests and existing `test:e2e`/`test:e2e:build` pass; add separate tests for this runtime API's artifacts.
- [ ] Unsupported behavior is documented and diagnosed where detectable. Existing framework preview/host-build regression tests remain, but framework production builds are not required for this issue.

## Decisions before implementation

Confirm the remaining scope exclusions, when Worker preparation begins and how it relates to `ready()`, the options/result/error types, config precedence, disposal and timeout behavior, and production CJS input representation.

Start with a small feasibility check for persistent Worker builds, FS/plugin reset, production inputs, and memory. Then implement the execution/API foundation, complete the Vanilla HTML/CSS/asset output, and validate the acceptance criteria. Producing one JS chunk or passing the host build alone does not complete this feature.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the feasibility check and read packages/vrowzer/src/index.ts, packages/vite-dev-server/src/node/transformer.ts, and packages/vite-dev-server/src/node/build.ts. Establish persistent Worker builds, snapshot and recovery behavior, then validate Vanilla JS artifacts against the listed acceptance criteria and separate runtime API tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, vite
Domain
api, build-system, devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.