Update extension templates to Bundler module resolution - #424
Conversation
Generated Preact POS extensions currently use Node module resolution, which TypeScript deprecates and which does not understand modern package exports consistently. Configure all shipped POS templates to emit ESNext modules and use Bundler resolution, matching the repository's existing extension-template precedent.\n\nResolves shop/issues-retail#33804.
|
It looks like there are lots more templates with this issue than just ours from https://github.com/search?q=repo%3AShopify%2Fextensions-templates+"moduleResolution"%3A+"node"&type=code. Should we make this a wider update? And if so, use that to align on these being the correct config options (that is what the AI recommended, but I haven't dug into if its right) |
|
Great callout @henryStelle. I added for the rest. |
henryStelle
left a comment
There was a problem hiding this comment.
I looked up these tsconfig options and these look like the correct changes. It would probably be good to get an 👍 from at least one of the other extension teams.
Exact-head terminal verificationRead-only local reproduction against
These are local reproduction results, not hosted CI results. |



What
Update every generated extension template still using legacy Node module resolution to emit ES2015 modules and use TypeScript's
Bundlermodule resolution.Why
TypeScript 6 rejects the deprecated
noderesolution mode withTS5107.Bundlercorrectly understands modern packageimportsandexports, including@shopify/ui-extensions/preact.ES2015is intentional. Shopify CLI bundles UI extensions for an ES2015 target. UsingESNextwould allowimport.metathrough TypeScript even though the CLI bundler replaces it with an empty object, which can produce a deployable runtime failure.Resolves shop/issues-retail#33804.
Changes
"module": "ES2015"to the 18 templates moving from Node resolution."moduleResolution": "node"to"moduleResolution": "Bundler".discount-details-function-settings-blockon its existingESNextplusBundlerconfiguration so valid dynamic imports remain supported there.Bundlerwas introduced in TypeScript 5.These configs use
noEmit, so their primary effect is TypeScript checking and editor resolution. Existing generated projects are unchanged.Review-driven investigation
This started with the three POS templates affected by shop/issues-retail#33804. Reviewer feedback prompted a repository-wide audit, which found 18 templates still using legacy Node resolution and expanded the fix to cover all of them consistently.
The implementation was then refined through two compatibility checks:
ESNextplusBundlerfixed TypeScript resolution, but allowedimport.metaeven though Shopify CLI cannot preserve it safely for the ES2015 runtime target. The 18 migrated templates therefore useES2015, which rejects that unsupported construct during type-checking.ES2015to the existing discount settings template rejected its valid dynamic imports withTS1323. That unrelated change was removed, leaving the discount template on its establishedESNextplusBundlerconfiguration.The TypeScript 5 baseline was also checked against current Shopify extension consumers rather than assumed:
^5.0.0.^5.9.3, and the Remix app template declares TypeScript^5.2.2.The exact final head is
64c651898afd5d278714661c85db0f91531f57b3. It contains only the 18 intended migrations; the discount settings template is not part of the final diff.Testing
TS5107with the previous Node configuration.ES2015plusBundlerresolves@shopify/ui-extensions/preactunder TypeScript 5.0.4, 5.9.3, and 6.0.2.noEmit: true, and validate under those three TypeScript versions: 18 useES2015, and the existing discount settings template retainsESNext.import.meta.urlregression test for the 18 migrated templates is rejected by TypeScript withTS1343. UnderESNext, the same test passed TypeScript, bundled successfully with a warning, and crashed at runtime withTypeError: Invalid URL.ES2015reproducesTS1323under TypeScript 5.0.4, 5.9.3, and 6.0.2.import.metaor dynamic imports.moduleResolution: "node"settings remain.git diff --checkpasses.Risk
Low for production. Existing projects are not modified, representative runtime bundles are byte-identical, and the configuration now rejects a module feature that Shopify CLI's ES2015 bundle target cannot preserve safely.
TypeScript 4.9 and older cannot read
moduleResolution: "Bundler"and fail withTS6046. TypeScript 5 or newer is an intentional baseline for newly generated extensions using these 18 migrated configurations. This is an accepted development-tooling compatibility boundary rather than a runtime change, and it matches the TypeScript baseline in Shopify's current typed extension examples and JavaScript app templates.Checklist
Co-authored by AI.