Skip to content

compilePackages: inline export default class Name { … } (ESM) drops the prototype method table — only constructor survives (ESM sibling of #4933; blocks ink render) #4976

Description

@proggeramlug

Summary

An inline export default class Name { … } declaration in a compilePackages (ESM) module loses its entire method table: Class.prototype ends up with only constructor. All prototype methods and class-field initializers are dropped, so instances have none of their methods.

The equivalent class written as a named declaration then default-exported works fine. So this is purely the inline export default class emit path — the ESM sibling of #4933 (module.exports = class, fixed in #4947 for CJS).

Minimal repro

node_modules/edcpkg/index.js ("type":"module", in perry.compilePackages + allow.compilePackages):

export default class Widget {
  field1;
  arrow = () => 'arrow';
  greet() { return 'hello'; }
}

node_modules/edcpkg2/index.js:

class Widget2 { greet() { return 'hi2'; } }
export default Widget2;     // named, then default-export
import Widget from 'edcpkg';
import Widget2 from 'edcpkg2';
console.log(Object.getOwnPropertyNames(Widget.prototype), typeof Widget.prototype.greet);
console.log(Object.getOwnPropertyNames(Widget2.prototype), typeof Widget2.prototype.greet);

Output:

export default class Widget:   proto keys = constructor            | greet = undefined   ✗
                               new Widget().greet = undefined  arrow = undefined         ✗
class X; export default X:     proto keys = constructor,greet      | greet = function    ✓

Node prints constructor,greet / function for both. User-code (non-compilePackages) classes of every shape (methods-only, bare-field+method, arrow-field+method, mixed) all work — confirmed — so the trigger is specifically inline export default class inside a compiled package.

How it surfaced (ink, #348)

This is the first render-execution wall (the React hook-dispatcher #4950 is fixed; full init + render() now run). ink/build/ink.js:128 is export default class Ink { … render(node){…} … }. Under Perry Ink.prototype = {constructor} only, so ink's render() does:

const instance = new Ink(inkOptions);
instance.render(node);   // TypeError: render is not a function

Confirmed via a runtime probe at that call site: Ink.prototype = object, Ink.prototype.render = undefined, ownKeys(proto) = ['constructor'].

Scope / suggested fix

  • Mirror the fix(cjs_wrap): flat-emit module.exports = <Class> that closes over a top-level binding (#4933) #4947 fix for the ESM side: emit export default class Name { … } by lowering it to a named class declaration whose methods/field-initializers are installed normally, then make the default export bind that class — rather than the current path that publishes a bare constructor with an empty prototype.
  • Both prototype methods and class-field initializers are dropped (the repro's greet method and arrow/field1 fields all vanish), so the fix needs to cover the full class-body installation, not just methods.

Impact

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions