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 00000000000..a90e736b6c5 --- /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/.changeset/perky-melons-arrive.md b/.changeset/perky-melons-arrive.md new file mode 100644 index 00000000000..9ab8667fbc3 --- /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 76c8842fd5a..15492bcdf04 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/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts b/packages/theme/src/cli/utilities/theme-environment/theme-environment.test.ts index e6bc84ba15c..f4ef8a89a9c 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 0426a527f2b..422fec583c7 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 () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6bc9736c72..6355cc39894 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: