codesandbox / codesandbox/codesandbox-client

Browser sandbox ESM→CJS converter rewrites static class field names that match an imported binding → SyntaxError: Unexpected number

Open
#8,909 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
13.6k
Forks
2.4k
Avg merge
6d 19h
Merged PRs (30d)
2

Description

🐛 bug report

Preflight Checklist

  • I have read the Contributing Guidelines for this project.
  • I agree to follow the Code of Conduct that this project
    adheres to.
  • I have searched the issue tracker for an issue that matches the one I want
    to file, without success.

Description of the problem

The fast ESM→CommonJS path used for node_modules in browser sandboxes (convertEsModule in packages/app/src/sandbox/eval/transpilers/babel/ast/convert-esmodule.ts) treats the key of a class field as a reference to an imported binding when both share a name. The key is rewritten to (0, $csb__mod.name), which is not a valid property name, so evaluating the module throws SyntaxError: Unexpected number.

Input (a dependency in node_modules):

import { x } from './a.js'

export class C {
  static x = x
}

Output produced by the converter (read from manager.transpiledModules[...].source.compiledCode):

"use strict";
exports.C = void 0;
Object.defineProperty(exports, "C", { enumerable: true, configurable: true, get: function $csbGet() { return C; } });
Object.defineProperty(exports, "__esModule", { value: true });
var $csb___a_js = require("./a.js");
class C {
  static (0, $csb___a_js.x) = (0, $csb___a_js.x)
}

Note static (0, $csb___a_js.x) = on the left-hand side. Renaming either the field or the import so they differ makes the module load.

Likely cause: the second pass in convert-esmodule.ts renames every escope reference whose name is in varsToRename and has resolved === null:

const scopeManager = escope.analyze(program, { ecmaVersion: 6 });
…
if (hasOwn(varsToRename, ref.identifier.name) && ref.resolved === null && !ref.writeExpr) {
  ref.identifier.name = `(0, ${varsToRename[ref.identifier.name].join('.')})`;
}

escope runs with ecmaVersion: 6, which predates class fields (PropertyDefinition, ES2022). It does not know that the identifier in a non-computed PropertyDefinition.key is a property name, reports it as an unresolved reference, and the rename is applied to it.

Suggested fix: skip identifiers that are the non-computed key of a PropertyDefinition or MethodDefinition during the rename pass, or move the scope analysis to a class-field-aware analyzer such as eslint-scope with ecmaVersion: 2022.

The pattern static propTypes = propTypes / static allowedProps = allowedProps is very common in React component libraries. Babel ≥ 7.23 with default preset-env targets and TypeScript with target: ES2022 emit native class fields, so more and more published packages have this shape. The same code runs in Node, Vite, webpack, esbuild, Rollup and every current browser.

How has this issue affected you? What are you trying to accomplish?

We maintain a React component library whose documentation site has an "Edit in CodeSandbox" button that opens each example in a browser sandbox. After a recent build change our published packages emit native class fields, and many class components contain static allowedProps = allowedProps. Since then no browser sandbox can import any of our components, so the button is broken for every example, and users who paste an example into a fresh React sandbox hit the same error.

VM Sandboxes are unaffected because they run a real bundler, but they require a signed-in user, so they are not a substitute for anonymous documentation visitors.

To Reproduce
  1. Create a sandbox from the React (create-react-app) browser template.

  2. Add a local package in the sandbox's own node_modules folder:

    node_modules/buggy-pkg/package.json

    { "name": "buggy-pkg", "version": "1.0.0", "type": "module", "main": "./index.js", "module": "./index.js" }
    

    node_modules/buggy-pkg/a.js

    export const x = { hello: 'world' }
    

    node_modules/buggy-pkg/index.js

    import { x } from './a.js'
    
    export class C {
      static x = x
    }
    
  3. Replace src/index.js with:

    import { C } from 'buggy-pkg'
    document.getElementById('root').textContent = 'C.x is: ' + JSON.stringify(C.x)
    
  4. Open the preview.

Expected: the page shows C.x is: {"hello":"world"}.

Actual: error overlay

SyntaxError
Unexpected number
    at $csb$eval (https://….csb.app/node_modules/buggy-pkg/index.js:…)

Renaming the field to static y = x makes it work. Any other browser template that uses the Babel transpiler (parcel, preact-cli, vue-cli, solid, …) behaves the same.

Link to sandbox: link (optional)

Preview: https://fk6lkn.csb.app

Your Environment
Software Name/Version
Сodesandbox Browser sandbox, create-react-app template, runtime bundle sandbox.d867b1f77.js, observed 2026-09-04
Browser Google Chrome (stable)
Operating System macOS 26.6.2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/app/src/sandbox/eval/transpilers/babel/ast/convert-esmodule.ts at the second-pass rename logic and inspect how convertEsModule handles class-field keys. Reproduce the case through manager.transpiledModules[...].source.compiledCode using the provided browser sandbox example. Done means static x = x remains valid and the module evaluates with C.x containing the imported value.

Written by the indexing model from the issue text.

Assessment

Tech stack
babel, typescript
Domain
build-system, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.