ShaneK · GitHub

Issue number: resolves #31333


What is the current behavior?

Currently, Stencil's lazy loader resolves component bundles with a template literal dynamic import wrapped in a /* @vite-ignore */ comment. Vite 8 moved dependency prebundling from esbuild to Rolldown, which honors that comment and leaves the import alone, so the .entry.js chunks never get emitted into the prebundle output. Requests for them 404 and no lazy component registers, which surfaces as Constructor for "ion-app#undefined" was not found in the console.

Angular CLI 22.1 is where most people hit this, since its dev server is the first to ship Vite 8. It only affects the lazy IonicModule path, because that's the only thing in the repo that pulls @ionic/core/loader. Standalone components import from @ionic/core/components and never touch the loader.

What is the new behavior?

With this change we set extras.enableImportInjection in core/stencil.config.ts. Stencil prepends a switch of literal import paths to the loader, ahead of the existing @vite-ignore import, so Rolldown can resolve each bundle and emit its chunk. The original import stays as the fallback, so nothing changes for consumers that already worked.

The fix is one config flag that would silently revert if anyone dropped it, so there's a guard alongside it. core/scripts/verify/lazy-imports.js reads the built dist/esm and dist/cjs, locates the loader chunk, and fails if any bundle is missing a literal import. It runs in both build-core and build-core-stencil-prerelease, so a Stencil upgrade that changes the injection behavior fails the nightly rather than shipping.

Does this introduce a breaking change?

  • Yes
  • No

Other information

Verified against a stock Angular CLI 22.1 app using IonicModule.forRoot() and a local build of this branch. Before the fix we had a ton of 404s on ion-app_8.entry.js and ion-button_2.entry.js, plus some Constructor for ... was not found errors. After the fix we had no console errors, components render fine, and ng build emits the entry chunks with the right names.

CI doesn't cover the actual symptom and it cannot here because we have no ng22 test apps on main, but we'll have it tested for free on major-9.0 with the existing ng22 test app there. The guard script asserts the build-output invariant for the extra validation.

The injected switch adds roughly 10.8 KB raw to the lazy runtime chunk, which is under 1 KB gzipped because the case clauses are near-identical repeated text, but people using the CDN build will have no extra overhead at all.

Anyone on a published @ionic/core can work around this with prebundle.exclude in angular.json until it ships:

"serve": {
  "options": {
    "prebundle": { "exclude": ["@ionic/angular", "@ionic/core"] }
  }
}

Read the original on github.com ↗