diff --git a/.changeset/twenty-monkeys-thank.md b/.changeset/twenty-monkeys-thank.md new file mode 100644 index 000000000000..02ce782ab007 --- /dev/null +++ b/.changeset/twenty-monkeys-thank.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fixes style-only change detection for Astro files if both the markup and styles are updated diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts index 2fca4aff54ad..861fccae4b71 100644 --- a/packages/astro/src/vite-plugin-astro/hmr.ts +++ b/packages/astro/src/vite-plugin-astro/hmr.ts @@ -65,7 +65,7 @@ const scriptRE = /.*?<\/script>/gs; // eslint-disable-next-line regexp/no-super-linear-backtracking const styleRE = /.*?<\/style>/gs; -function isStyleOnlyChanged(oldCode: string, newCode: string) { +export function isStyleOnlyChanged(oldCode: string, newCode: string) { if (oldCode === newCode) return false; // Before we can regex-capture style tags, we remove the frontmatter and scripts @@ -89,9 +89,14 @@ function isStyleOnlyChanged(oldCode: string, newCode: string) { // Finally, we can compare styles const oldStyles: string[] = []; const newStyles: string[] = []; - oldCode.match(styleRE)?.forEach((m) => oldStyles.push(m)); - newCode.match(styleRE)?.forEach((m) => newStyles.push(m)); - // The length must also be the same for style only change. If style tags are added/removed, + oldCode = oldCode.replace(styleRE, (m) => (oldStyles.push(m), '')); + newCode = newCode.replace(styleRE, (m) => (newStyles.push(m), '')); + + // Remaining of `oldCode` and `newCode` is the markup, return false if they're different + if (oldCode !== newCode) return false; + + // Finally, check if only the style changed. + // The length must also be the same for style only change. If style tags are added/removed, // we need to regenerate the main Astro file so that its CSS imports are also added/removed return oldStyles.length === newStyles.length && !isArrayEqual(oldStyles, newStyles); } diff --git a/packages/astro/test/units/vite-plugin-astro/hmr.test.js b/packages/astro/test/units/vite-plugin-astro/hmr.test.js new file mode 100644 index 000000000000..f7f96819246c --- /dev/null +++ b/packages/astro/test/units/vite-plugin-astro/hmr.test.js @@ -0,0 +1,63 @@ +import { describe, it } from 'node:test'; +import * as assert from 'node:assert/strict'; +import { isStyleOnlyChanged } from '../../../dist/vite-plugin-astro/hmr.js'; + +describe('isStyleOnlyChanged', () => { + it('should return false if nothing change', () => { + const oldCode = 'a'; + const newCode = 'a'; + assert.equal(isStyleOnlyChanged(oldCode, newCode), false); + }); + + it('should return false if script has changed', () => { + const oldCode = ''; + const newCode = ''; + assert.equal(isStyleOnlyChanged(oldCode, newCode), false); + }); + + it('should return true if only style has changed', () => { + const oldCode = ''; + const newCode = ''; + assert.equal(isStyleOnlyChanged(oldCode, newCode), true); + }); + + it('should return false if style tags are added or removed', () => { + const oldCode = ''; + const newCode = ''; + assert.equal(isStyleOnlyChanged(oldCode, newCode), false); + }); + + it('should return false if frontmatter has changed', () => { + const oldCode = ` +--- +title: Hello +--- +`; + const newCode = ` +--- +title: Hi +--- +`; + assert.equal(isStyleOnlyChanged(oldCode, newCode), false); + }); + + it('should return false if both frontmatter and style have changed', () => { + const oldCode = ` +--- +title: Hello +--- +`; + const newCode = ` +--- +title: Hi +--- +`; + assert.equal(isStyleOnlyChanged(oldCode, newCode), false); + }); + + it('should return false if both markup and style have changed', () => { + const oldCode = '

Hello

'; + const newCode = '

Hi

'; + assert.equal(isStyleOnlyChanged(oldCode, newCode), false); + }); +});