善为士者不武;善战者不怒;善胜敌者不与;善用人者为之下。是谓不争之德,是谓用人之力,是谓配天,古之极。
github: miniapp-shaking
第三章我们遍历json的时候有介绍过通过groupName
和replaceComponents
来取代对应的业务组件来解决大公司复杂逻辑共用导致的超包问题。这很好,现在我要实现一个更加高级的功能,来达到精细化的控制业务代码的程度。这主要是为了解决超级复杂项目的问题,一般的小项目用不到。
举一个例子,不同的业务组的组件可能不光光是在组件层面,他们可能在一个单一个的js
、wxs
和wxml
文件里面分别写了自己的业务逻辑,例如以下例子:
index.js
const app = getApp()
Page({
/** groupStart:groupA, groupC */
sum() {
return 1 + 1 + 1;
},
/** groupEnd */
/** groupStart:groupB */
sum2() {
return 2 + 2;
},
/** groupEnd */
sum3() {
return 3 + 3;
},
})
index.wxml
<view>主包view>
<componentOne>componentOne>
index.json
{
"usingComponents": {
"componentOne": "../../components/component-1"
}
}
方法sum2
和组件componentOne
是业务组groupB
的,那么对于业务组groupA
来说摇树的时候如果能把groupB
的代码干掉将会节省很多的空间,并且不仅是componentOne
组件,连它的所有依赖都将被忽略掉。被摇树之后的代码应该是这样的:
index.js
const app = getApp()
Page({
/** groupStart:groupA, groupC */
sum() {
return 1 + 1 + 1;
},
/** groupEnd */
/** groupStart:groupB *//** groupEnd */
sum3() {
return 3 + 3;
},
})
index.wxml
<view>主包view>
index.json
{
"usingComponents": {}
}
这对于大公司的非常复杂的项目是非常有帮助的,能够解决他们超包的问题。
现在我们来实现这一个过程,首先我们在config里面配置一个参数needDeleteGroupCode
来表明是否需要做删除业务代码的操作,因为一些小项目不需要这样做,可以加快摇树的速度。
this.needDeleteGroupCode = options.needDeleteGroupCode || false;
接着我们新建两个正则表达式来标志哪些代码属于哪些业务组
// 业务逻辑
if (this.groupName && this.needDeleteGroupCode) {
this.groupCodeJsRegexp = new RegExp(`(?<=\\/\\*\\*?\\s*groupStart:((?!${this.groupName}).)+\\s*\\*\\/)[\\s\\S]*?(?=\\/\\*\\*?\\s*groupEnd\\s*\\*\\/)`, 'ig');
this.groupCodeWxmlRegexp = new RegExp(`(?<=this.groupName}).)+\\s*-->)[\\s\\S]*?(?=)`, 'ig');
}
我们定义一个规则,通过注释来标志js和wxml的代码所属的业务,规则是这样的:
对于js和wxs来说我们定义一个规则,由groupStart:
开始,然后后面加上业务组名称
,多个业务组可以通过逗号或者空格分开,之后以groupEnd
结束。这中间的代码块就被标记为这些业务组的代码。非自己业务组的代码将会被摇树掉,如果没有这些标志的被视为共有的代码保留。注意只能使用/** */或者/* */
这样的注释,并且只能写在一行中。
/** groupStart:groupA,groupC */
业务代码
/** groupEnd */
同理对于wxml
,wxml
这些标签被删除后会同步删除json
的组件,并忽略被删除组件的所有依赖。
<componentOne>componentOne>
注意:业务组名称不能存在包含关系,例如goup1,group12。
在BaseDepend
中新建一个变量来保存有业务代码标志的文件:
class BaseDepend {
constructor(config, rootDir = '') {
...
// 有业务组代码的文件
this.groupFile = new Set();
}
}
新建一个方法遍历时忽略掉被删除业务组代码的依赖
class BaseDepend {
/**
* 删除业务组代码
* @param codeStr
* @returns {void|string|*}
* @private
*/
_deleteGroupCode(codeStr, file) {
if (this.config.needDeleteGroupCode && codeStr) {
const ext = path.extname(file);
const regExp = ext === '.wxml' ? this.config.groupCodeWxmlRegexp : this.config.groupCodeJsRegexp;
if (regExp.test(codeStr)) {
if (!this.groupFile.has(file)) {
// 保存有业务组代码的文件
this.groupFile.add(file);
}
return codeStr.replace(regExp, '');
}
}
return codeStr;
}
}
jsDeps(file) {
// 保存依赖
const deps = [];
// 文件所处的目录
const dirname = path.dirname(file);
// 读取js内容
let content = fse.readFileSync(file, 'utf-8');
content = this._deleteGroupCode(content, file);
// 将代码转化为AST树
.....
}
wxsDeps(filePath) {
const deps = [];
const dirname = path.dirname(filePath);
// 读取js内容
let content = fse.readFileSync(filePath, 'utf-8');
content = this._deleteGroupCode(content, filePath);
// 将代码转化为AST
....
}
wxmlDeps(file) {
const deps = [];
const dirName = path.dirname(file);
let content = fse.readFileSync(file, 'utf-8');
content = this._deleteGroupCode(content, file);
...
}
getWxmlTags(filePath) {
let needDelete = true;
const tags = new Set();
if (fse.existsSync(filePath)) {
let content = fse.readFileSync(filePath, 'utf-8');
content = this._deleteGroupCode(content, filePath);
.....
}
}
到此时被删除业务组的代码的依赖已经被忽略了。
在容器类中真正的删除这些代码,这步操作一定要在拷贝完文件之后,否则源码会被修改。
class DependContainer {
async init() {
this.clear();
this.initMainDepend();
this.initSubDepend();
this.handleAsyncFile();
this.splitIsolatedNpmForSubPackage();
const allFiles = await this.copyAllFiles();
this.deleteGroupCode();
....
console.log('success!');
}
deleteGroupCode() {
if (this.config.needDeleteGroupCode) {
console.log('正在删除业务组代码...');
const fileSet = this.mainDepend.groupFile;
this.subDepends.forEach(subDepend => {
this.appendSet(fileSet, subDepend.groupFile);
});
Array.from(fileSet).forEach(file => {
const targetPath = file.replace(this.config.sourceDir, this.config.targetDir);
let content = fse.readFileSync(targetPath, 'utf-8');
const ext = path.extname(file);
const regExp = ext === '.wxml' ? this.config.groupCodeWxmlRegexp : this.config.groupCodeJsRegexp;
content = content.replace(regExp, '');
fse.outputFileSync(targetPath, content);
});
}
}
}
现在我们已经删除了非自己业务组的代码了。为什么我不压缩混淆代码呢?因为这对于解决超包问题无用,请关注我的另一片文章:微信小程序源码压缩探索
下一章我们将介绍如何删除dead code
,敬请关注。
连载文章链接:
手写小程序摇树工具(一)——依赖分析介绍
手写小程序摇树工具(二)——遍历js文件
手写小程序摇树工具(三)——遍历json文件
手写小程序摇树工具(四)——遍历wxml、wxss、wxs文件
手写小程序摇树工具(五)——从单一文件开始深度依赖收集
手写小程序摇树工具(六)——主包和子包依赖收集
手写小程序摇树工具(七)——生成依赖图
手写小程序摇树工具(八)——移动独立npm包
手写小程序摇化工具(九)——删除业务组代码