Skip to content

Exclude server-only @webjsdev/core code from the browser bundle #119

Description

@vivek7405

Problem

@webjsdev/core/index.js re-exports renderToString and renderToStream from ./src/render-server.js. The SSR pipeline imports them on the server; nothing on the client ever calls them. But because they sit on the root export, every browser page that imports @webjsdev/core for html / WebComponent also pulls them in:

This is orthogonal to #112 (display-only component elision). #112 elides USER components that have no client work. This issue is about the framework's OWN surface: server-only code on the public root export.

Design / approach

Options worth comparing in this work:

  1. Separate browser entry: emit dist/webjs-core-browser.js that excludes render-server and any other server-only modules. Use the browser condition in exports so Node-side imports still get the full surface but the importmap (browser side) points at the browser-only entry.
  2. Conditional exports with {browser, default}: simpler than option 1; same dist file but conditionally aliased.
  3. Side-effect annotations + esbuild dead-code elimination: mark render-server.js's exports as side-effect-free and rely on esbuild to strip them when the bundle for the browser entry never references them. Requires confirming esbuild's tree-shaking actually drops them from a multi-entry split.
  4. Two index files: index.js (universal, current behaviour) + index-browser.js (browser-only). The browser entry skips the render-server re-exports. Mirror with exports.browser condition.

The investigation is the work: measure the byte savings for each option, pick the simplest that delivers the savings, and verify the bundled-mode importmap routes to the right entry.

Audit list (start here when picking the issue up)

Server-only modules currently inside the public root export of @webjsdev/core:

  • src/render-server.js: renderToString, renderToStream (and possibly injectDSD).
  • Any helpers that are imported only by render-server.js and not by client-side code (might tree-shake naturally if marked side-effect-free).
  • Inspect src/csp-nonce.js. setCspNonceProvider is server-side configuration; reading cspNonce() is client.
  • Inspect src/escape.js. Used on both sides; KEEP.

Run a grep across src/render-client.js, src/component.js, and the directives bundle to confirm what's actually called on the client; everything else on the root export is candidate dead weight.

Acceptance criteria

  • Browser bundle for @webjsdev/core drops renderToString, renderToStream, and any other strictly server-only exports.
  • Node-side imports (SSR pipeline, framework internals) still get the full surface via the default / node / import conditions.
  • importmap routing (workspace + npm-installed modes) points at the browser entry, not the universal one.
  • Measured byte savings reported in the PR body (bundle size diff before/after, gzipped).
  • Tests pin: SSR still renders correctly via the Node entry; client-side import { html, WebComponent } from '@webjsdev/core' works without referencing the stripped exports.
  • AGENTS.md updated if the public-surface contract changes (e.g. clear note that renderToString is server-only and not part of the browser bundle).

Context

See PR #117 for the bundled-mode pipeline this builds on, and #112 for the orthogonal user-component elision work. This issue concerns only @webjsdev/core's own root surface; it does not depend on or block #112.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions