From 30895ae02676f97846ff84f979c80d7c8ff3d986 Mon Sep 17 00:00:00 2001 From: Gray Gilmore Date: Fri, 29 May 2026 13:18:55 -0700 Subject: [PATCH 1/2] Bump @shopify/theme-hot-reload from 0.0.18 to 0.0.22 Picks up four releases that improve the hot-reload script served to the browser during `theme dev`: - Prevent EventSource connections to unknown origins - Use the original storefront domain when calling the Section Rendering API - Allow messages from the tab opener for theme preview - Improve stylesheet hot-reloading in the Online Store Editor to avoid full refreshes --- .changeset/perky-melons-arrive.md | 5 +++++ packages/theme/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/perky-melons-arrive.md diff --git a/.changeset/perky-melons-arrive.md b/.changeset/perky-melons-arrive.md new file mode 100644 index 0000000000..9ab8667fbc --- /dev/null +++ b/.changeset/perky-melons-arrive.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Bump @shopify/theme-hot-reload to address HMR issues diff --git a/packages/theme/package.json b/packages/theme/package.json index 76c8842fd5..15492bcdf0 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -49,7 +49,7 @@ "yaml": "2.8.3" }, "devDependencies": { - "@shopify/theme-hot-reload": "^0.0.18", + "@shopify/theme-hot-reload": "^0.0.22", "@vitest/coverage-istanbul": "^3.1.4", "node-stream-zip": "^1.15.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6bc9736c7..6355cc3989 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -681,8 +681,8 @@ importers: version: 2.8.3 devDependencies: '@shopify/theme-hot-reload': - specifier: ^0.0.18 - version: 0.0.18 + specifier: ^0.0.22 + version: 0.0.22 '@vitest/coverage-istanbul': specifier: ^3.1.4 version: 3.2.4(vitest@3.2.4(@types/node@22.19.17)(jiti@2.6.1)(jsdom@28.1.0)(msw@2.12.10(@types/node@22.19.17)(typescript@5.9.3))(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.3)) @@ -3810,8 +3810,8 @@ packages: resolution: {integrity: sha512-ro9PL+5FGMxkLzYfhC6hfv/WlpxK7tUXDlv5uWson/TQIfbs1fo7Nm+VePpJXry0Y+umRDppTRWNmud7YnSuvA==} hasBin: true - '@shopify/theme-hot-reload@0.0.18': - resolution: {integrity: sha512-l+IBuk+rG5T+5PKYyPrwgh7PDCxmEMpBFJeen6PM+h6RI4CDhAGRaiwUo5eN1o1JX51HdHHCts3rTEW+KUgq+Q==} + '@shopify/theme-hot-reload@0.0.22': + resolution: {integrity: sha512-RiaLPqhW3iAJKlO3KRvU4sQJ0cJIwhZ5Zx77wYw+3+oFXHC+vNrB+mjIarxoqCbUdm+E1eUwAs0aGU74YjFWkA==} '@shopify/theme-language-server-common@2.21.0': resolution: {integrity: sha512-/jdb51pAEAsSBKGMxrSV/n/xVWbPsJ00aUQHqL2VXtiFkb5x3Ny4PShlENAee9OIOyJxxmnrOqObKkpzfeg+aA==} @@ -13105,7 +13105,7 @@ snapshots: transitivePeerDependencies: - encoding - '@shopify/theme-hot-reload@0.0.18': {} + '@shopify/theme-hot-reload@0.0.22': {} '@shopify/theme-language-server-common@2.21.0': dependencies: From 38bc94a229c2e87c8c6018736baa6da9dce7c841 Mon Sep 17 00:00:00 2001 From: Gray Gilmore Date: Fri, 29 May 2026 13:53:44 -0700 Subject: [PATCH 2/2] Allow the Online Store Editor origin through dev server CORS Hot reload stopped working in the theme editor after 226b49e740 locked the dev server's CORS allowlist down to localhost and the store domain to close a vulnerability where any site could read from the localhost server. The theme editor renders its storefront preview in an iframe served from https://online-store-web.shopifyapps.com, which opens a hot-reload EventSource back to the local dev server. That origin wasn't on the allowlist, so the browser blocked the connection ("Connection closed by the server, attempting to reconnect..."). Add the Online Store Editor origin to the allowlist so hot reload works in the editor again, without reopening the wildcard CORS hole. --- .../fix-theme-editor-hot-reload-cors.md | 5 +++++ .../theme-environment.test.ts | 21 +++++++++++++++++++ .../theme-environment/theme-environment.ts | 7 ++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-theme-editor-hot-reload-cors.md diff --git a/.changeset/fix-theme-editor-hot-reload-cors.md b/.changeset/fix-theme-editor-hot-reload-cors.md new file mode 100644 index 0000000000..a90e736b6c --- /dev/null +++ b/.changeset/fix-theme-editor-hot-reload-cors.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Fix `theme dev` hot reload not working in the theme editor by allowing the Online Store Editor origin through the dev server's CORS policy diff --git a/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts b/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts index e6bc84ba15..f4ef8a89a9 100644 --- a/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts +++ b/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts @@ -257,6 +257,27 @@ describe('setupDevServer', () => { expect(vi.mocked(render)).not.toHaveBeenCalled() }) + test('CORS allows the theme editor (Online Store Editor) origin so hot reload works in the editor', async () => { + const {res} = await dispatchEvent('/assets/file2.css', {origin: 'https://online-store-web.shopifyapps.com'}) + expect(res.getHeader('access-control-allow-origin')).toEqual('https://online-store-web.shopifyapps.com') + }) + + test('CORS allows the storefront origin', async () => { + const origin = `https://${defaultServerContext.session.storeFqdn}` + const {res} = await dispatchEvent('/assets/file2.css', {origin}) + expect(res.getHeader('access-control-allow-origin')).toEqual(origin) + }) + + test('CORS does not allow unknown origins', async () => { + const {res} = await dispatchEvent('/assets/file2.css', {origin: 'https://evil.example.com'}) + expect(res.getHeader('access-control-allow-origin')).toBeUndefined() + }) + + test('CORS does not set headers on direct navigation without an Origin header', async () => { + const {res} = await dispatchEvent('/assets/file2.css') + expect(res.getHeader('access-control-allow-origin')).toBeUndefined() + }) + test('serves proxied local assets', async () => { const eventPromise = dispatchEvent('/cdn/somepathhere/assets/file1.css') await expect(eventPromise).resolves.not.toThrow() diff --git a/packages/theme/src/cli/utilities/theme-environment/theme-environment.ts b/packages/theme/src/cli/utilities/theme-environment/theme-environment.ts index 0426a527f2..422fec583c 100644 --- a/packages/theme/src/cli/utilities/theme-environment/theme-environment.ts +++ b/packages/theme/src/cli/utilities/theme-environment/theme-environment.ts @@ -130,7 +130,12 @@ interface DevelopmentServerInstance { function createDevelopmentServer(theme: Theme, ctx: DevServerContext, initialWork: Promise) { const app = createApp() - const allowedOrigins = [`http://${ctx.options.host}:${ctx.options.port}`, `https://${ctx.session.storeFqdn}`] + const allowedOrigins = [ + `http://${ctx.options.host}:${ctx.options.port}`, + `https://${ctx.session.storeFqdn}`, + // Required for HMR with the theme editor + 'https://online-store-web.shopifyapps.com', + ] app.use( defineLazyEventHandler(async () => {