Skip to content

feat(client): publish a single-file browser bundle as ./bundle - #8

Merged
sunib merged 1 commit into
mainfrom
feat/single-file-bundle
Jul 14, 2026
Merged

feat(client): publish a single-file browser bundle as ./bundle#8
sunib merged 1 commit into
mainfrom
feat/single-file-bundle

Conversation

@sunib

@sunib sunib commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Closes the first-implementer ask from gitops-api: vendoring the client currently means copying all eleven files of dist/, because index.js imports ./store.js imports ./merge.js and the browser resolves that graph at runtime.

What ships

dist/index.js          unchanged — per-module, tree-shakeable, for bundler-based consumers
dist/krm-stream.js     new — same public API, one file, no relative imports
"exports": {
  ".":        { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
  "./bundle": { "types": "./dist/index.d.ts", "default": "./dist/krm-stream.js" }
}

A host with no bundler vendors one file and serves it. Both builds stay published.

Done-when, checked

  • npm pack contains dist/krm-stream.js (28.5 kB).
  • Copied that file alone into an empty directory and imported it: all eight required exports present (LiveResourceStore, applyStreamEvent, StreamSequence, connectWithEventSource, connectResourceStream, resourceStreamURL, pathKey, defaultPolicy), 21 exports total, zero relative imports remaining.
  • The browser suite runs twice, once per entry point — chromium and chromium-bundle projects, same assertions, real EventSource. 12/12 pass. If a flatten ever drops an export or a polyfill creeps in, the bundle project goes red on its own while the module project stays green.
  • 72 unit tests, lint and typecheck pass.

On the constraints you named

The fourth devDependency. esbuild, with the same one-line justification the other three carry: it runs once per release here so that every no-bundler consumer does not each reinvent the step; it emits no polyfill and no runtime dependency; --target=es2022 pins it to exactly what tsc already emits, so the bundle cannot quietly acquire a polyfill the per-module build lacks. It bundles dist/index.js rather than src/, so the file a bundler-based consumer imports is the file this flattens — one source of truth.

Source maps. Not emitted. A host serving the file publicly should not have to also serve a .map, or field a console warning for not doing so.

The example page. ?entry=bundle selects the entry via dynamic import, so one page proves both. Two demo pages would mean the second one drifts.

Unrelated fix riding along

The package README still told npm readers the project "has not been published to npm yet". That sentence is live on npm right now. Removed. (It wraps across two lines, which is how it survived an earlier grep for it.)

Not in this PR

The onChange gap you raised is real and slightly wider than reported — applyStreamEvent collapses ApplyResult{added, structural, flashed, conflicts} down to flashed alone and drops the uid, so a host cannot highlight per-resource, tell an arrival from a change, or know it must rebuild rows rather than re-read values. It is a public-API change and is being handled separately.

🤖 Generated with Claude Code

gitops-api embeds the client rather than bundling it: the Go server
go:embeds the published ESM and serves it, with no npm, no node_modules
and no bundler anywhere in that build. That is the constraint the library
was designed around, and it works — but vendoring means copying all eleven
files of dist/, because index.js imports ./store.js imports ./merge.js and
the browser resolves that graph at runtime.

Publish the graph flattened as well:

  dist/index.js       unchanged, per-module, tree-shakeable
  dist/krm-stream.js  same public API, one file, no relative imports

addressable as @configbutler/krm-stream/bundle. A host with no bundler now
vendors one file instead of eleven.

esbuild is the fourth devDependency, and it carries the same one-line
justification as the other three: it runs once per release here so that
every no-bundler consumer does not each reinvent the step, it emits no
polyfill and no runtime dependency, and no sourcemap — a host serving the
file publicly should not have to serve a .map or field a console warning
for not doing so.

The browser suite now runs twice, once per entry point (chromium and
chromium-bundle projects), against a real EventSource. "The bundle exports
the same things" is a claim about a browser, not about a build log — if a
flatten ever drops an export, the bundle project goes red on its own.

Also fixes the package README, which still told npm readers the package
"has not been published to npm yet".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sunib, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4160829d-b212-43ba-b222-cc6671c8e19e

📥 Commits

Reviewing files that changed from the base of the PR and between d5d686c and a3d4afe.

⛔ Files ignored due to path filters (1)
  • packages/krm-stream/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • Taskfile.yml
  • examples/vanilla-browser/index.html
  • examples/vanilla-browser/playwright.config.ts
  • examples/vanilla-browser/tests/fixtures.ts
  • examples/vanilla-browser/tests/live-krm.spec.ts
  • packages/krm-stream/README.md
  • packages/krm-stream/package.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/single-file-bundle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sunib
sunib merged commit 5bbcdae into main Jul 14, 2026
8 checks passed
@sunib
sunib deleted the feat/single-file-bundle branch July 14, 2026 09:51
@github-actions github-actions Bot mentioned this pull request Jul 14, 2026
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.

1 participant