Skip to content

fix: keep source map source names as emitted in maps and stack frames - #1470

Merged
dannyhw merged 2 commits into
mainfrom
dannyhw/fix-source-map-source-names
Sep 26, 2026
Merged

dannyhw merged 2 commits into
mainfrom
dannyhw/fix-source-map-source-names

Conversation

@dannyhw

@dannyhw dannyhw commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1468.

This fixes both bugs from the issue, using the corrected root cause from the follow-up comment. Checking further showed the scope is wider than the issue describes, in three ways:

  • Bug 1 also affects Rspack. Rspack only drops sourceRoot for swc output. When Babel writes the map (@callstack/repack/babel-loader, or babel-swc-loader without swc), Rspack names files src/<dir>/<absolute path> too. Workspace packages in a monorepo get the same doubled name.
  • Bug 2 is not only about ^. source-map passes every source name through new URL(). That encodes spaces and non-ASCII characters (Home Screen.tsx, Écran.tsx), so standalone apps are affected too, not just files outside the project root.
  • The %5E comes from source-map, not parseUrl, as the follow-up showed. Decoding only in parseUrl brings back the code frame, but the response file stays encoded, so tapping a frame in LogBox still does nothing.

Changes

  1. Loaders: removed sourceRoot: this.context from babelLoader and both transforms in babelSwcLoader. Upstream babel-loader doesn't set it either. It can still be set through babelOverrides / swcOverrides.
  2. Symbolicator: prepareSourceMap (renamed from normalizeInvalidWebpackSourceUrls) escapes % in every source name before SourceMapConsumer reads the map. decodeURIComponent is then an exact inverse of the consumer's encoding, so lookup.source comes back exactly as the bundler emitted it. A file name that really contains % is not decoded twice. The embedded-content lookup re-escapes the name, so sourceContentFor still matches. That one change fixes the /symbolicate response, /open-stack-frame and the Symbolicated stack frame log line.
  3. parseUrl: returns the decoded pathname, falling back to the raw path if the escape sequence is malformed. symbolicator.getSource passes it raw file names, and the asset route passes a param Fastify has already decoded. new URL() was encoding both again.

The 5.4 canary changes are all kept. #1434 (remote source URLs): __repack_source__ URLs still come out encoded, and getRemoteSource and getPrintableFile decode them as before (there's a test for this). #1446 (getSource with absolute paths) is untouched and now actually gets used for nosources-* maps.

Tests

  • tests/integration/src/loaders/source-map-sources.test.ts (new). It compiles a small on-disk project with webpack and Rspack, through both babel-loader and babel-swc-loader, and checks that every file is named once by its absolute path in the source map. The project has app files, a space in a path, a local node_modules dependency, a hoisted node_modules dependency and a workspace package. On main, 3 of 4 cases fail; only Rspack + swc passes. With the fix, all 4 pass, in about 0.4s. @swc/core is linked into the fixture so webpack exercises the swc path too, and hermes-parser is added as a test dev dependency. The project is written to a temp directory, as NativeEntryPlugin.srcLayout.test.ts already does.
  • Symbolicator.test.ts: covers [projectRoot^2], a space, non-ASCII, a literal %25 and an absolute path with a space. For each it checks the frame file, the code frame from embedded content, and the no-sources fallback, where getSource must receive the raw name. It also checks that remote __repack_source__ URLs stay encoded, and that the log line strips a [projectRoot^N] placeholder. On main, 11 of these fail.
  • parseUrl.test.ts / resolveProjectPath.test.ts: decoded paths, malformed escapes, and parseUrl → resolveProjectPath composed the way the dev server does it.

Manual verification

apps/tester-app with devtool: 'nosources-source-map' on both bundlers. I symbolicated a frame from src/App.tsx and one from react-native/.../ActionSheetIOS.js with curl, then posted each result to /open-stack-frame with REACT_EDITOR pointing at a stub script that records its arguments:

webpack Rspack
[projectRoot]/ sources / doubled 32 / 0 32 / 0
src/App.tsx frame file [projectRoot]/src/App.tsx [projectRoot]/src/App.tsx
node_modules frame file [projectRoot^2]/node_modules/... [projectRoot^2]/node_modules/...
code frame (from disk) both frames both frames
path the editor received exists, both frames exists, both frames

Known limitation

A literal %XX sequence in a directory name is now decoded by parseUrl, so symbolicator.getSource or an asset request for it would look up the decoded name. Asset file names are already sanitised to [a-z0-9_], and the Symbolicator keeps literal % exact. In return, paths with spaces, non-ASCII characters or ^ now work, which is far more common.

Babel and swc were given `sourceRoot: this.context` alongside an absolute
`sourceFileName`. Webpack, and Rspack whenever Babel produced the map,
prepend `sourceRoot` to the absolute source, so project files and workspace
packages were named `src/<dir>/<absolute path>` in development and release
source maps.

`source-map` normalises every source name through `new URL()`, so the dev
server returned `[projectRoot%5E2]/...` for files outside the project root
and encoded any path with a space or non-ASCII character. That name reached
LogBox, `/open-stack-frame` and the no-sources fallback, none of which
resolve an encoded placeholder. Source names are now escaped before the
consumer reads the map and decoded on the way out, and `parseUrl` returns a
decoded path.

Fixes #1468
@changeset-bot

changeset-bot Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5c8f026

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

This PR includes changesets to release 6 packages
Name Type
@callstack/repack Patch
@callstack/repack-dev-server Patch
@callstack/repack-plugin-expo-modules Patch
@callstack/repack-plugin-nativewind Patch
@callstack/repack-plugin-reanimated Patch
@callstack/repack-init 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

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
repack-website Ready Ready Preview Sep 26, 2026 12:21pm UTC

Request Review

@dannyhw
dannyhw merged commit 4a36980 into main Sep 26, 2026
10 checks passed
@dannyhw
dannyhw deleted the dannyhw/fix-source-map-source-names branch September 26, 2026 13:20

This branch was successfully deployed

1 active deployment
Preview — 5c8f026f Deployed Sep 26, 2026 by vercel[bot]
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.

Webpack source maps name project files with a doubled path; [projectRoot^N] placeholders never resolve on the dev server

1 participant