Skip to content

Support type=module v2 addons in webpack builds - #2780

Open
NullVoxPopuli-ai-agent wants to merge 2 commits into
embroider-build:stablefrom
NullVoxPopuli-ai-agent:type-module-webpack-macros
Open

Support type=module v2 addons in webpack builds#2780
NullVoxPopuli-ai-agent wants to merge 2 commits into
embroider-build:stablefrom
NullVoxPopuli-ai-agent:type-module-webpack-macros

Conversation

@NullVoxPopuli-ai-agent

Copy link
Copy Markdown

Part of the type=module quest issue: #1773. Fixes #1672. Should also fix #1774 and likely #1674 (explanation below). Supersedes the macros half of #1906 and resolves the blocker that PR was stuck on.

Problem

When a v2 addon sets "type": "module" in its package.json, webpack applies strict ESM semantics to all of the addon's .js files, which breaks them in three ways that non-type=module addons never hit:

  1. Lost __esModule interop for externalized modules ([type=module] _ember_component_template_only is not a function #1774). Default-importing one of the CommonJS modules we externalize — e.g. the /@embroider/ext-cjs/ virtual modules that stand in for @ember/component/template-only when it isn't resolvable statically — yields the module's exports object instead of its default export, because webpack (correctly, per spec) skips __esModule interop for strict-ESM importers. At runtime: _ember_component_template_only__WEBPACK_IMPORTED_MODULE_x__ is not a function.

  2. Fully-specified resolution (importSync breaks the builds of apps consuming type=module packages #1672). Import specifiers inside the addon must carry file extensions, so directory imports fail, and so does the relative extensionless es-compat2 import that @embroider/macros emits when compiling importSync():

    Module not found: Error: Can't resolve '…/@embroider/macros/src/addon/es-compat2'
    BREAKING CHANGE: The request … failed to resolve only because it was resolved as fully specified
    
  3. require() is not allowed in strict ESM, so the require() calls that importSync() compiles to are not treated as webpack dependencies and get left for the runtime AMD loader, which can't resolve relative paths. This was the remaining blocker on @embroider/macros and type=module #1906's test (Could not find module ./side-effecting.js imported from (require)).

This is also very likely the mechanism behind #1674: a request originating in a strict-ESM addon file carries fullySpecified resolve options that survive being rehomed into a rewritten package (see the analysis in #1572 (comment)), producing the Can't resolve '.' error. With the addon's files back in javascript/auto, those resolve options are never created in the first place.

Solution

  • @embroider/webpack: add a module rule that opts .js files owned by v2 addons back into type: 'javascript/auto' with resolve: { fullySpecified: false }. This is exactly the treatment every non-type=module v2 addon already gets (their .js files default to javascript/auto because their package.json has no type field), so type=module addons now behave identically to all other v2 addons. Non-addon packages are unaffected and keep spec-compliant strict-ESM behavior.
  • @embroider/macros: emit fully-specified (extension-bearing) paths for our es-compat2 and runtime helper modules (embroider-mode only; the classic AMD specifier is unchanged). This keeps the emitted code correct under strict ESM semantics in any bundler, independent of the webpack rule.

Testing

  • New scenario v2-addon-type-module-test.ts: a type=module v2 addon (with addon-main.cjs) covering a template-only component, a @glimmer/component subclass, an internal directory import, and importSync() of a relative module. Runs in both safe and optimized embroider modes. All pass on release, lts_3_28, and lts_5_12; a control run without the two source fixes fails with the strict-ESM errors quoted above.
  • Macros jest suite: 356 passing (two harness/assertion updates for the now-extension-bearing emitted paths).
  • No regressions: v2-addon-test.ts and macro-test.ts release scenarios (including CLASSIC=true runs) all pass.
  • Verified end-to-end against the quest repro repo (https://github.com/NullVoxPopuli/embroider-type-module-testing): the previously-failing app-embroider-3-lib-import-sync app builds and passes with these packages linked in.

The same interop/fully-specified fix for ember-auto-import is embroider-build/ember-auto-import#718.

🤖 Generated with Claude Code

Two coordinated fixes:

1. @embroider/webpack: add a module rule that opts .js files owned by v2
   addons back into webpack's regular javascript/auto handling. When a v2
   addon sets "type": "module" in its package.json, webpack otherwise
   applies strict ESM semantics to the addon's files, which breaks them in
   three ways that non-type=module addons never hit:
   - default-importing one of the CommonJS modules we externalize (like
     the /@embroider/ext-cjs/ virtual modules) yields the module's exports
     object rather than its default export, because strict ESM importers
     don't get __esModule interop (embroider-build#1774)
   - import specifiers must be fully-specified, so directory imports and
     the relative extensionless es-compat2 import emitted by
     @embroider/macros importSync fail to resolve (embroider-build#1672)
   - require() is not allowed in strict ESM, so the require() calls that
     importSync compiles to get left for the runtime AMD loader (which
     can't resolve relative paths) instead of being handled by webpack

2. @embroider/macros: emit fully-specified (extension-bearing) import
   paths for our es-compat2 and runtime helper modules, so they resolve
   under strict ESM semantics in any bundler.

Fixes embroider-build#1672. Part of the type=module quest embroider-build#1773. Supersedes the macros
half of embroider-build#1906.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NullVoxPopuli-ai-agent

Copy link
Copy Markdown
Author

Note on CI: every release-* job on this PR (all 38, including suites this PR doesn't touch like release-compat-renaming) fails during build setup with

TypeError: Cannot read properties of undefined (reading 'cleanup')
TypeError: Cannot read properties of undefined (reading 'templateCompiler')

That's pre-existing drift, not caused by this change: the release scenario floats ember-source@latest / ember-cli@latest, which moved to the 7.x line after stable's last green CI run on April 29. The pinned scenarios — including the new lts_3_28-v2-addon-type-module and lts_5_12-v2-addon-type-module — pass.

Locally (against the committed lockfile, ember-source 6.12), release-v2-addon-type-module passes in both safe and optimized modes, and fails without the two source fixes with the strict-ESM errors described above.

@NullVoxPopuli

Copy link
Copy Markdown
Collaborator

looks like we haven't fixed (don't plan to fix?) release scenarios

The v1 addon gets rewritten to v2 during compat, so this exercises the
request path where an import from a strict-ESM file lands in a
rewritten package (the suspected embroider-build#1674 mechanism), plus default-import
interop through that path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NullVoxPopuli-ai-agent

Copy link
Copy Markdown
Author

Added explicit coverage for the "type=module v2 addon depends on a v1 addon" case: esm-v2-addon now declares inner-v1-addon as a dependency and both default-imports and named-imports from it. In embroider the v1 addon gets rewritten to v2 during compat, so this exercises the request path where an import originating in a strict-ESM file lands in a rewritten package — the suspected #1674 mechanism. Passes in safe + optimized on release, lts_3_28, and lts_5_12 locally.

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.

2 participants