Skip to content

Commit 1b848f3

Browse files
thecrypticacephilipp-spiess
authored andcommitted
Make getModuleDependencies async
1 parent 61429d0 commit 1b848f3

File tree

4 files changed

+60
-39
lines changed

4 files changed

+60
-39
lines changed

packages/@tailwindcss-cli/src/commands/build/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
143143

144144
let resolvedPath = path.resolve(inputBasePath, pluginPath)
145145
fullRebuildPaths.push(resolvedPath)
146-
fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
146+
fullRebuildPaths.push(...(await getModuleDependencies(resolvedPath)))
147147
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
148148
(m) => m.default ?? m,
149149
)
@@ -156,7 +156,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
156156

157157
let resolvedPath = path.resolve(inputBasePath, configPath)
158158
fullRebuildPaths.push(resolvedPath)
159-
fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
159+
fullRebuildPaths.push(...(await getModuleDependencies(resolvedPath)))
160160
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
161161
(m) => m.default ?? m,
162162
)
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import fs from 'node:fs'
1+
import fs from 'node:fs/promises'
22
import path from 'node:path'
33

4-
let jsExtensions = ['.js', '.cjs', '.mjs']
4+
// Patterns we use to match dependencies in a file whether in CJS, ESM, or TypeScript
5+
const DEPENDENCY_PATTERNS = [
6+
/import[\s\S]*?['"](.{3,}?)['"]/gi,
7+
/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi,
8+
/export[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi,
9+
/require\(['"`](.+)['"`]\)/gi,
10+
]
511

612
// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve
713
// `b.ts` before `b.js`
@@ -13,73 +19,88 @@ let jsExtensions = ['.js', '.cjs', '.mjs']
1319
// c // .ts
1420
// a.js
1521
// b // .js or .ts
16-
22+
let jsExtensions = ['.js', '.cjs', '.mjs']
1723
let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx']
1824
let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx']
1925

20-
function resolveWithExtension(file: string, extensions: string[]) {
26+
async function resolveWithExtension(file: string, extensions: string[]) {
2127
// Try to find `./a.ts`, `./a.cts`, ... from `./a`
2228
for (let ext of extensions) {
2329
let full = `${file}${ext}`
24-
if (fs.existsSync(full) && fs.statSync(full).isFile()) {
25-
return full
26-
}
30+
31+
let stats = await fs.stat(full).catch(() => null)
32+
if (stats?.isFile()) return full
2733
}
2834

2935
// Try to find `./a/index.js` from `./a`
3036
for (let ext of extensions) {
3137
let full = `${file}/index${ext}`
32-
if (fs.existsSync(full)) {
38+
39+
let exists = await fs.access(full).then(
40+
() => true,
41+
() => false,
42+
)
43+
if (exists) {
3344
return full
3445
}
3546
}
3647

3748
return null
3849
}
3950

40-
function* _getModuleDependencies(
51+
async function traceDependencies(
52+
seen: Set<string>,
4153
filename: string,
4254
base: string,
43-
seen: Set<string>,
44-
ext = path.extname(filename),
45-
): Iterable<string> {
55+
ext: string,
56+
): Promise<void> {
4657
// Try to find the file
47-
let absoluteFile = resolveWithExtension(
48-
path.resolve(base, filename),
49-
jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder,
50-
)
58+
let extensions = jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder
59+
let absoluteFile = await resolveWithExtension(path.resolve(base, filename), extensions)
5160
if (absoluteFile === null) return // File doesn't exist
5261

5362
// Prevent infinite loops when there are circular dependencies
5463
if (seen.has(absoluteFile)) return // Already seen
55-
seen.add(absoluteFile)
5664

5765
// Mark the file as a dependency
58-
yield absoluteFile
66+
seen.add(absoluteFile)
5967

6068
// Resolve new base for new imports/requires
6169
base = path.dirname(absoluteFile)
6270
ext = path.extname(absoluteFile)
6371

64-
let contents = fs.readFileSync(absoluteFile, 'utf-8')
72+
let contents = await fs.readFile(absoluteFile, 'utf-8')
73+
74+
// Recursively trace dependencies in parallel
75+
let promises = []
6576

66-
// Find imports/requires
67-
for (let match of [
68-
...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
69-
...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
70-
...contents.matchAll(/export[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
71-
...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
72-
]) {
73-
// Bail out if it's not a relative file
74-
if (!match[1].startsWith('.')) continue
77+
for (let pattern of DEPENDENCY_PATTERNS) {
78+
for (let match of contents.matchAll(pattern)) {
79+
// Bail out if it's not a relative file
80+
if (!match[1].startsWith('.')) continue
7581

76-
yield* _getModuleDependencies(match[1], base, seen, ext)
82+
promises.push(traceDependencies(seen, match[1], base, ext))
83+
}
7784
}
85+
86+
await Promise.all(promises)
7887
}
7988

80-
export function getModuleDependencies(absoluteFilePath: string) {
81-
if (absoluteFilePath === null) return new Set<string>()
82-
return new Set(
83-
_getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set()),
89+
/**
90+
* Trace all dependencies of a module recursively
91+
*
92+
* The result in an unordered set of absolute file paths. Meaning that the order
93+
* is not guaranteed to be equal to source order or across runs.
94+
**/
95+
export async function getModuleDependencies(absoluteFilePath: string) {
96+
let seen = new Set<string>()
97+
98+
await traceDependencies(
99+
seen,
100+
absoluteFilePath,
101+
path.dirname(absoluteFilePath),
102+
path.extname(absoluteFilePath),
84103
)
104+
105+
return seen
85106
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
9494

9595
let resolvedPath = path.resolve(inputBasePath, pluginPath)
9696
context.fullRebuildPaths.push(resolvedPath)
97-
context.fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
97+
context.fullRebuildPaths.push(...(await getModuleDependencies(resolvedPath)))
9898
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
9999
(m) => m.default ?? m,
100100
)
@@ -107,7 +107,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
107107

108108
let resolvedPath = path.resolve(inputBasePath, configPath)
109109
context.fullRebuildPaths.push(resolvedPath)
110-
context.fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
110+
context.fullRebuildPaths.push(...(await getModuleDependencies(resolvedPath)))
111111
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
112112
(m) => m.default ?? m,
113113
)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export default function tailwindcss(): Plugin[] {
9696
let resolvedPath = path.resolve(inputBasePath, pluginPath)
9797
addWatchFile(resolvedPath)
9898
fullRebuildPaths.push(resolvedPath)
99-
for (let file of getModuleDependencies(resolvedPath)) {
99+
for (let file of await getModuleDependencies(resolvedPath)) {
100100
addWatchFile(file)
101101
fullRebuildPaths.push(file)
102102
}
@@ -113,7 +113,7 @@ export default function tailwindcss(): Plugin[] {
113113
let resolvedPath = path.resolve(inputBasePath, configPath)
114114
addWatchFile(resolvedPath)
115115
fullRebuildPaths.push(resolvedPath)
116-
for (let file of getModuleDependencies(resolvedPath)) {
116+
for (let file of await getModuleDependencies(resolvedPath)) {
117117
addWatchFile(file)
118118
fullRebuildPaths.push(file)
119119
}

0 commit comments

Comments
 (0)