Skip to content

Commit b349227

Browse files
committed
replace detective-typescript with our own implementation
We are not parsing the code but just trying to pluck out the dependencies used via `import` and `require`.
1 parent 6a2c58a commit b349227

File tree

1 file changed

+66
-27
lines changed

1 file changed

+66
-27
lines changed

src/lib/getModuleDependencies.js

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,79 @@
11
import fs from 'fs'
22
import path from 'path'
3-
import resolve from 'resolve'
4-
import detective from 'detective-typescript'
53

6-
function createModule(file) {
7-
let source = fs.readFileSync(file, 'utf-8')
8-
return { file, requires: detective(source, { mixedImports: true }) }
9-
}
10-
11-
function* _getModuleDependencies(entryFile) {
12-
yield entryFile
4+
let jsExtensions = ['.js', '.cjs', '.mjs']
135

14-
let mod = createModule(entryFile)
6+
// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve
7+
// `b.ts` before `b.js`
8+
//
9+
// E.g.:
10+
//
11+
// a.ts
12+
// b // .ts
13+
// c // .ts
14+
// a.js
15+
// b // .js or .ts
1516

16-
let ext = path.extname(entryFile)
17-
let isTypeScript = ext === '.ts' || ext === '.cts' || ext === '.mts'
18-
let extensions = [...(isTypeScript ? ['.ts', '.cts', '.mts'] : []), '.js', '.cjs', '.mjs']
17+
let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx']
18+
let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx']
1919

20-
// Iterate over the modules, even when new
21-
// ones are being added
22-
for (let dep of mod.requires) {
23-
// Only track local modules, not node_modules
24-
if (!dep.startsWith('./') && !dep.startsWith('../')) {
25-
continue
20+
function resolveWithExtension(file, extensions) {
21+
// Try to find `./a.ts`, `./a.ts`, ... from `./a`
22+
for (let ext of extensions) {
23+
let full = `${file}${ext}`
24+
if (fs.existsSync(full) && fs.statSync(full).isFile()) {
25+
return full
2626
}
27+
}
2728

28-
try {
29-
let basedir = path.dirname(mod.file)
30-
let depPath = resolve.sync(dep, { basedir, extensions })
31-
yield* _getModuleDependencies(depPath)
32-
} catch (_err) {
33-
// eslint-disable-next-line no-empty
29+
// Try to find `./a/index.js` from `./a`
30+
for (let ext of extensions) {
31+
let full = `${file}/index${ext}`
32+
if (fs.existsSync(full)) {
33+
return full
3434
}
3535
}
36+
37+
return null
38+
}
39+
40+
function* _getModuleDependencies(filename, base, seen) {
41+
let ext = path.extname(filename)
42+
43+
// Try to find the file
44+
let absoluteFile = resolveWithExtension(
45+
path.resolve(base, filename),
46+
jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder
47+
)
48+
if (absoluteFile === null) return // File doesn't exist
49+
50+
// Prevent infinite loops when there are circular dependencies
51+
if (seen.has(absoluteFile)) return // Already seen
52+
seen.add(absoluteFile)
53+
54+
// Mark the file as a dependency
55+
yield absoluteFile
56+
57+
// Resolve new base for new imports/requires
58+
base = path.dirname(absoluteFile)
59+
60+
let contents = fs.readFileSync(absoluteFile, 'utf-8')
61+
62+
// Find imports/requires
63+
for (let match of [
64+
...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
65+
...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
66+
...contents.matchAll(/require\(['"`](.{3,})['"`]\)/gi),
67+
]) {
68+
// Bail out if it's not a relative file
69+
if (!match[1].startsWith('.')) continue
70+
71+
yield* _getModuleDependencies(match[1], base, seen)
72+
}
3673
}
3774

38-
export default function getModuleDependencies(entryFile) {
39-
return new Set(_getModuleDependencies(entryFile))
75+
export default function getModuleDependencies(absoluteFilePath) {
76+
return Array.from(
77+
_getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set())
78+
)
4079
}

0 commit comments

Comments
 (0)