• 手写小程序摇树优化工具(九)——删除业务组代码


    善为士者不武;善战者不怒;善胜敌者不与;善用人者为之下。是谓不争之德,是谓用人之力,是谓配天,古之极。

    github: miniapp-shaking

    1. 背景介绍

    第三章我们遍历json的时候有介绍过通过groupNamereplaceComponents来取代对应的业务组件来解决大公司复杂逻辑共用导致的超包问题。这很好,现在我要实现一个更加高级的功能,来达到精细化的控制业务代码的程度。这主要是为了解决超级复杂项目的问题,一般的小项目用不到。

    举一个例子,不同的业务组的组件可能不光光是在组件层面,他们可能在一个单一个的jswxswxml文件里面分别写了自己的业务逻辑,例如以下例子:
    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;
      },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    index.wxml

    <view>主包view>
    
    <componentOne>componentOne>
    
    
    • 1
    • 2
    • 3
    • 4

    index.json

    {
      "usingComponents": {
        "componentOne": "../../components/component-1"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    方法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;
      },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    index.wxml

    <view>主包view>
    
    • 1

    index.json

    {
      "usingComponents": {}
    }
    
    • 1
    • 2
    • 3

    这对于大公司的非常复杂的项目是非常有帮助的,能够解决他们超包的问题。

    2. 实现

    2.1 定义规则

    现在我们来实现这一个过程,首先我们在config里面配置一个参数needDeleteGroupCode来表明是否需要做删除业务代码的操作,因为一些小项目不需要这样做,可以加快摇树的速度。

    this.needDeleteGroupCode = options.needDeleteGroupCode || false;
    
    • 1

    接着我们新建两个正则表达式来标志哪些代码属于哪些业务组

    // 业务逻辑
    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');
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们定义一个规则,通过注释来标志js和wxml的代码所属的业务,规则是这样的:

    对于js和wxs来说我们定义一个规则,由groupStart:开始,然后后面加上业务组名称,多个业务组可以通过逗号或者空格分开,之后以groupEnd结束。这中间的代码块就被标记为这些业务组的代码。非自己业务组的代码将会被摇树掉,如果没有这些标志的被视为共有的代码保留。注意只能使用/** */或者/* */这样的注释,并且只能写在一行中。

     /** groupStart:groupA,groupC */
      业务代码
      /** groupEnd */
    
    • 1
    • 2
    • 3

    同理对于wxmlwxml这些标签被删除后会同步删除json的组件,并忽略被删除组件的所有依赖。

    
    <componentOne>componentOne>
    
    
    • 1
    • 2
    • 3

    注意:业务组名称不能存在包含关系,例如goup1,group12。

    2.2 过滤被删除代码的依赖

    BaseDepend中新建一个变量来保存有业务代码标志的文件:

    class BaseDepend {
      constructor(config, rootDir = '') {
       	...
        // 有业务组代码的文件
        this.groupFile = new Set();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    新建一个方法遍历时忽略掉被删除业务组代码的依赖

    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);
          .....
        }
    }
    
    • 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

    到此时被删除业务组的代码的依赖已经被忽略了。

    2.3 真正删除业务组代码

    在容器类中真正的删除这些代码,这步操作一定要在拷贝完文件之后,否则源码会被修改。

    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);
          });
        }
      }
    }
    
    • 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

    现在我们已经删除了非自己业务组的代码了。为什么我不压缩混淆代码呢?因为这对于解决超包问题无用,请关注我的另一片文章:微信小程序源码压缩探索

    下一章我们将介绍如何删除dead code,敬请关注。

    连载文章链接:
    手写小程序摇树工具(一)——依赖分析介绍
    手写小程序摇树工具(二)——遍历js文件
    手写小程序摇树工具(三)——遍历json文件
    手写小程序摇树工具(四)——遍历wxml、wxss、wxs文件
    手写小程序摇树工具(五)——从单一文件开始深度依赖收集
    手写小程序摇树工具(六)——主包和子包依赖收集
    手写小程序摇树工具(七)——生成依赖图
    手写小程序摇树工具(八)——移动独立npm包
    手写小程序摇化工具(九)——删除业务组代码

  • 相关阅读:
    zookeeper介绍
    【RocketMQ生产者发送消息的三种方式:发送同步消息、异步消息、单向消息&案例实战&详细学习流程】
    springboot属性注入增强(一)背景/需求
    django无法导入第三方库
    Prompt
    第53期|GPTSecurity周报
    OD(7)之time调用与Linux-vDSO机制
    服务器正文20:跨平台(win和linux)用cmake给程序增加汇编代码
    Autosar MCAL MCU配置时钟-基于cfg
    Day04--实现本地生活的首页基础布局
  • 原文地址:https://blog.csdn.net/qq_28506819/article/details/127983251