fix(assets): resolve Picture TDZ error when combined with content render()#16171
fix(assets): resolve Picture TDZ error when combined with content render()#16171alexanderniebuhr merged 8 commits intowithastro:mainfrom
Conversation
…der (withastro#16036) Introduce an internal virtual module (virtual:astro-get-image) that exports only getImage and imageConfig without any Astro component references. The content runtime now imports from this narrower module instead of astro:assets, breaking the circular initialization dependency that caused a TDZ ReferenceError when prerendered pages using <Picture> were bundled in the same chunk as content collection render() calls.
🦋 Changeset detectedLatest commit: 38866ae The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Use colon separator (virtual:astro:*) instead of hyphen so the module matches existing optimizeDeps.exclude patterns in both Astro core and the Cloudflare adapter. The hyphenated name was not excluded from Vite's dependency optimizer, causing esbuild to fail resolving it.
|
Hi @matthewp I think this is not related with my change. If you have some free time, please check this PR. |
|
@Desel72 thank you for your PR. Will get this reviewed. |
| // @ts-expect-error Internal virtual module that exports only getImage (no component | ||
| // references like Image/Picture) to avoid TDZ errors during prerender bundling (#16036). | ||
| const { getImage } = await import('virtual:astro:get-image'); |
There was a problem hiding this comment.
We must add this module to dev-only.d.ts, so that we don't need the ts directive
|
Hi @alexanderniebuhr Can this be merged? |
|
@Desel72 you need to address Ema's feedback: #16171 (comment) |
travisbreaks
left a comment
There was a problem hiding this comment.
Circular dependency causing TDZ errors is a nasty class of bug. The virtual module approach is a reasonable solution. A few things:
-
Virtual module boundary durability: The fix introduces `virtual:astro:get-image` to break the cycle. Future imports could re-introduce it if someone imports from `assets/consts.ts` in a path that also touches `content/runtime.ts`. A comment in the virtual module entry point warning about this would help prevent regression.
-
The @ts-expect-error question: ematipico's feedback about adding proper TS definitions is the right call. Virtual modules should have type declarations (`declare module "virtual:astro:get-image"`) so the `@ts-expect-error` isn't needed. This is a pattern Astro already uses for other virtual modules.
-
Test coverage: Does the test reproduce the exact bundling condition (Picture + content render() in the same chunk)? TDZ errors are sensitive to chunk splitting heuristics, so the test should ideally force co-location rather than relying on the current bundler behavior.
Good fix for a tricky problem. The TS definitions point from ematipico would make this merge-ready.
…t-error Add type declaration for the virtual:astro:get-image module so TypeScript recognizes the import without needing a @ts-expect-error directive.
|
Hi @matthewp @ematipico @alexanderniebuhr I've done. Please check. |
commit: |
|
Hi @alexanderniebuhr Thanks. I am going to move to #16035. I've created PR: #16194. Please review asap. |
|
Tested the preview in a real-life project and it works. |
|
@ematipico this is ready for another look when you have the chance |
Introduce an internal virtual module (
virtual:astro-get-image) that exports onlygetImageandimageConfigwithout any Astro component references. The content runtime now imports from this narrower module instead ofastro:assets, breaking the circular initialization dependency that caused a TDZ ReferenceError when prerendered pages using<Picture>were bundled in the same chunk as content collectionrender()calls.Closes #16036
Changes
virtual:astro-get-imageinpackages/astro/src/assets/consts.tsthat exports onlygetImageandimageConfig— noImage,Picture, orFontcomponent references.astro:assetsVite plugin inpackages/astro/src/assets/vite-plugin-assets.tsto resolve and load the new virtual module.packages/astro/src/content/runtime.tsto importgetImagefromvirtual:astro-get-imageinstead ofastro:assets, avoiding the TDZ caused by Rollup eagerly referencing$$Picturein the namespace object before its declaration is initialized.virtual:prefix (like existingvirtual:image-service) rather thanastro:prefix to keep this as an internal module and avoid exposing new public API surface.Testing
Added a regression test (
content-collection-picture-render.test.js) with a fixture that reproduces the exact scenario:<Picture>fromastro:assetsgetStaticPaths()+render(entry)with images in markdown bodyWithout the fix,
astro buildfails with:ReferenceError: Cannot access '$$Picture' before initialization
With the fix, the build succeeds and all 4 test assertions pass. Existing related test suites also pass with zero regressions:
content-collection-tla-svg.test.js(3/3)content-collections-render.test.js(13/13)core-image-picture-emit-file.test.js(6/6)units/content-collections/image-references.test.js(9/9)Docs
No docs changes needed. This is a bug fix for an internal module resolution issue — no user-facing API changes.