Skip to content

Commit 54d097a

Browse files
authored
Merge pull request #5768 from Shopify/fix-compiled-assets-issue
Fix an issue with hot-reloading the compiled assets (scripts.js, block-scripts.js, etc)
2 parents 10ba0ae + 93dc1f4 commit 54d097a

File tree

5 files changed

+79
-14
lines changed

5 files changed

+79
-14
lines changed

.changeset/orange-bugs-retire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/theme': patch
3+
---
4+
5+
Fix an issue with hot-reloading the compiled assets (scripts.js, block-scripts.js, etc)

packages/theme/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"yaml": "2.7.0"
5252
},
5353
"devDependencies": {
54-
"@shopify/theme-hot-reload": "^0.0.11",
54+
"@shopify/theme-hot-reload": "^0.0.18",
5555
"@vitest/coverage-istanbul": "^1.6.0",
5656
"node-stream-zip": "^1.15.0"
5757
},

packages/theme/src/cli/utilities/theme-environment/hot-reload/server.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('Hot Reload', () => {
9191

9292
const expectedSectionEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${testSectionFileKey}","payload":{"sectionNames":["first","second"],"replaceTemplates":${JSON.stringify(
9393
getInMemoryTemplates(ctx),
94-
)}},"version":"${HOT_RELOAD_VERSION}"}`
94+
)},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
9595

9696
// Verify local sync event
9797
expect(hotReloadEvents.at(-1)).toMatch(expectedSectionEvent)
@@ -142,7 +142,7 @@ describe('Hot Reload', () => {
142142
// Since this is a template, sectionNames will be empty (no sections to reload)
143143
const expectedTemplateEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${templateKey}","payload":{"sectionNames":[],"replaceTemplates":${JSON.stringify(
144144
getInMemoryTemplates(ctx),
145-
)}},"version":"${HOT_RELOAD_VERSION}"}`
145+
)},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
146146

