Skip to content

Commit 8cc1cfa

Browse files
makkarpovatinux
andauthoredJul 11, 2022
fix: monkey-patch Vite HMR issue & make it reproducible (#464) (#496)
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
1 parent ecb1160 commit 8cc1cfa

File tree

9 files changed

+1538
-2387
lines changed

9 files changed

+1538
-2387
lines changed
 

‎package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,27 @@
2727
"dependencies": {
2828
"@nuxt/kit": "^3.0.0-rc.4",
2929
"@nuxt/postcss8": "^1.1.3",
30-
"@types/tailwindcss": "^3.0.10",
30+
"@types/tailwindcss": "^3.0.11",
3131
"autoprefixer": "^10.4.7",
3232
"chalk": "^5.0.1",
3333
"clear-module": "^4.1.2",
3434
"consola": "^2.15.3",
3535
"defu": "^6.0.0",
3636
"postcss": "^8.4.14",
3737
"postcss-custom-properties": "^12.1.8",
38-
"postcss-nesting": "^10.1.8",
38+
"postcss-nesting": "^10.1.10",
3939
"tailwind-config-viewer": "^1.7.1",
40-
"tailwindcss": "^3.1.4",
41-
"ufo": "^0.8.4"
40+
"tailwindcss": "^3.1.5",
41+
"ufo": "^0.8.5"
4242
},
4343
"devDependencies": {
44+
"@fontsource/inter": "^4.5.11",
4445
"@nuxt/module-builder": "latest",
4546
"@nuxt/test-utils": "latest",
4647
"@nuxtjs/eslint-config-typescript": "latest",
4748
"codecov": "latest",
4849
"eslint": "latest",
49-
"nuxt": "^3.0.0-rc.4",
50+
"nuxt": "npm:nuxt3@latest",
Has a conversation. Original line has a conversation.
5051
"standard-version": "latest"
5152
}
5253
}

‎playground/app.vue

-14
This file was deleted.

‎playground/nuxt.config.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,13 @@ export default defineNuxtConfig({
77
],
88
tailwindcss: {
99
exposeConfig: true
10-
}
10+
},
11+
css: [
12+
// Including Inter CSS is the first component to reproduce HMR issue. It also causes playground to look better,
13+
// since Inter is a native font for Tailwind UI
14+
'@fontsource/inter/400.css',
15+
'@fontsource/inter/500.css',
16+
'@fontsource/inter/600.css',
17+
'@fontsource/inter/700.css'
18+
]
1119
})

‎playground/pages/index.vue

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<template>
2+
<div>
3+
<div>
4+
<CallToAction />
5+
</div>
6+
<div class="max-w-screen-lg p-4 mx-auto space-y-4">
7+
<div>
8+
<span class="pr-1 font-medium">This is a HMR test, try changing the color:</span>
9+
<span class="text-indigo-500">meow!</span>
10+
</div>
11+
<div>
12+
<span class="text-sm font-semibold text-gray-700">Resolved tailwind config:</span>
13+
<textarea
14+
class="p-4 block text-sm border border-gray-100 rounded shadow w-full h-[32rem] font-mono outline-none"
15+
readonly
16+
:value="JSON.stringify(tailwindConfig, null, 2)"
17+
/>
18+
</div>
19+
</div>
20+
</div>
21+
</template>
22+
23+
<script setup lang="ts">
24+
import tailwindConfig from '#tailwind-config'
25+
</script>

‎playground/tailwind.config.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default {
2+
theme: {
3+
extend: {
4+
fontFamily: {
5+
sans: 'Inter, ui-sans-serif, system-ui, -apple-system, Arial, Roboto, sans-serif'
6+
}
7+
}
8+
}
9+
}

‎playground/tailwind.config.ts

-6
This file was deleted.

