Skip to content

[heft-isolated-typescript-transpile-plugin] .js.map can list a second, absolute source (another file the worker transpiled), with a wrong mapping #6152

Open

Description

Package: @rushstack/heft-isolated-typescript-transpile-plugin 1.2.30 (SWC via transformFile)

Summary

With sourceMap: true and an ES5 targetOverride, some emitted .js.map files list two sources:

  1. the input file, as a relative path (the plugin rewrites sources[0]);
  2. a different, unrelated file of the same project, as an absolute path.

Exactly one mapping segment points at line 1, column 0 of that second file. The generated code at that position comes from the input file: an ES5 template-literal downlevel ("...".concat(...)). The synthesized node apparently gets a span at the start of another file that the same worker process had already loaded into SWC's source map.

Effects

  • The source map is wrong: a debugger jumps to an unrelated file.
  • The absolute path embeds the machine's checkout folder in the build output, so identical inputs produce different bytes on different machines (e.g. CI vs. a developer box). This defeats build-cache and output-equality checks for every downstream bundle that inlines these maps (e.g. webpack with source-map-loader).

Repro

  1. Use a library with about 650 .ts/.tsx files and these emit kinds:
    "emitKinds": [
      { "outDir": "/lib-commonjs", "formatOverride": "CommonJS", "targetOverride": "ES5" },
      { "outDir": "/lib-esm", "formatOverride": "ES2020", "targetOverride": "ES5" }
    ]
    with sourceMap: true in tsconfig.
  2. Run heft build --clean. In our case, 8 of 1,328 .js.map files had sources.length === 2. The extra source was always the same file (deterministic for a given file set), e.g.
    "sources": ["../../../../../src/<folder>/<input>.ts", "/<absolute checkout path>/src/<other folder>/<other file>.tsx"]
    
  3. Decoding mappings gives exactly one segment with source index 1, mapping a "...".concat( in the generated code to line 1, column 0 of the other file.

Where

lib-commonjs/TranspileWorker.js rewrites only parsedMap.sources[0] = relativeSrcFilePath. Any extra source that SWC adds keeps SWC's absolute file name.

Suggested fix

  • In the plugin (defensive): when parsedMap.sources.length > 1, keep only the input file. Re-encode mappings so that segments pointing at other sources become unmapped generated positions (1-field segments), and trim sourcesContent to match. We apply this as a local patch: it changes only the affected maps (8 of 1,328 in the repro) and leaves every single-source map byte-identical; all source-0 lookups are unchanged.
  • Upstream in SWC (root cause): the synthesized span in the ES5 template-literal transform should be a dummy span or the original node's span, not a position in another file of the shared source map. This probably deserves a separate SWC issue once confirmed.

AI-generated with GitHub Copilot on behalf of Naman Kanakiya (@namankanakiya); findings were reproduced on real builds by two independent sessions.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Needs triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions