Skip to content

Commit 7b1f9da

Browse files
committed
feat: 组件被模块引用关系收集
1 parent 85261f8 commit 7b1f9da

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed

collectDeps.js

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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 = /src(\\|\/)components/
33+
// components/xxx 别名引入的匹配正则
34+
const componentsAlias = /^components/
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

Comments
 (0)