Static class fields can’t be tree-shaken away on subsequent compilation
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 40.1k
- Forks
- 1.3k
- PR merge metrics
- No merged PRs in 30d
Description
Hey Evan,
Thank you for creating and maintaining esbuild!
We @ Framer stumbled upon an issue around static class fields. When you use them, and you’re compiling for ES2021 and below, esbuild produces code that’s not tree-shakeable on a subsequent compilation. This is relevant e.g. when you’re a library author and are distributing the library as a bundle.
Steps to reproduce
-
Create
my-library.jswith the following code:export class Navigation { state = defaultState() static defaultProps = { enabled: true, } static contextType = NavigationCallbackContext } -
Bundle the library with
{ target: "es2019" }intomy-library-compiled.js. Observe that the library is compiled down to:var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); export class Navigation { constructor() { __publicField(this, "state", defaultState()); } } __publicField(Navigation, "defaultProps", { enabled: true }); __publicField(Navigation, "contextType", NavigationCallbackContext); -
Now, import the compiled library from another file (say,
my-app.js):import {} from "./my-library-compiled.js" -
Bundle that file (using esbuild, webpack, etc – doesn’t matter). Observe that the app bundle includes
Navigation, even though it’s not used.
Actual result
Any classes that use static fields in the library can’t be tree-shaken away due to top-level __publicField setters.
Expected result
esbuild compiles classes with static fields down to something like this:
export class Navigation {
state = defaultState()
static defaultProps = {
enabled: true,
}
static contextType = NavigationCallbackContext
}
↓
export var Navigation = /* @__PURE__ */ (() => {
class Navigation {
constructor() {
__publicField(this, "state", defaultState());
}
}
__publicField(Navigation, "defaultProps", {
enabled: true
});
__publicField(Navigation, "contextType", NavigationCallbackContext);
return Navigation;
})()
which allows Navigation to be tree-shaken away.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked esbuild reproduction and compare the two compilation stages described in the issue. Trace static class-field lowering and subsequent tree shaking, then verify that an unused class with lowered static fields is removed without changing the output for a used class.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, javascript
- Domain
- build-system, compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100