Skip to content

Commit

Permalink
fix: monkey-patch Vite HMR issue & make it reproducible (#464) (#496)
Browse files Browse the repository at this point in the history
Co-authored-by: Sébastien Chopin <[email protected]>
  • Loading branch information
makkarpov and atinux authored Jul 11, 2022
1 parent ecb1160 commit 8cc1cfa
Show file tree
Hide file tree
Showing 9 changed files with 1,538 additions and 2,387 deletions.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,27 @@
"dependencies": {
"@nuxt/kit": "^3.0.0-rc.4",
"@nuxt/postcss8": "^1.1.3",
"@types/tailwindcss": "^3.0.10",
"@types/tailwindcss": "^3.0.11",
"autoprefixer": "^10.4.7",
"chalk": "^5.0.1",
"clear-module": "^4.1.2",
"consola": "^2.15.3",
"defu": "^6.0.0",
"postcss": "^8.4.14",
"postcss-custom-properties": "^12.1.8",
"postcss-nesting": "^10.1.8",
"postcss-nesting": "^10.1.10",
"tailwind-config-viewer": "^1.7.1",
"tailwindcss": "^3.1.4",
"ufo": "^0.8.4"
"tailwindcss": "^3.1.5",
"ufo": "^0.8.5"
},
"devDependencies": {
"@fontsource/inter": "^4.5.11",
"@nuxt/module-builder": "latest",
"@nuxt/test-utils": "latest",
"@nuxtjs/eslint-config-typescript": "latest",
"codecov": "latest",
"eslint": "latest",
"nuxt": "^3.0.0-rc.4",
"nuxt": "npm:nuxt3@latest",

This comment has been minimized.

Copy link
@productdevbook

productdevbook Jul 11, 2022

looks like this is wrong

"standard-version": "latest"
}
}
14 changes: 0 additions & 14 deletions playground/app.vue

This file was deleted.

10 changes: 9 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,13 @@ export default defineNuxtConfig({
],
tailwindcss: {
exposeConfig: true
}
},
css: [
// Including Inter CSS is the first component to reproduce HMR issue. It also causes playground to look better,
// since Inter is a native font for Tailwind UI
'@fontsource/inter/400.css',
'@fontsource/inter/500.css',
'@fontsource/inter/600.css',
'@fontsource/inter/700.css'
]
})
25 changes: 25 additions & 0 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<div>
<div>
<CallToAction />
</div>
<div class="max-w-screen-lg p-4 mx-auto space-y-4">
<div>
<span class="pr-1 font-medium">This is a HMR test, try changing the color:</span>
<span class="text-indigo-500">meow!</span>
</div>
<div>
<span class="text-sm font-semibold text-gray-700">Resolved tailwind config:</span>
<textarea
class="p-4 block text-sm border border-gray-100 rounded shadow w-full h-[32rem] font-mono outline-none"
readonly
:value="JSON.stringify(tailwindConfig, null, 2)"
/>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import tailwindConfig from '#tailwind-config'
</script>
9 changes: 9 additions & 0 deletions playground/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
theme: {
extend: {
fontFamily: {
sans: 'Inter, ui-sans-serif, system-ui, -apple-system, Arial, Roboto, sans-serif'
}
}
}
}
6 changes: 0 additions & 6 deletions playground/tailwind.config.ts

This file was deleted.

40 changes: 40 additions & 0 deletions src/hmr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { isAbsolute, resolve } from 'path'
import { HmrContext, Plugin } from 'vite'
import minimatch from 'minimatch'

export default function (tailwindConfig, rootDir: string, cssPath: string): Plugin {
const resolvedContent: string[] = tailwindConfig.content.map(f => !isAbsolute(f) ? resolve(rootDir, f) : f)

return {
name: 'nuxt:tailwindcss',
handleHotUpdate (ctx: HmrContext): void {
if (resolvedContent.findIndex(c => minimatch(ctx.file, c)) === -1) {
return
}

const extraModules = ctx.server.moduleGraph.getModulesByFile(cssPath)
const timestamp = +Date.now()

for (const mod of extraModules) {
// This will invalidate Vite cache (e.g. next page reload will be fine), but won't help with HMR on its own
ctx.server.moduleGraph.invalidateModule(mod, undefined, timestamp)
}

// Surely, this is not the best way to fix that, especially given direct `send` call bypassing all Vite logic.
// But just returning extra modules does not cause Vite to send the update, and I didn't find a way to trigger
// that update manually other than sending it directly

ctx.server.ws.send({
type: 'update',
updates: Array.from(extraModules).map((mod) => {
return {
type: mod.type === 'js' ? 'js-update' : 'css-update',
path: mod.url,
acceptedPath: mod.url,
timestamp
}
})
})
}
}
};
24 changes: 19 additions & 5 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import {
requireModule,
isNuxt2,
createResolver,
resolvePath
resolvePath,
addVitePlugin
} from '@nuxt/kit'
import { name, version } from '../package.json'
import vitePlugin from './hmr'
import defaultTailwindConfig from './tailwind.config'

const logger = consola.withScope('nuxt:tailwindcss')
Expand All @@ -34,25 +36,32 @@ export default defineNuxtModule({
config: defaultTailwindConfig(nuxt.options),
viewer: true,
exposeConfig: false,
injectPosition: 0
injectPosition: 0,
disableHmrHotfix: false
}),
async setup (moduleOptions, nuxt) {
const configPath = await resolvePath(moduleOptions.configPath)
const cssPath = await resolvePath(moduleOptions.cssPath, { extensions: ['.css', '.sass', '.scss', '.less', '.styl'] })
const injectPosition = ~~Math.min(moduleOptions.injectPosition, (nuxt.options.css || []).length + 1)

// Include CSS file in project css
let resolvedCss: string
if (typeof cssPath === 'string') {
if (existsSync(cssPath)) {
logger.info(`Using Tailwind CSS from ~/${relative(nuxt.options.srcDir, cssPath)}`)
nuxt.options.css.splice(injectPosition, 0, cssPath)
resolvedCss = cssPath
} else {
logger.info('Using default Tailwind CSS file from runtime/tailwind.css')
const resolver = createResolver(import.meta.url)
nuxt.options.css.splice(injectPosition, 0, resolver.resolve('runtime/tailwind.css'))
resolvedCss = createResolver(import.meta.url).resolve('runtime/tailwind.css')
}
}

// Inject only if this file isn't listed already by user (e.g. user may put custom path both here and in css):
const resolvedNuxtCss = await Promise.all(nuxt.options.css.map(p => resolvePath(p)))
if (!resolvedNuxtCss.includes(resolvedCss)) {
nuxt.options.css.splice(injectPosition, 0, resolvedCss)
}

// Extend the Tailwind config
let tailwindConfig: any = {}
if (existsSync(configPath)) {
Expand Down Expand Up @@ -111,6 +120,11 @@ export default defineNuxtModule({
await installModule('@nuxt/postcss8')
}

if (nuxt.options.dev && !moduleOptions.disableHmrHotfix) {
// Insert Vite plugin to work around HMR issue
addVitePlugin(vitePlugin(tailwindConfig, nuxt.options.rootDir, resolvedCss))
}

// Add _tailwind config viewer endpoint
if (nuxt.options.dev && moduleOptions.viewer) {
const route = '/_tailwind'
Expand Down
Loading

0 comments on commit 8cc1cfa

Please sign in to comment.