Skip to content

Commit 7f4b079

Browse files
Move Root class to the module scope
1 parent b316d8f commit 7f4b079

File tree

1 file changed

+166
-160
lines changed

1 file changed

+166
-160
lines changed

packages/@tailwindcss-vite/src/index.ts

+166-160
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ export default function tailwindcss(): Plugin[] {
2424
// to manually rebuild the css file after the compilation is done.
2525
let cssPlugins: readonly Plugin[] = []
2626

27-
let roots: DefaultMap<string, Root> = new DefaultMap((id) => new Root(id))
28-
2927
// The Vite extension has two types of sources for candidates:
3028
//
3129
// 1. The module graph: These are all modules that vite transforms and we want
@@ -45,164 +43,9 @@ export default function tailwindcss(): Plugin[] {
4543
let moduleGraphCandidates = new Set<string>()
4644
let moduleGraphScanner = new Scanner({})
4745

48-
class Root {
49-
// Content is only used in serve mode where we need to capture the initial
50-
// contents of the root file so that we can restore it during the
51-
// `renderStart` hook.
52-
public lastContent: string = ''
53-
54-
// The lazily-initialized Tailwind compiler components. These are persisted
55-
// throughout rebuilds but will be re-initialized if the rebuild strategy is
56-
// set to `full`.
57-
private compiler?: Awaited<ReturnType<typeof compile>>
58-
59-
private rebuildStrategy: 'full' | 'incremental' = 'full'
60-
61-
// This is the compiler-specific scanner instance that is used only to scan
62-
// files for custom @source paths. All other modules we scan for candidates
63-
// will use the shared moduleGraphScanner instance.
64-
private scanner?: Scanner
65-
66-
// List of all candidates that were being returned by the root scanner
67-
// during the lifetime of the root.
68-
private candidates: Set<string> = new Set<string>()
69-
70-
// List of all file dependencies that were captured while generating the
71-
// root. These are retained so we can clear the require cache when we
72-
// rebuild the root.
73-
private dependencies = new Set<string>()
74-
75-
constructor(private id: string) {}
76-
77-
// Generate the CSS for the root file. This can return false if the file is
78-
// not considered a Tailwind root. When this happened, the root can be GCed.
79-
public async generate(
80-
content: string,
81-
addWatchFile: (file: string) => void,
82-
): Promise<string | false> {
83-
await import('@tailwindcss/node/esm-cache-hook')
84-
85-
this.lastContent = content
86-
87-
let inputPath = idToPath(this.id)
88-
let inputBase = path.dirname(path.resolve(inputPath))
89-
90-
if (this.compiler === null || this.scanner === null || this.rebuildStrategy === 'full') {
91-
this.rebuildStrategy = 'incremental'
92-
clearRequireCache(Array.from(this.dependencies))
93-
this.dependencies = new Set([idToPath(inputPath)])
94-
95-
let postcssCompiled = await postcss([
96-
postcssImport({
97-
load: (path) => {
98-
this.dependencies.add(path)
99-
addWatchFile(path)
100-
return fs.readFile(path, 'utf8')
101-
},
102-
}),
103-
fixRelativePathsPlugin(),
104-
]).process(content, {
105-
from: inputPath,
106-
to: inputPath,
107-
})
108-
let css = postcssCompiled.css
109-
110-
// This is done inside the Root#generate() method so that we can later
111-
// use information from the Tailwind compiler to determine if the file
112-
// is a CSS root (necessary because we will probably inline the
113-
// `@import` resolution at some point).
114-
if (!isCssRootFile(css)) {
115-
return false
116-
}
117-
118-
this.compiler = await compile(css, {
119-
loadPlugin: async (pluginPath) => {
120-
if (pluginPath[0] !== '.') {
121-
return import(pluginPath).then((m) => m.default ?? m)
122-
}
123-
124-
let resolvedPath = path.resolve(inputBase, pluginPath)
125-
let [module, moduleDependencies] = await Promise.all([
126-
import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()),
127-
getModuleDependencies(resolvedPath),
128-
])
129-
addWatchFile(resolvedPath)
130-
this.dependencies.add(resolvedPath)
131-
for (let file of moduleDependencies) {
132-
addWatchFile(file)
133-
this.dependencies.add(file)
134-
}
135-
return module.default ?? module
136-
},
137-
138-
loadConfig: async (configPath) => {
139-
if (configPath[0] !== '.') {
140-
return import(configPath).then((m) => m.default ?? m)
141-
}
142-
143-
let resolvedPath = path.resolve(inputBase, configPath)
144-
let [module, moduleDependencies] = await Promise.all([
145-
import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()),
146-
getModuleDependencies(resolvedPath),
147-
])
148-
149-
addWatchFile(resolvedPath)
150-
this.dependencies.add(resolvedPath)
151-
for (let file of moduleDependencies) {
152-
addWatchFile(file)
153-
this.dependencies.add(file)
154-
}
155-
return module.default ?? module
156-
},
157-
})
158-
this.scanner = new Scanner({
159-
sources: this.compiler.globs.map((pattern) => ({
160-
base: inputBase, // Globs are relative to the input.css file
161-
pattern,
162-
})),
163-
})
164-
}
165-
166-
if (!this.scanner || !this.compiler) {
167-
// TypeScript does not properly refine the scanner and compiler
168-
// properties (even when extracted into a variable)
169-
throw new Error('Tailwind CSS compiler is not initialized.')
170-
}
171-
172-
// This should not be here, but right now the Vite plugin is setup where
173-
// we setup a new scanner and compiler every time we request the CSS file
174-
// (regardless whether it actually changed or not).
175-
for (let candidate of this.scanner.scan()) {
176-
this.candidates.add(candidate)
177-
}
178-
179-
// Watch individual files found via custom `@source` paths
180-
for (let file of this.scanner.files) {
181-
addWatchFile(file)
182-
}
183-
184-
// Watch globs found via custom `@source` paths
185-
for (let glob of this.scanner.globs) {
186-
if (glob.pattern[0] === '!') continue
187-
188-
let relative = path.relative(config!.base, glob.base)
189-
if (relative[0] !== '.') {
190-
relative = './' + relative
191-
}
192-
// Ensure relative is a posix style path since we will merge it with the
193-
// glob.
194-
relative = normalizePath(relative)
195-
196-
addWatchFile(path.posix.join(relative, glob.pattern))
197-
}
198-
199-
return this.compiler.build([...moduleGraphCandidates, ...this.candidates])
200-
}
201-
202-
public invalidate() {
203-
this.rebuildStrategy = 'full'
204-
}
205-
}
46+
let roots: DefaultMap<string, Root> = new DefaultMap(
47+
(id) => new Root(id, () => moduleGraphCandidates, config!.base),
48+
)
20649

