Skip to content

fix(rest): Bundle path-to-regexp into ESM output for StackBlitz compat#3866

Open
ntucker wants to merge 2 commits intomasterfrom
fix/rest-bundle-path-to-regexp
Open

fix(rest): Bundle path-to-regexp into ESM output for StackBlitz compat#3866
ntucker wants to merge 2 commits intomasterfrom
fix/rest-bundle-path-to-regexp

Conversation

@ntucker
Copy link
Copy Markdown
Collaborator

@ntucker ntucker commented Apr 4, 2026

Motivation

StackBlitz WebContainers fundamentally cannot resolve CJS named exports from path-to-regexp, regardless of import style. Previous attempts (#3860 named import consolidation, #3862 namespace import *) all fail with:

Attempted import error: 'compile' is not exported from 'path-to-regexp'

This was confirmed to be unreproducible locally — webpack with importExportsPresence: 'error' passes fine, and Node 18/20/24 ESM loaders all detect the exports correctly. The issue is specific to WebContainers' CJS module analysis.

Solution

The only fix is to eliminate the CJS boundary entirely. This PR adds a rollup ESM build (dist/esm/index.js) that bundles path-to-regexp directly into the output, converting it from CJS to ESM at build time.

  • New rollup config entry produces dist/esm/index.js (ESM format, path-to-regexp inlined, other deps external)
  • "module" and "browser" export conditions now point to dist/esm/index.js
  • Explicit "types" condition added to exports map for correct TS resolution
  • CJS (dist/index.js), React Native (lib/), and UMD (dist/index.umd.min.js) paths are unchanged
  • RestHelpers.ts reverted to simple named imports (the bundled ESM has proper export statements, no CJS interop needed)

The bundled ESM output is 962 lines (vs 430 for path-to-regexp alone + the rest of the package) — minimal size increase since path-to-regexp was already a required dependency.

Open questions

N/A

Made with Cursor


Note

Medium Risk
Changes the @data-client/rest build pipeline and module wiring by inlining a dependency into the ESM/lib output, which could affect bundling behavior and runtime compatibility across environments.

Overview
Resolves StackBlitz/WebContainer CJS/ESM interop issues by bundling path-to-regexp into the @data-client/rest ESM/lib output and tree-shaking it down to just compile, parse, and pathToRegexp.

This adds a custom Rollup plugin/config path for BROWSERSLIST_ENV=2020, updates build:lib to run Rollup after Babel, and switches RestHelpers to import the path helpers via a new local src/pathToRegexp.ts re-export (compiled to lib/pathToRegexp.js).

Reviewed by Cursor Bugbot for commit 77d9d53. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 4, 2026

🦋 Changeset detected

Latest commit: 77d9d53

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@data-client/rest Patch
example-benchmark-react Patch
test-bundlesize Patch
coinbase-lite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs-site Ignored Ignored Preview Apr 5, 2026 5:12am

StackBlitz WebContainers fundamentally cannot resolve CJS named exports
from path-to-regexp, regardless of import style (named, namespace, etc).

Add a rollup ESM build (dist/esm/index.js) that bundles path-to-regexp
directly, eliminating the CJS boundary. Point the "module" and "browser"
export conditions to this bundled ESM output. CJS and React Native paths
are unchanged.

Reverts the namespace import workaround since it's no longer needed.

Made-with: Cursor
@ntucker ntucker force-pushed the fix/rest-bundle-path-to-regexp branch from ca0bc87 to 3d3378a Compare April 5, 2026 04:41
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

Size Change: -524 B (-0.65%)

Total Size: 80.4 kB

📦 View Changed
Filename Size Change
examples/test-bundlesize/dist/App.js 1.46 kB -1.79 kB (-55.14%) 🏆
examples/test-bundlesize/dist/rdcEndpoint.js 7.87 kB +1.48 kB (+23.14%) 🚨
examples/test-bundlesize/dist/webpack-runtime.js 726 B -212 B (-22.6%) 🎉
ℹ️ View Unchanged
Filename Size
examples/test-bundlesize/dist/polyfill.js 307 B
examples/test-bundlesize/dist/rdcClient.js 10.4 kB
examples/test-bundlesize/dist/react.js 59.7 kB

compressed-size-action

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.08%. Comparing base (7c395d6) to head (77d9d53).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3866   +/-   ##
=======================================
  Coverage   98.08%   98.08%           
=======================================
  Files         152      153    +1     
  Lines        2877     2877           
  Branches      564      564           
=======================================
  Hits         2822     2822           
  Misses         11       11           
  Partials       44       44           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…athToRegexpESM

The regex that strips CJS `exports.X = void 0` lines only matched exactly
two chained assignments. TypeScript CJS output can chain all exports on one
line (e.g. 7+ assignments). The old regex would leave the line intact,
causing a ReferenceError in ESM where `exports` is undefined.

Use `/^(?:exports\.\w+\s*=\s*)+void 0;\s*\n/gm` which matches one or more
chained `exports.X =` followed by `void 0`.

Co-authored-by: Nathaniel Tucker <me@ntucker.me>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 77d9d53. Configure here.

],
"scripts": {
"build:lib": "NODE_ENV=production BROWSERSLIST_ENV='2020' POLYFILL_TARGETS='chrome>88,safari>14' yarn g:babel --out-dir lib",
"build:lib": "NODE_ENV=production BROWSERSLIST_ENV='2020' POLYFILL_TARGETS='chrome>88,safari>14' yarn g:babel --out-dir lib && NODE_ENV=production BROWSERSLIST_ENV='2020' yarn g:rollup",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev watch mode broken by chained build commands

Low Severity

The build:lib script was changed from a single Babel command to yarn g:babel --out-dir lib && ... yarn g:rollup. The dev script calls run build:lib -w, where -w is meant to enable Babel's watch mode. With shell && chaining, the -w flag is appended to the last command (yarn g:rollup), so Babel runs once without watching and rollup gets the -w flag instead. This breaks the dev workflow — source file changes are no longer automatically recompiled.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 77d9d53. Configure here.

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.

2 participants