-
-
Notifications
You must be signed in to change notification settings - Fork 191
/
transform.ts
118 lines (99 loc) · 2.9 KB
/
transform.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import MagicString from 'magic-string'
import { ImportInfo, TransformOptions, Resolver } from '../types'
const excludeRE = [
// imported from other module
/\bimport\s*([\w_$]*?),?\s*\{([\s\S]*?)\}\s*from\b/g,
// defined as function
/\bfunction\s*([\w_$]+)\s*\(/g,
// defined as local variable
/\b(?:const|let|var)\s*([\s\S]+)[=\n;]/g,
]
const matchRE = /[^.\w_$]([\w_$]+)\b/g
const importAsRE = /^.*\sas\s+/
const multilineCommentsRE = /\/\*(.|[\r\n])*?\*\//gm
const singlelineCommentsRE = /\/\/.*/g
function stripeComments(code: string) {
return code
.replace(multilineCommentsRE, '')
.replace(singlelineCommentsRE, '')
}
export function transform(
code: string,
id: string,
{
imports,
sourceMap,
resolvers,
resolvedImports = {},
}: TransformOptions,
) {
const noComments = stripeComments(code)
const identifiers = new Set(Array.from(noComments.matchAll(matchRE)).map(i => i[1]))
// nothing matched, skip
if (!identifiers.size)
return null
// remove those already defined
for (const regex of excludeRE) {
Array.from(noComments.matchAll(regex))
.flatMap(i => [...(i[1]?.split(',') || []), ...(i[2]?.split(',') || [])])
.map(i => i.replace(importAsRE, '').trim())
.forEach(i => identifiers.delete(i))
}
// nothing matched, skip
if (!identifiers.size)
return null
const modules: Record<string, ImportInfo[]> = {}
// group by module name
Array.from(identifiers).forEach((name) => {
let info = getOwn(resolvedImports, name) || getOwn(imports, name)
if (!info && resolvers?.length) {
const resolved = firstNonNullResult(resolvers, name)
if (resolved) {
if (typeof resolved === 'string') {
info = {
module: resolved,
name,
from: 'default',
}
}
else {
info = resolved
}
resolvedImports[name] = info
}
}
if (!info || !info.module)
return
if (!modules[info.module])
modules[info.module] = []
modules[info.module].push(info)
})
if (!Object.keys(modules).length)
return
// stringify import
const importStatements = Object.entries(modules)
.map(([moduleName, names]) => {
const imports = names
.map(({ name, from }) => from ? `${from} as ${name}` : name)
.join(', ')
return `import { ${imports} } from '${moduleName}';`
})
.join('')
const s = new MagicString(code)
s.prependLeft(0, importStatements)
return {
code: s.toString(),
map: sourceMap ? s.generateMap() : null,
}
}
function firstNonNullResult(array: Resolver[], name: string) {
for (let i = 0; i < array.length; i++) {
const res = array[i](name)
if (res)
return res
}
}
const hasOwnProperty = Object.prototype.hasOwnProperty
function getOwn<T, K extends keyof T>(object: T, key: K) {
return hasOwnProperty.call(object, key) ? object[key] : undefined
}