fix(dev): avoid FOUC when swapping out link tag (fix #7973) (#8495) · vitejs/vite@01fa807

4 files changed

lines changed

Original file line numberDiff line numberDiff line change

@@ -161,6 +161,20 @@ if (!isBuild) {

161161

expect(textpost).not.toMatch('direct')

162162

})

163163
164+

test('it swaps out link tags', async () => {

165+

await page.goto(viteTestUrl)

166+
167+

editFile('global.css', (code) => code.replace('white', 'tomato'))

168+
169+

let el = await page.$('.link-tag-added')

170+

await untilUpdated(() => el.textContent(), 'yes')

171+
172+

el = await page.$('.link-tag-removed')

173+

await untilUpdated(() => el.textContent(), 'yes')

174+
175+

expect((await page.$$('link')).length).toBe(1)

176+

})

177+
164178

test('not loaded dynamic import', async () => {

165179

await page.goto(viteTestUrl + '/counter/index.html')

166180
Original file line numberDiff line numberDiff line change

@@ -41,12 +41,42 @@ if (import.meta.hot) {

4141

update.type === 'css-update' && update.path.match('global.css')

4242

)

4343

if (cssUpdate) {

44-

const el = document.querySelector('#global-css') as HTMLLinkElement

45-

text('.css-prev', el.href)

46-

// We don't have a vite:afterUpdate event, but updates are currently sync

47-

setTimeout(() => {

48-

text('.css-post', el.href)

49-

}, 0)

44+

text(

45+

'.css-prev',

46+

(document.querySelector('.global-css') as HTMLLinkElement).href

47+

)

48+
49+

// We don't have a vite:afterUpdate event.

50+

// We need to wait until the tag has been swapped out, which

51+

// includes the time taken to download and parse the new stylesheet.

52+

const observer = new MutationObserver((mutations) => {

53+

mutations.forEach((mutation) => {

54+

mutation.addedNodes.forEach((node) => {

55+

if (

56+

node.nodeType === Node.ELEMENT_NODE &&

57+

(node as Element).tagName === 'LINK'

58+

) {

59+

text('.link-tag-added', 'yes')

60+

}

61+

})

62+

mutation.removedNodes.forEach((node) => {

63+

if (

64+

node.nodeType === Node.ELEMENT_NODE &&

65+

(node as Element).tagName === 'LINK'

66+

) {

67+

text('.link-tag-removed', 'yes')

68+

text(

69+

'.css-post',

70+

(document.querySelector('.global-css') as HTMLLinkElement).href

71+

)

72+

}

73+

})

74+

})

75+

})

76+
77+

observer.observe(document.querySelector('#style-tags-wrapper'), {

78+

childList: true

79+

})

5080

}

5181

})

5282
Original file line numberDiff line numberDiff line change

@@ -1,4 +1,10 @@

1-

<link id="global-css" rel="stylesheet" href="./global.css?param=required" />

1+

<div id="style-tags-wrapper">

2+

<link

3+

class="global-css"

4+

rel="stylesheet"

5+

href="./global.css?param=required"

6+

/>

7+

</div>

28

<script type="module" src="./hmr.ts"></script>

39

<style>

410

.import-image {

@@ -16,4 +22,6 @@

1622

<div class="custom-communication"></div>

1723

<div class="css-prev"></div>

1824

<div class="css-post"></div>

25+

<div class="link-tag-added">no</div>

26+

<div class="link-tag-removed">no</div>

1927

<div class="import-image"></div>

Original file line numberDiff line numberDiff line change

@@ -87,7 +87,18 @@ async function handleMessage(payload: HMRPayload) {

8787

const newPath = `${base}${searchUrl.slice(1)}${

8888

searchUrl.includes('?') ? '&' : '?'

8989

}t=${timestamp}`

90-

el.href = new URL(newPath, el.href).href

90+
91+

// rather than swapping the href on the existing tag, we will

92+

// create a new link tag. Once the new stylesheet has loaded we

93+

// will remove the existing link tag. This removes a Flash Of

94+

// Unstyled Content that can occur when swapping out the tag href

95+

// directly, as the new stylesheet has not yet been loaded.

96+

const newLinkTag = el.cloneNode() as HTMLLinkElement

97+

newLinkTag.href = new URL(newPath, el.href).href

98+

const removeOldEl = () => el.remove()

99+

newLinkTag.addEventListener('load', removeOldEl)

100+

newLinkTag.addEventListener('error', removeOldEl)

101+

el.after(newLinkTag)

91102

}

92103

console.log(`[vite] css hot updated: ${searchUrl}`)

93104

}