diff --git a/.changeset/lovely-terms-tell.md b/.changeset/lovely-terms-tell.md new file mode 100644 index 0000000000..5d697a5288 --- /dev/null +++ b/.changeset/lovely-terms-tell.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Fix hot-reloading for the {% javascript %} tag when serving compiled assets scripts with multibyte characters diff --git a/packages/theme/src/cli/utilities/theme-environment/local-assets.ts b/packages/theme/src/cli/utilities/theme-environment/local-assets.ts index 2c3f676fdc..d8cfc20107 100644 --- a/packages/theme/src/cli/utilities/theme-environment/local-assets.ts +++ b/packages/theme/src/cli/utilities/theme-environment/local-assets.ts @@ -130,7 +130,7 @@ function handleStylesCss(ctx: DevServerContext, event: H3Event) { return serveStatic(event, { getContents: () => stylesheet, - getMeta: () => ({type: 'text/css', size: stylesheet.length, mtime: new Date()}), + getMeta: () => ({type: 'text/css', size: Buffer.byteLength(stylesheet), mtime: new Date()}), }) } @@ -190,7 +190,7 @@ function handleBlockScriptsJs(ctx: DevServerContext, event: H3Event, kind: 'bloc return serveStatic(event, { getContents: () => javascript, - getMeta: () => ({type: 'text/javascript', size: javascript.length, mtime: new Date()}), + getMeta: () => ({type: 'text/javascript', size: Buffer.byteLength(javascript), mtime: new Date()}), }) } 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 02508a5ee6..a623668f62 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 @@ -638,6 +638,32 @@ describe('setupDevServer', () => { `) }) + test('serves compiled_assets scripts with correct content-length when content contains multi-byte characters', async () => { + const snippetFile = { + key: 'snippets/multibyte-snippet.liquid', + checksum: 'multibyte1', + value: ` +
+ {% javascript %} + // ...not full cart — derive count from response + console.log("Hey, hey, heeeey!") + {% endjavascript %} +
`, + } + + localThemeFileSystem.files.set(snippetFile.key, snippetFile) + + const {res, body} = await dispatchEvent('/cdn/somepath/compiled_assets/snippet-scripts.js') + + const bodyString = body.toString() + const bodyByteLength = Buffer.byteLength(bodyString) + + // The dash (—) is 3 bytes in UTF-8 but only 1 character. + // content-length must reflect the byte length, not the character count. + expect(bodyByteLength).toBeGreaterThan(bodyString.length) + expect(res.getHeader('content-length')).toBe(bodyByteLength) + }) + test('forwards unknown compiled_assets requests to SFR', async () => { const fetchStub = vi.fn(async () => new Response()) vi.stubGlobal('fetch', fetchStub)