‎src/hmr.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { isAbsolute, resolve } from 'path'
2+
import { HmrContext, Plugin } from 'vite'
3+
import minimatch from 'minimatch'
4+
5+
export default function (tailwindConfig, rootDir: string, cssPath: string): Plugin {
6+
const resolvedContent: string[] = tailwindConfig.content.map(f => !isAbsolute(f) ? resolve(rootDir, f) : f)
7+
8+
return {
9+
name: 'nuxt:tailwindcss',
10+
handleHotUpdate (ctx: HmrContext): void {
11+
if (resolvedContent.findIndex(c => minimatch(ctx.file, c)) === -1) {
12+
return
13+
}
14+
15+
const extraModules = ctx.server.moduleGraph.getModulesByFile(cssPath)
16+
const timestamp = +Date.now()
17+
18+
for (const mod of extraModules) {
19+
// This will invalidate Vite cache (e.g. next page reload will be fine), but won't help with HMR on its own
20+
ctx.server.moduleGraph.invalidateModule(mod, undefined, timestamp)
21+
}
22+
23+
// Surely, this is not the best way to fix that, especially given direct `send` call bypassing all Vite logic.
24+
// But just returning extra modules does not cause Vite to send the update, and I didn't find a way to trigger
25+
// that update manually other than sending it directly
26+
27+
ctx.server.ws.send({
28+
type: 'update',
29+
updates: Array.from(extraModules).map((mod) => {
30+
return {
31+
type: mod.type === 'js' ? 'js-update' : 'css-update',
32+
path: mod.url,
33+
acceptedPath: mod.url,
34+
timestamp
35+
}
36+
})
37+
})
38+
}
39+
}
40+
};

‎src/module.ts

+19-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import {
1111
requireModule,
1212
isNuxt2,
1313
createResolver,
14-
resolvePath
14+
resolvePath,
15+
addVitePlugin
1516
} from '@nuxt/kit'
1617
import { name, version } from '../package.json'
18+
import vitePlugin from './hmr'
1719
import defaultTailwindConfig from './tailwind.config'
1820

1921
const logger = consola.withScope('nuxt:tailwindcss')
@@ -34,25 +36,32 @@ export default defineNuxtModule({
3436
config: defaultTailwindConfig(nuxt.options),
3537
viewer: true,
3638
exposeConfig: false,
37-
injectPosition: 0
39+
injectPosition: 0,
40+
disableHmrHotfix: false
3841
}),
3942
async setup (moduleOptions, nuxt) {
4043
const configPath = await resolvePath(moduleOptions.configPath)
4144
const cssPath = await resolvePath(moduleOptions.cssPath, { extensions: ['.css', '.sass', '.scss', '.less', '.styl'] })
4245
const injectPosition = ~~Math.min(moduleOptions.injectPosition, (nuxt.options.css || []).length + 1)
4346

4447
// Include CSS file in project css
48+
let resolvedCss: string
4549
if (typeof cssPath === 'string') {
4650
if (existsSync(cssPath)) {
4751
logger.info(`Using Tailwind CSS from ~/${relative(nuxt.options.srcDir, cssPath)}`)
48-
nuxt.options.css.splice(injectPosition, 0, cssPath)
52+
resolvedCss = cssPath
4953
} else {
5054
logger.info('Using default Tailwind CSS file from runtime/tailwind.css')
51-
const resolver = createResolver(import.meta.url)
52-
nuxt.options.css.splice(injectPosition, 0, resolver.resolve('runtime/tailwind.css'))
55+
resolvedCss = createResolver(import.meta.url).resolve('runtime/tailwind.css')
5356
}
5457
}
5558

59+
// Inject only if this file isn't listed already by user (e.g. user may put custom path both here and in css):
60+
const resolvedNuxtCss = await Promise.all(nuxt.options.css.map(p => resolvePath(p)))
61+
if (!resolvedNuxtCss.includes(resolvedCss)) {
62+
nuxt.options.css.splice(injectPosition, 0, resolvedCss)
63+
}
64+
5665
// Extend the Tailwind config
5766
let tailwindConfig: any = {}
5867
if (existsSync(configPath)) {
@@ -111,6 +120,11 @@ export default defineNuxtModule({
111120
await installModule('@nuxt/postcss8')
112121
}
113122

123+
if (nuxt.options.dev && !moduleOptions.disableHmrHotfix) {
124+
// Insert Vite plugin to work around HMR issue
125+
addVitePlugin(vitePlugin(tailwindConfig, nuxt.options.rootDir, resolvedCss))
126+
}
127+
114128
// Add _tailwind config viewer endpoint
115129
if (nuxt.options.dev && moduleOptions.viewer) {
116130
const route = '/_tailwind'

‎yarn.lock

+1,430-2,356
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.