147147
// Verify local sync event for JSON update
148148
expect(hotReloadEvents.at(-1)).toMatch(expectedTemplateEvent)
@@ -163,7 +163,7 @@ describe('Hot Reload', () => {
163163
// Since this is a section group, sectionNames will contain all the section names
164164
const expectedSectionGroupEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${sectionGroupKey}","payload":{"sectionNames":["first","second"],"replaceTemplates":${JSON.stringify(
165165
getInMemoryTemplates(ctx),
166-
)}},"version":"${HOT_RELOAD_VERSION}"}`
166+
)},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
167167

168168
// Verify local sync event for JSON update
169169
expect(hotReloadEvents.at(-1)).toMatch(expectedSectionGroupEvent)
@@ -178,7 +178,7 @@ describe('Hot Reload', () => {
178178
expect(hotReloadEvents.at(-1)).toMatch(
179179
`data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${anotherSectionKey}","payload":{"sectionNames":["first","second"],"replaceTemplates":${JSON.stringify(
180180
getInMemoryTemplates(ctx),
181-
)}},"version":"${HOT_RELOAD_VERSION}"}`,
181+
)},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`,
182182
)
183183
// Wait for remote sync
184184
await nextTick()
@@ -197,7 +197,7 @@ describe('Hot Reload', () => {
197197
expect(getInMemoryTemplates(ctx)).toEqual({[anotherSectionKey]: 'default-value'})
198198
const expectedUnreferencedSectionEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${anotherSectionKey}","payload":{"sectionNames":[],"replaceTemplates":${JSON.stringify(
199199
getInMemoryTemplates(ctx),
200-
)}},"version":"${HOT_RELOAD_VERSION}"}`
200+
)},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
201201
expect(hotReloadEvents.at(-1)).toMatch(expectedUnreferencedSectionEvent)
202202
await nextTick()
203203
expect(hotReloadEvents.at(-1)).toMatch(expectedUnreferencedSectionEvent.replace('local', 'remote'))
@@ -207,7 +207,7 @@ describe('Hot Reload', () => {
207207
await triggerFileEvent('add', cssFileKey)
208208
// It does not add assets to the in-memory templates:
209209
expect(getInMemoryTemplates(ctx)).toEqual({})
210-
const expectedCssEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${cssFileKey}","payload":{"sectionNames":[],"replaceTemplates":{}},"version":"${HOT_RELOAD_VERSION}"}`
210+
const expectedCssEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${cssFileKey}","payload":{"sectionNames":[],"replaceTemplates":{},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
211211
expect(hotReloadEvents.at(-1)).toMatch(expectedCssEvent)
212212
// Wait for remote sync
213213
await nextTick()
@@ -218,7 +218,7 @@ describe('Hot Reload', () => {
218218
await triggerFileEvent('add', cssLiquidFileKey)
219219
// It does not add assets to the in-memory templates:
220220
expect(getInMemoryTemplates(ctx)).toEqual({})
221-
const expectedCssLiquidEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${cssLiquidFileKey}","payload":{"sectionNames":[],"replaceTemplates":{}},"version":"${HOT_RELOAD_VERSION}"}`
221+
const expectedCssLiquidEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${cssLiquidFileKey}","payload":{"sectionNames":[],"replaceTemplates":{},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
222222
expect(hotReloadEvents.at(-1)).toMatch(expectedCssLiquidEvent)
223223
// Wait for remote sync
224224
await nextTick()
@@ -227,7 +227,7 @@ describe('Hot Reload', () => {
227227
// -- Test other file types (e.g. JS) --
228228
const jsFileKey = 'assets/something.js'
229229
await triggerFileEvent('add', jsFileKey)
230-
const expectedJsEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${jsFileKey}","payload":{"sectionNames":[],"replaceTemplates":{}},"version":"${HOT_RELOAD_VERSION}"}`
230+
const expectedJsEvent = `data: {"sync":"local","themeId":"${THEME_ID}","type":"update","key":"${jsFileKey}","payload":{"sectionNames":[],"replaceTemplates":{},"updatedFileParts":{"stylesheetTag":false,"javascriptTag":false}},"version":"${HOT_RELOAD_VERSION}"}`
231231
expect(hotReloadEvents.at(-1)).toMatch(expectedJsEvent)
232232
// Wait for remote sync
233233
await nextTick()

packages/theme/src/cli/utilities/theme-environment/hot-reload/server.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@ import {renderError, renderInfo, renderWarning} from '@shopify/cli-kit/node/ui'
1717
import {extname, joinPath} from '@shopify/cli-kit/node/path'
1818
import {parseJSON} from '@shopify/theme-check-node'
1919
import {readFile} from '@shopify/cli-kit/node/fs'
20+
import {NodeTypes, toLiquidHtmlAST, walk} from '@shopify/liquid-html-parser'
2021
import EventEmitter from 'node:events'
2122
import type {
2223
HotReloadEvent,
2324
HotReloadFileEvent,
2425
HotReloadOpenEvent,
2526
HotReloadFullEvent,
2627
} from '@shopify/theme-hot-reload'
27-
import type {Theme, ThemeFSEventPayload} from '@shopify/cli-kit/node/themes/types'
28+
import type {Theme, ThemeAsset, ThemeFSEventPayload} from '@shopify/cli-kit/node/themes/types'
2829
import type {DevServerContext} from '../types.js'
2930

31+
// --- Section tag content cache ---
32+
33+
interface TagContent {
34+
content: string
35+
changed: boolean
36+
}
37+
38+
const tagContentCache = new Map<string, {checksum: string; stylesheet: TagContent; javascript: TagContent}>()
39+
3040
// --- Template Replacers ---
3141

3242
/** Store existing section names and types read from JSON files in the project */
@@ -352,6 +362,7 @@ function collectReloadInfoForFile(key: string, ctx: DevServerContext) {
352362
return {
353363
sectionNames: type === 'sections' ? findSectionNamesToReload(key, ctx) : [],
354364
replaceTemplates: needsTemplateUpdate(key) ? getInMemoryTemplates(ctx) : {},
365+
updatedFileParts: getUpdatedFileParts(key, ctx),
355366
}
356367
}
357368

@@ -392,3 +403,52 @@ export function handleHotReloadScriptInjection(html: string, ctx: DevServerConte
392403
function isAsset(key: string) {
393404
return key.startsWith('assets/')
394405
}
406+
407+
function getUpdatedFileParts(key: string, ctx: DevServerContext): {stylesheetTag: boolean; javascriptTag: boolean} {
408+
const file = ctx.localThemeFileSystem.files.get(key)
409+
const validPrefixes = ['sections/', 'snippets/', 'blocks/']
410+
const isValidFileType = validPrefixes.some((prefix) => key.startsWith(prefix)) && key.endsWith('.liquid')
411+
412+
if (!file || !isValidFileType) {
413+
return {stylesheetTag: false, javascriptTag: false}
414+
}
415+
416+
const tagContents = getTagContents(file)
417+
418+
return {
419+
stylesheetTag: tagContents.stylesheet.changed,
420+
javascriptTag: tagContents.javascript.changed,
421+
}
422+
}
423+
424+
function getTagContents(file: ThemeAsset) {
425+
const cached = tagContentCache.get(file.key)
426+
const cacheEntry = {
427+
checksum: file.checksum,
428+
stylesheet: {content: '', changed: false},
429+
javascript: {content: '', changed: false},
430+
}
431+
432+
if (cached?.checksum === file.checksum) {
433+
return cached
434+
}
435+
436+
tagContentCache.delete(file.key)
437+
438+
if (!file.value) return cacheEntry
439+
440+
walk(toLiquidHtmlAST(file.value), (node) => {
441+
if (node.type !== NodeTypes.LiquidRawTag) return
442+
443+
if (node.name === 'stylesheet' || node.name === 'javascript') {
444+
const content = node.body.value
445+
const changed = !cached || content !== cached[node.name].content
446+
447+
cacheEntry[node.name] = {content, changed}
448+
}
449+
})
450+
451+
tagContentCache.set(file.key, cacheEntry)
452+
453+
return cacheEntry
454+
}

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)