1
+ /**
2
+ * 收集主框架 src/components 组件被那些子模块依赖
3
+ * 作用:
4
+ * 1. 明确各组件被引用的关系
5
+ * 2. 修改功能组件时便于开发、测试确认验证范围
6
+ * 运行
7
+ * 1. npm run deps --module=promo (指定收集模块依赖)
8
+ * 2. npm run deps (不指定收集模块,收集 src/pages 下的所有模块)
9
+ *
10
+ * 支持按模块增量收集 deps
11
+ */
12
+
13
+ const path = require ( 'path' )
14
+ const fs = require ( 'fs' )
15
+ const babel = require ( '@babel/core' ) ;
16
+ const t = require ( '@babel/types' ) ;
17
+ const prettier = require ( 'prettier' )
18
+ const { parse } = require ( '@babel/parser' )
19
+ const _ = require ( 'lodash' )
20
+ // path 路径处理的临时替换操作符
21
+ const operator = '='
22
+ const depsModule = process . env . npm_config_module
23
+ // 子模块的依赖集合
24
+ let sourcesMap = { }
25
+ // pure
26
+ const output = '../src/components/deps.json'
27
+ const outputPure = '../src/components/pureDeps.json'
28
+
29
+ // 忽略处理的文件夹
30
+ const ignoreDirectorys = [ 'node_modules' , 'dist' , '.git' ]
31
+ // src/components 相对路径引入的匹配正则
32
+ const componentsRelativeReg = / s r c ( \\ | \/ ) c o m p o n e n t s /
33
+ // components/xxx 别名引入的匹配正则
34
+ const componentsAlias = / ^ c o m p o n e n t s /
35
+
36
+ // 根据路径获取文件、文件夹
37
+ const getFiles = ( filePath ) => fs . readdirSync ( path . join ( __dirname , filePath ) )
38
+ // 根据 file path 径获取 file stats
39
+ const getStatsSync = ( filePath ) => {
40
+ return new Promise ( ( resolve ) => {
41
+ fs . stat ( path . join ( __dirname , filePath ) , ( err , stats ) => {
42
+ if ( ! err ) resolve ( stats )
43
+ } )
44
+ } )
45
+ }
46
+
47
+ // 将引入资源的 path 切割为数组 a/b/c.js => [a, b, c.js]
48
+ const getPathArrayByPath = ( path ) => path . replace ( / \\ | \/ / g, operator ) . split ( operator )
49
+
50
+ // 获取当前处理的模块所有文件的路径集合
51
+ const getDelModuleAllFilesPath = ( ) => {
52
+ // 处理模块的 config 路径
53
+ let configPaths = [ ]
54
+ return async function ( filePath ) {
55
+ const files = getFiles ( filePath ) . filter ( file => ! ignoreDirectorys . find ( ignoreFile => ignoreFile === file ) )
56
+
57
+ for ( const file of files ) {
58
+ const nextLevelFilePath = `${ filePath } /${ file } `
59
+ const stats = await getStatsSync ( nextLevelFilePath )
60
+ // 为文件夹则继续查找路径
61
+ if ( stats . isDirectory ( ) ) {
62
+ await arguments . callee ( nextLevelFilePath )
63
+ } else {
64
+ configPaths . push ( nextLevelFilePath )
65
+ }
66
+ }
67
+
68
+ return configPaths
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 根据正则对数据进行过滤
74
+ * @param { Array } filesPath 原数据
75
+ * @param { RegExp } filterReg 正则
76
+ * @returns 过滤后的数据
77
+ */
78
+ const getDelModuleAllMatchPath = ( filesPath , diffModule ) => {
79
+ const JSXReg = new RegExp ( `(components|(${ diffModule } /pages)).*(.jsx|.js)$` )
80
+ const configPaths = filesPath . filter ( path => JSXReg . test ( path ) )
81
+
82
+ return configPaths
83
+ }
84
+
85
+ // 获取 alias 别名的相对路径
86
+ const delAliasRelativePath = ( completeDepsPath ) => {
87
+ const completeDepsPathArray = getPathArrayByPath ( completeDepsPath )
88
+ const srcIndex = completeDepsPathArray . indexOf ( 'src' )
89
+ let srcPath = completeDepsPathArray . slice ( srcIndex )
90
+ srcPath . pop ( )
91
+ const relativePath = new Array ( srcPath . length ) . join ( '../' )
92
+
93
+ return relativePath
94
+ }
95
+
96
+ const getDeps = ( depsPath ) => {
97
+ let deps = { }
98
+ const source = fs . readFileSync ( path . join ( __dirname , depsPath ) , { encoding : 'utf-8' } )
99
+ const ast = parse ( source , {
100
+ // parse in strict mode and allow module declarations
101
+ sourceType : 'module' ,
102
+ plugins : [
103
+ 'jsx' ,
104
+ 'decorators-legacy' ,
105
+ // 'classProperties'
106
+ ] ,
107
+ } )
108
+
109
+ const dirName = path . dirname ( depsPath )
110
+ const completeDepsPath = path . join ( __dirname , depsPath )
111
+
112
+ babel . traverse ( ast , {
113
+ ImportDeclaration ( nodePath ) {
114
+ const { node } = nodePath
115
+ const value = node . source . value
116
+
117
+ // 处理以 components 别名引入的资源 如: components/If
118
+ if ( componentsAlias . test ( value ) ) {
119
+ const relativePath = delAliasRelativePath ( completeDepsPath )
120
+ const resolvePath = path . resolve ( __dirname , dirName , relativePath + value )
121
+ deps . resolvePath ?
122
+ deps [ resolvePath ] . push ( completeDepsPath ) :
123
+ deps [ resolvePath ] = [ completeDepsPath ]
124
+ } else if ( value . includes ( './' ) ) {
125
+ const resolvePath = path . join ( __dirname , dirName , value )
126
+
127
+ // 处理以 components 相对路径引入的资源 如: ../../../../components/If
128
+ if ( componentsRelativeReg . test ( resolvePath ) ) {
129
+ deps . resolvePath ?
130
+ deps [ resolvePath ] . push ( completeDepsPath ) :
131
+ deps [ resolvePath ] = [ completeDepsPath ]
132
+ }
133
+ }
134
+ }
135
+ } )
136
+
137
+ return deps
138
+ }
139
+
140
+
141
+ // begin del
142
+ const collectModuleDeps = async ( delModulePath , diffModule ) => {
143
+ // 临时暂存数据
144
+ let sources = { } ;
145
+
146
+ // 获取当前 delModulePath 文件夹下的所有文件路径
147
+ const filesPath = await getDelModuleAllFilesPath ( ) ( delModulePath ) ;
148
+ // 对所有的 path 进行过滤,获取需要的 path
149
+ const matchFilesPaths = getDelModuleAllMatchPath ( filesPath , diffModule ) ;
150
+ matchFilesPaths . forEach ( dep => {
151
+ const deps = getDeps ( dep ) ;
152
+
153
+ for ( const item in deps ) {
154
+ if ( sources [ item ] ) {
155
+ sources [ item ] . push ( ...deps [ item ] ) ;
156
+ } else {
157
+ sources [ item ] = deps [ item ] ;
158
+ }
159
+ }
160
+ } ) ;
161
+ const keys = Object . keys ( sources ) ;
162
+ keys . forEach ( el => {
163
+ const urlArray = getPathArrayByPath ( el ) ;
164
+ const srcIndex = urlArray . indexOf ( 'src' ) ;
165
+ // src/components/xxx 以组件名 xxx 作为 key
166
+ const shortUrl = urlArray . slice ( srcIndex + 2 ) . join ( '/' ) ;
167
+
168
+ if ( ! sourcesMap [ shortUrl ] )
169
+ sourcesMap [ shortUrl ] = { } ;
170
+ if ( ! sourcesMap [ shortUrl ] [ diffModule ] )
171
+ sourcesMap [ shortUrl ] [ diffModule ] = [ ] ;
172
+
173
+ sources [ el ] . forEach ( source => {
174
+ const sourceArray = getPathArrayByPath ( source ) ;
175
+ const diffModuleIndex = sourceArray . indexOf ( diffModule ) ;
176
+ const sourceUrl = sourceArray . slice ( diffModuleIndex + 1 ) ;
177
+ sourcesMap [ shortUrl ] [ diffModule ] . push ( sourceUrl . join ( '/' ) ) ;
178
+ } ) ;
179
+
180
+ } ) ;
181
+ }
182
+
183
+ const getPureSources = ( sources ) => {
184
+ let pureSourcesMap = { }
185
+ Object . keys ( sources ) . forEach ( key => {
186
+ const modules = Object . keys ( sources [ key ] )
187
+ pureSourcesMap [ key ] = modules
188
+ } )
189
+
190
+ return pureSourcesMap
191
+ }
192
+
193
+ // prettier 格式化 code
194
+ const code = ( sources ) => prettier . format (
195
+ typeof sources === 'object' ? JSON . stringify ( sources ) : sources ,
196
+ {
197
+ parser :'json' ,
198
+ singleQuote : true ,
199
+ trailingComma : 'all' ,
200
+ quoteProps :'consistent' ,
201
+ printWidth : 120
202
+ }
203
+ )
204
+
205
+ const getModuleDeps = async ( ) => {
206
+ const files = depsModule ? [ depsModule ] : getFiles ( '../src/pages' )
207
+ for ( const file of files ) {
208
+ console . log ( `开始处理:${ file } ` )
209
+ const selectedModulePath = `../src/pages/${ file } `
210
+
211
+ await collectModuleDeps ( selectedModulePath , file )
212
+ console . log ( `处理结束:${ file } \n` )
213
+ }
214
+
215
+ const depsPath = path . join ( __dirname , output )
216
+ const pureDepsPath = path . join ( __dirname , outputPure )
217
+
218
+ // json 文件存在则将 json 文件内容和当前处理的模块 sourcesMap 进行合并后再写入
219
+ if ( fs . existsSync ( depsPath ) ) {
220
+ const existsJson = fs . readFileSync ( depsPath , 'utf-8' )
221
+ const mergeSource = _ . merge ( JSON . parse ( existsJson ) , sourcesMap )
222
+ fs . writeFileSync ( depsPath , code ( mergeSource ) )
223
+ fs . writeFileSync ( pureDepsPath , code ( getPureSources ( mergeSource ) ) )
224
+ } else {
225
+ // json 文件不存在则直接写入
226
+ fs . writeFileSync ( depsPath , code ( sourcesMap ) )
227
+ fs . writeFileSync ( pureDepsPath , code ( getPureSources ( sourcesMap ) ) )
228
+ }
229
+ }
230
+
231
+ getModuleDeps ( )
0 commit comments