Skip to content

ESM Transition Plan for AnalogJS v2.0.0-alpha #1844

@benpsnyder

Description

@benpsnyder

📖 Description

We want to transition AnalogJS v2.0.0-alpha to ECMAScript Modules (ESM) as the default module format. This unlocks better build optimization, aligns with Vite and Angular’s future direction, and removes legacy CommonJS (CJS) constraints.

However, Angular 17+ builders and parts of the ecosystem still rely on CommonJS-only dependencies, which could introduce compatibility issues if we go fully ESM without a transition plan. This issue kicks off the discussion around how to adopt ESM safely, while maintaining compatibility with Angular’s build system and avoiding unnecessary breakage for downstream consumers.


⚠️ Problem Areas

  • Angular Constraints
    Angular 17+ supports ESM via esbuild/Webpack, but some internal builders and third-party deps remain CJS, leading to build warnings or outright errors.
    ➡️ See: Angular CLI Build System Migration

  • Dependency Landscape
    Non-ESM-compatible dependencies trigger CommonJS warnings, reduce tree-shaking, and hurt build optimization.

  • Downstream Consumption
    If @analogjs/* packages are published as ESM-only, any consumer still using CJS (require('@analogjs/...')) will break with ERR_REQUIRE_ESM.

    • ✅ Vite-based apps: no problem (already ESM-first).
    • ✅ Node SSR (Nitro/Angular Universal) on Node 18+: safe with "type": "module".
    • ❌ Legacy CJS tooling (Jest without ESM setup, schematics, ts-node scripts): will break.
  • Collaboration & Planning
    Large shifts like this need alignment up front to prevent wasted effort (e.g., PRs landing before consensus).


💡 Proposed Solutions

  1. Phased ESM Rollout

    • Identify and upgrade ESM-compatible dependencies.
    • Keep CJS fallback for Angular builder–tied deps.
    • Update internal imports to comply with "exports"/bundler-style resolution.
  2. Experimental ESM Branch

    • Maintain a separate alpha-esm branch to test the full switch.
    • CI matrix (Linux/Windows, Node 18/20/22) to surface environment-specific breakage early.
  3. Dual Output Strategy (Optional)

    • For sensitive entry points (builders, schematics, CLI hooks), consider shipping dual builds (ESM + CJS) temporarily.
    • This softens the transition for downstreams still on CJS.
  4. Community Alignment

    • Use issues (like this) to discuss breaking changes before PRs.
    • Document migration steps clearly ("type": "module", await import(), updating Jest configs).

❓ Open Questions

  • Which Angular 17+ dependencies are still blocking ESM adoption?
  • Do we want to go ESM-only in alpha (fast clean break) or dual-build for a few cycles?
  • What’s the minimum Node/TypeScript baseline we enforce for v2-alpha?
  • How should we update create-analog templates to reflect the ESM-first approach?

✅ Next Steps

  • Audit Angular 17+ CommonJS dependencies.
  • Test full ESM builds in a separate branch.
  • Verify compatibility in downstream contexts (SSR, Storybook, Vitest, Jest).
  • Gather community feedback before finalizing release strategy.
  • Update templates, docs, and migration guides.

🔗 References


📝 Notes

This issue starts the collaborative planning for AnalogJS's ESM transition. Please share your thoughts:

  • Any known blockers in your projects?
  • Strong opinions on ESM-only vs dual-build for alpha?
  • Tooling setups (Jest, schematics, SSR) we should prioritize testing against?

Updated Argument

After diving deeper into our codebase, here's the concrete technical debt that strengthens the case for ESM migration:

🔍 Current State Analysis

Measurable CommonJS Technical Debt

  • 33 CommonJS require() statements across 16 files in our codebase
  • 15+ packages manually excluded from optimization in deps-plugin.ts (@angular/platform-server, @nx/angular, webpack, etc.) specifically because they use CommonJS
  • Dual dependency conflicts: We currently ship both @angular-devkit/build-angular (CommonJS) AND @angular/build (ESM) as dependencies

Specific Performance Pain Points

  • Vite pre-bundling overhead: Our optimizeDeps.exclude list forces manual dependency management instead of automatic bundler optimization
  • Build optimizer limitations: Our angular-build-optimizer-plugin.ts detects Angular packages via /fesm20/ patterns because CommonJS prevents better static analysis
  • SSR complexity: Files like ssr-build-plugin.ts contain manual global.globalThis. replacements that exist only due to CommonJS/ESM interop issues
  • Development friction: Hot reload performance suffers in our 963-line angular-vite-plugin.ts when mixing module systems

🎯 Concrete Migration Benefits

Current CommonJS Pain ESM Solution AnalogJS Benefit
33 require() statements Native import Faster dev server, better HMR
Manual 15+ package exclusions Automatic optimization Simpler deps-plugin.ts
Dual @angular-devkit + @angular/build deps Single @angular/build ESM Cleaner dependency tree
/fesm20/ pattern detection Static ESM analysis Better build optimizer
globalglobalThis patches Native ESM globals Cleaner SSR plugins

🚀 Strategic Positioning

Angular's ESM Leadership Opportunity

  • Current transition state: AnalogJS templates use Angular 19+ with both legacy and modern build packages, showing we're already in the ecosystem's transition period
  • Validation opportunity: Angular 18+ introduced @angular/build/private ESM exports, but the community needs frameworks like AnalogJS to validate real-world usage
  • Vite-native advantage: We already have esbuild integration, making us perfectly positioned to benefit immediately from ESM optimizations

Phased Implementation Strategy

  1. Critical path: Target packages/vite-plugin-angular/src/lib/utils/devkit.ts first - migrate from require('@angular-devkit/build-angular') to import('@angular/build/private')
  2. Plugin modernization: Incrementally update deps-plugin.ts, platform-plugin.ts, and router-plugin.ts
  3. Performance benchmarking: Measure build times before/after to quantify improvements
  4. Testing ecosystem: Evaluate Jest 30 (improved ESM support) or recommend Vitest for ESM-native testing

This data-driven approach shows AnalogJS has specific, measurable technical debt that ESM migration directly resolves - not just theoretical future benefits, but immediate improvements to our existing plugin architecture and build optimization systems.

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