20750
function scanFile(id: string, content: string, extension: string, isSSR: boolean) {
20851
let updated = false
@@ -487,3 +330,166 @@ class DefaultMap<K, V> extends Map<K, V> {
487330
return value
488331
}
489332
}
333+
334+
class Root {
335+
// Content is only used in serve mode where we need to capture the initial
336+
// contents of the root file so that we can restore it during the
337+
// `renderStart` hook.
338+
public lastContent: string = ''
339+
340+
// The lazily-initialized Tailwind compiler components. These are persisted
341+
// throughout rebuilds but will be re-initialized if the rebuild strategy is
342+
// set to `full`.
343+
private compiler?: Awaited<ReturnType<typeof compile>>
344+
345+
private rebuildStrategy: 'full' | 'incremental' = 'full'
346+
347+
// This is the compiler-specific scanner instance that is used only to scan
348+
// files for custom @source paths. All other modules we scan for candidates
349+
// will use the shared moduleGraphScanner instance.
350+
private scanner?: Scanner
351+
352+
// List of all candidates that were being returned by the root scanner during
353+
// the lifetime of the root.
354+
private candidates: Set<string> = new Set<string>()
355+
356+
// List of all file dependencies that were captured while generating the root.
357+
// These are retained so we can clear the require cache when we rebuild the
358+
// root.
359+
private dependencies = new Set<string>()
360+
361+
constructor(
362+
private id: string,
363+
private getSharedCandidates: () => Set<string>,
364+
private base: string,
365+
) {}
366+
367+
// Generate the CSS for the root file. This can return false if the file is
368+
// not considered a Tailwind root. When this happened, the root can be GCed.
369+
public async generate(
370+
content: string,
371+
addWatchFile: (file: string) => void,
372+
): Promise<string | false> {
373+
await import('@tailwindcss/node/esm-cache-hook')
374+
375+
this.lastContent = content
376+
377+
let inputPath = idToPath(this.id)
378+
let inputBase = path.dirname(path.resolve(inputPath))
379+
380+
if (this.compiler === null || this.scanner === null || this.rebuildStrategy === 'full') {
381+
this.rebuildStrategy = 'incremental'
382+
clearRequireCache(Array.from(this.dependencies))
383+
this.dependencies = new Set([idToPath(inputPath)])
384+
385+
let postcssCompiled = await postcss([
386+
postcssImport({
387+
load: (path) => {
388+
this.dependencies.add(path)
389+
addWatchFile(path)
390+
return fs.readFile(path, 'utf8')
391+
},
392+
}),
393+
fixRelativePathsPlugin(),
394+
]).process(content, {
395+
from: inputPath,
396+
to: inputPath,
397+
})
398+
let css = postcssCompiled.css
399+
400+
// This is done inside the Root#generate() method so that we can later use
401+
// information from the Tailwind compiler to determine if the file is a
402+
// CSS root (necessary because we will probably inline the `@import`
403+
// resolution at some point).
404+
if (!isCssRootFile(css)) {
405+
return false
406+
}
407+
408+
this.compiler = await compile(css, {
409+
loadPlugin: async (pluginPath) => {
410+
if (pluginPath[0] !== '.') {
411+
return import(pluginPath).then((m) => m.default ?? m)
412+
}
413+
414+
let resolvedPath = path.resolve(inputBase, pluginPath)
415+
let [module, moduleDependencies] = await Promise.all([
416+
import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()),
417+
getModuleDependencies(resolvedPath),
418+
])
419+
addWatchFile(resolvedPath)
420+
this.dependencies.add(resolvedPath)
421+
for (let file of moduleDependencies) {
422+
addWatchFile(file)
423+
this.dependencies.add(file)
424+
}
425+
return module.default ?? module
426+
},
427+
428+
loadConfig: async (configPath) => {
429+
if (configPath[0] !== '.') {
430+
return import(configPath).then((m) => m.default ?? m)
431+
}
432+
433+
let resolvedPath = path.resolve(inputBase, configPath)
434+
let [module, moduleDependencies] = await Promise.all([
435+
import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()),
436+
getModuleDependencies(resolvedPath),
437+
])
438+
439+
addWatchFile(resolvedPath)
440+
this.dependencies.add(resolvedPath)
441+
for (let file of moduleDependencies) {
442+
addWatchFile(file)
443+
this.dependencies.add(file)
444+
}
445+
return module.default ?? module
446+
},
447+
})
448+
this.scanner = new Scanner({
449+
sources: this.compiler.globs.map((pattern) => ({
450+
base: inputBase, // Globs are relative to the input.css file
451+
pattern,
452+
})),
453+
})
454+
}
455+
456+
if (!this.scanner || !this.compiler) {
457+
// TypeScript does not properly refine the scanner and compiler properties
458+
// (even when extracted into a variable)
459+
throw new Error('Tailwind CSS compiler is not initialized.')
460+
}
461+
462+
// This should not be here, but right now the Vite plugin is setup where we
463+
// setup a new scanner and compiler every time we request the CSS file
464+
// (regardless whether it actually changed or not).
465+
for (let candidate of this.scanner.scan()) {
466+
this.candidates.add(candidate)
467+
}
468+
469+
// Watch individual files found via custom `@source` paths
470+
for (let file of this.scanner.files) {
471+
addWatchFile(file)
472+
}
473+
474+
// Watch globs found via custom `@source` paths
475+
for (let glob of this.scanner.globs) {
476+
if (glob.pattern[0] === '!') continue
477+
478+
let relative = path.relative(this.base, glob.base)
479+
if (relative[0] !== '.') {
480+
relative = './' + relative
481+
}
482+
// Ensure relative is a posix style path since we will merge it with the
483+
// glob.
484+
relative = normalizePath(relative)
485+
486+
addWatchFile(path.posix.join(relative, glob.pattern))
487+
}
488+
489+
return this.compiler.build([...this.getSharedCandidates(), ...this.candidates])
490+
}
491+
492+
public invalidate() {
493+
this.rebuildStrategy = 'full'
494+
}
495+
}

0 commit comments

Comments
 (0)