1 | 'use strict'
|
2 |
|
3 | const fs = require('fs')
|
4 | const path = require('path')
|
5 | const babylon = require('babylon')
|
6 | const traverse = require('babel-traverse')['default']
|
7 | const t = require('babel-types')
|
8 | const chalk = require('chalk')
|
9 |
|
10 | class VendorExtracter {
|
11 | constructor (opts) {
|
12 | this.excludes = []
|
13 | this.defaultExt = '.js'
|
14 | this.fileModuleFomatter = opts.fileModuleFomatter || (i => i)
|
15 | this.moduleFilter = opts.moduleFilter || (_ => true)
|
16 | }
|
17 |
|
18 | parse (entryFiles) {
|
19 | if (!Array.isArray(entryFiles)) entryFiles = [ entryFiles ]
|
20 |
|
21 | let vendorList = this._parseModules(entryFiles, [], [])
|
22 | return vendorList
|
23 | }
|
24 |
|
25 | _parseModules (files, parsed, result) {
|
26 | if (!Array.isArray(files) || files.length <= 0) return result
|
27 |
|
28 | const fileModules = [], parsedFileModules = []
|
29 | files.forEach(file => {
|
30 |
|
31 | file = path.resolve(file)
|
32 | parsedFileModules.push(file)
|
33 |
|
34 |
|
35 | let codeContent
|
36 | try {
|
37 | codeContent = fs.readFileSync(file, { encoding: 'utf-8' })
|
38 | }
|
39 | catch (e) {
|
40 | console.log(chalk.red(`[Extracter] 读取入口 ${file} 失败,${e.message}`))
|
41 | return
|
42 | }
|
43 |
|
44 | let modules = this._parseModulesFromCode(codeContent, file)
|
45 | modules = modules.map(this.fileModuleFomatter)
|
46 | modules = modules.filter(this.moduleFilter)
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | modules.forEach(moduleName => {
|
52 | moduleName = this.fileModuleFomatter(moduleName)
|
53 |
|
54 | if (result.indexOf(moduleName) < 0 && this._isFromNodeModules(moduleName)) {
|
55 | result.push(moduleName)
|
56 | }
|
57 |
|
58 | if (!this._isFromNodeModules(moduleName)) {
|
59 | let base = path.dirname(file)
|
60 | let location = path.resolve(base, moduleName)
|
61 |
|
62 | if(!path.extname(location)) {
|
63 | location += this.defaultExt
|
64 | }
|
65 | fileModules.push(location)
|
66 | }
|
67 | })
|
68 | })
|
69 |
|
70 | parsed = [].concat(parsed, parsedFileModules)
|
71 | files = fileModules.filter(f => parsed.indexOf(f) < 0)
|
72 |
|
73 | return this._parseModules(files, parsed, result)
|
74 | }
|
75 |
|
76 | _isFromNodeModules (moduleName) {
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | return !/^[\./]/.test(moduleName)
|
82 | }
|
83 |
|
84 | _parseModulesFromCode (codeContent, fileName) {
|
85 | const modules = []
|
86 | const _self = this
|
87 | try {
|
88 | const ast = babylon.parse(codeContent, {
|
89 | sourceType: 'module',
|
90 | plugins: [
|
91 | "jsx",
|
92 | "flow",
|
93 | "asyncFunctions",
|
94 | "classConstructorCall",
|
95 | "doExpressions",
|
96 | "trailingFunctionCommas",
|
97 | "objectRestSpread",
|
98 | "decorators",
|
99 | "classProperties",
|
100 | "exportExtensions",
|
101 | "exponentiationOperator",
|
102 | "asyncGenerators",
|
103 | "functionBind",
|
104 | "functionSent"
|
105 | ]
|
106 | })
|
107 |
|
108 | console.log(`[Extracter] 开始解析 ${fileName}`);
|
109 |
|
110 | traverse(ast, {
|
111 | enter (path) {
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | const node = path.node
|
118 | if (t.isImportDeclaration(node)) {
|
119 | modules.push(node.source.value)
|
120 | }
|
121 | if (t.isCallExpression(node) && node.callee.name == 'require') {
|
122 | const arg = node.arguments[0]
|
123 | if (!t.isStringLiteral(arg)) {
|
124 | console.log(`[Extracter] 解析 ${fileName} 时发现有模块不是以字符串字面量的形式引入的,当前版本不支持该形式`)
|
125 | return []
|
126 | }
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | if (_self._isFromNodeModules(arg.value)) {
|
133 | const sub = arg.value.split('/')
|
134 | if (arg.value[0] == '@') {
|
135 | modules.push(sub.slice(0, 2).join('/'))
|
136 | }
|
137 | else modules.push(sub[0])
|
138 | }
|
139 | else modules.push(arg.value)
|
140 | }
|
141 | }
|
142 | })
|
143 | }
|
144 | catch (e) {
|
145 | console.log(chalk.red(`[Extracter] 解析文件 ${fileName} 代码失败,${e.message}`))
|
146 | return []
|
147 | }
|
148 |
|
149 | return modules
|
150 | }
|
151 |
|
152 | output (vendorList) {
|
153 | const source =
|
154 | 'module.exports = {' + '\n' +
|
155 | vendorList.map((name, i) => {
|
156 | if (i == vendorList.length - 1) {
|
157 | return ` "${name}": require('${name}')`
|
158 | }
|
159 | else return ` "${name}": require('${name}'),`
|
160 | }).join('\n') + '\n' +
|
161 | '};'
|
162 |
|
163 | return source
|
164 | }
|
165 |
|
166 | parseAndOutput (entryFiles) {
|
167 | const vendorList = this.parse(entryFiles)
|
168 | return this.output(vendorList)
|
169 | }
|
170 | }
|
171 |
|
172 | module.exports = VendorExtracter
|