-
Notifications
You must be signed in to change notification settings - Fork 9
/
index.ts
110 lines (96 loc) 路 4.3 KB
/
index.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
import { renameFunctionParameters } from '@wakaru/ast-utils'
import { isFunctionExpression } from '@wakaru/ast-utils/matchers'
import { Module } from '../../Module'
import type { ModuleMapping } from '@wakaru/ast-utils/types'
import type { ArrayExpression, ArrowFunctionExpression, Collection, FunctionExpression, JSCodeshift, NumericLiteral, ObjectExpression } from 'jscodeshift'
/**
* Find the modules array in browserify bootstrap.
*
* ```js
* (function() {
* // prelude
* })()({
* // [id]: [ module factory function, module map ]
* // module map is short require name -> numeric require(id)
* 1: [
* function(require, module, exports) { ... },
* { "./foo": 2 }
* ],
* ...
* },
* { /** cache * / },
* [ /* entry ids * /])
* ```
*
* @see https://github.com/browserify/browser-pack/blob/master/prelude.js
*/
export function getModulesFromBrowserify(j: JSCodeshift, root: Collection):
{
modules: Set<Module>
moduleIdMapping: ModuleMapping
} | null {
const modules = new Set<Module>()
const moduleIdMapping: ModuleMapping = {}
const moduleDefinition = root.find(j.CallExpression, {
callee: {
type: 'CallExpression',
},
arguments: [{
type: 'ObjectExpression' as const,
properties: (properties: any[]) => {
return properties.every(prop => j.ObjectProperty.check(prop)
&& j.NumericLiteral.check(prop.key)
&& j.ArrayExpression.check(prop.value)
&& prop.value.elements.length === 2
&& isFunctionExpression(j, prop.value.elements[0])
&& j.ObjectExpression.check(prop.value.elements[1])
&& prop.value.elements[1].properties.every(prop => j.ObjectProperty.check(prop) && j.StringLiteral.check(prop.key) && j.NumericLiteral.check(prop.value)),
)
},
}, {
type: 'ObjectExpression' as const,
}, {
type: 'ArrayExpression' as const,
elements: (elements: any[]) => elements.every(el => j.NumericLiteral.check(el)),
}],
})
if (!moduleDefinition.size()) return null
moduleDefinition.forEach((path) => {
const [modulesObject, _moduleCache, entryIdArray] = path.node.arguments as [ObjectExpression, ObjectExpression, ArrayExpression]
const entryIds: number[] = (entryIdArray.elements as NumericLiteral[]).map(el => el.value)
modulesObject.properties.forEach((property) => {
if (!j.ObjectProperty.check(property)) return
if (!j.NumericLiteral.check(property.key)) return
if (!j.ArrayExpression.check(property.value)) return
const moduleId = property.key.value
const [moduleFactory, moduleMap] = property.value.elements as [FunctionExpression | ArrowFunctionExpression, ObjectExpression]
if (!j.BlockStatement.check(moduleFactory.body)) {
console.warn('moduleFactory.body is not a BlockStatement', moduleFactory.body)
return
}
renameFunctionParameters(j, moduleFactory, ['require', 'module', 'exports'])
const program = j.program(moduleFactory.body.body)
if (moduleFactory.body.directives) {
program.directives = [...(program.directives || []), ...moduleFactory.body.directives]
}
const moduleContent = j(program)
const isEntry = entryIds.includes(moduleId)
const module = new Module(moduleId, moduleContent, isEntry)
modules.add(module)
moduleMap.properties.forEach((property) => {
if (!j.ObjectProperty.check(property)) return
if (!j.StringLiteral.check(property.key)) return
if (!j.NumericLiteral.check(property.value)) return
const shortName = property.key.value
const moduleId = property.value.value
const prevShortName = moduleIdMapping[moduleId]
if (!!prevShortName && prevShortName !== shortName) {
console.warn(`Module ${moduleId} has multiple short names: ${prevShortName} and ${shortName}`)
}
moduleIdMapping[moduleId] = shortName
})
})
})
if (!modules.size) return null
return { modules, moduleIdMapping }
}