@@ -4,10 +4,11 @@ import type {
44 Plugin ,
55 ResolvedConfig ,
66 ConfigEnv ,
7- ViteDevServer ,
87 PluginOption ,
98 TransformResult ,
109 UserConfig ,
10+ ModuleNode ,
11+ ViteDevServer ,
1112} from 'vite' ;
1213import { type Compiler , createCompiler } from '@vanilla-extract/compiler' ;
1314import {
@@ -70,6 +71,8 @@ export function vanillaExtractPlugin({
7071 let isBuild : boolean ;
7172 const vitePromise = import ( 'vite' ) ;
7273
74+ const transformedModules = new Set < string > ( ) ;
75+
7376 const getIdentOption = ( ) =>
7477 identifiers ?? ( config . mode === 'production' ? 'short' : 'debug' ) ;
7578 const getAbsoluteId = ( filePath : string ) => {
@@ -92,22 +95,43 @@ export function vanillaExtractPlugin({
9295 return normalizePath ( resolvedId ) ;
9396 } ;
9497
95- function invalidateModule ( absoluteId : string ) {
96- if ( ! server ) return ;
97-
98+ /**
99+ * Custom invalidation function that takes a chain of importers to invalidate. If an importer is a
100+ * VE module, its virtual CSS is invalidated instead. Otherwise, the module is invalidated
101+ * normally.
102+ */
103+ const invalidateImporterChain = ( {
104+ importerChain,
105+ server,
106+ timestamp,
107+ } : {
108+ importerChain : Set < ModuleNode > ;
109+ server : ViteDevServer ;
110+ timestamp : number ;
111+ } ) => {
98112 const { moduleGraph } = server ;
99- const modules = moduleGraph . getModulesByFile ( absoluteId ) ;
100113
101- if ( modules ) {
102- for ( const module of modules ) {
103- moduleGraph . invalidateModule ( module ) ;
114+ const seen = new Set < ModuleNode > ( ) ;
104115
105- // Vite uses this timestamp to add `?t=` query string automatically for HMR.
106- module . lastHMRTimestamp =
107- module . lastInvalidationTimestamp || Date . now ( ) ;
116+ for ( const mod of importerChain ) {
117+ if ( mod . id && cssFileFilter . test ( mod . id ) ) {
118+ const virtualModules = moduleGraph . getModulesByFile (
119+ fileIdToVirtualId ( mod . id ) ,
120+ ) ;
121+
122+ for ( const virtualModule of virtualModules ?? [ ] ) {
123+ moduleGraph . invalidateModule ( virtualModule , seen , timestamp , true ) ;
124+ }
125+ } else if ( mod . id ) {
126+ // `mod` is from the compiler's internal Vite server, so look up the
127+ // corresponding module in the consuming server's graph by ID
128+ const serverMod = moduleGraph . getModuleById ( mod . id ) ;
129+ if ( serverMod ) {
130+ moduleGraph . invalidateModule ( serverMod , seen , timestamp , true ) ;
131+ }
108132 }
109133 }
110- }
134+ } ;
111135
112136 return [
113137 {
@@ -138,6 +162,10 @@ export function vanillaExtractPlugin({
138162 name : PLUGIN_NAMESPACE ,
139163 configureServer ( _server ) {
140164 server = _server ;
165+
166+ server . watcher . on ( 'unlink' , ( file ) => {
167+ transformedModules . delete ( normalizePath ( file ) ) ;
168+ } ) ;
141169 } ,
142170 config ( _userConfig , _configEnv ) {
143171 configEnv = _configEnv ;
@@ -151,7 +179,7 @@ export function vanillaExtractPlugin({
151179 } ,
152180 } ;
153181 } ,
154- async configResolved ( _resolvedConfig ) {
182+ configResolved ( _resolvedConfig ) {
155183 config = _resolvedConfig ;
156184 isBuild = config . command === 'build' && ! config . build . watch ;
157185 packageName = getPackageInfo ( config . root ) . name ;
@@ -218,52 +246,75 @@ export function vanillaExtractPlugin({
218246 }
219247
220248 const identOption = getIdentOption ( ) ;
249+ const normalizedId = normalizePath ( validId ) ;
221250
222251 if ( unstable_mode === 'transform' ) {
252+ transformedModules . add ( normalizedId ) ;
253+
223254 return transform ( {
224255 source : code ,
225- filePath : normalizePath ( validId ) ,
256+ filePath : normalizedId ,
226257 rootPath : config . root ,
227258 packageName,
228259 identOption,
229260 } ) ;
230261 }
231262
232- if ( compiler ) {
233- const absoluteId = getAbsoluteId ( validId ) ;
263+ if ( ! compiler ) {
264+ return null ;
265+ }
234266
235- const { source, watchFiles } = await compiler . processVanillaFile (
236- absoluteId ,
237- { outputCss : true } ,
238- ) ;
239- const result : TransformResult = {
240- code : source ,
241- map : { mappings : '' } ,
242- } ;
267+ const absoluteId = getAbsoluteId ( validId ) ;
243268
244- // We don't need to watch files or invalidate modules in build mode or during SSR
245- if ( isBuild || options ?. ssr ) {
246- return result ;
247- }
269+ const { source , watchFiles } = await compiler . processVanillaFile (
270+ absoluteId ,
271+ { outputCss : true } ,
272+ ) ;
248273
249- for ( const file of watchFiles ) {
250- if (
251- ! file . includes ( 'node_modules' ) &&
252- normalizePath ( file ) !== absoluteId
253- ) {
254- this . addWatchFile ( file ) ;
255- }
256-
257- // We have to invalidate the virtual module & deps, not the real one we just transformed
258- // The deps have to be invalidated in case one of them changing was the trigger causing
259- // the current transformation
260- if ( cssFileFilter . test ( file ) ) {
261- invalidateModule ( fileIdToVirtualId ( file ) ) ;
262- }
263- }
274+ transformedModules . add ( normalizedId ) ;
275+
276+ const result : TransformResult = {
277+ code : source ,
278+ map : { mappings : '' } ,
279+ } ;
264280
281+ // We don't need to watch files or invalidate modules in build mode or during SSR
282+ if ( isBuild || options ?. ssr ) {
265283 return result ;
266284 }
285+
286+ for ( const file of watchFiles ) {
287+ if (
288+ ! file . includes ( 'node_modules' ) &&
289+ normalizePath ( file ) !== absoluteId
290+ ) {
291+ this . addWatchFile ( file ) ;
292+ }
293+ }
294+
295+ return result ;
296+ } ,
297+ // The compiler's module graph is always a subset of the consuming Vite dev server's module
298+ // graph, so this early exit will be hit for any modules that aren't related to VE modules.
299+ async handleHotUpdate ( { file, server, timestamp } ) {
300+ if ( ! compiler ) {
301+ return ;
302+ }
303+
304+ const importerChain = await compiler . findImporterTree (
305+ normalizePath ( file ) ,
306+ transformedModules ,
307+ ) ;
308+
309+ if ( importerChain . size === 0 ) {
310+ return ;
311+ }
312+
313+ invalidateImporterChain ( {
314+ importerChain,
315+ server,
316+ timestamp,
317+ } ) ;
267318 } ,
268319 resolveId ( source ) {
269320 const [ validId , query ] = source . split ( '?' ) ;
0 commit comments