AST应用-变量名压缩

250 阅读6分钟

1. 什么是变量名压缩

// 原始代码
function calc(parameterA, parameterB) {
  var localVariableA = parameterA + 1;
  var localVariableB = parameterB + 2;
  return localVariableA + localVariableB;
}

// 压缩后
function calc(a,b) {
  var c = a + 1;
  var d = b + 2;
  return c + d;
}

2. 核心原理

2.1 核心原理

变量名压缩主要依赖于作用域分析标识符替换

2.2 步骤

  1. 作用域分析
    • 构建作用域树,识别变量定义和引用关系
    • 分析变量的生命周期和可访问范围
    • 检测命名冲突和变量遮蔽
  2. 变量重命名
    • 按作用域从内到外遍历
    • 使用短标识符池(如单字母 a,b,c...)
    • 为每个变量分配最短可用名称
    • 确保不与保留字冲突
  3. 冲突处理
    • 当内层作用域变量可能与外层冲突时,选择下一个可用名称
    • 维护一个已使用标识符集合避免重复

2.3 重要优化策略:

  • 优先压缩使用频率高的长变量名
  • 保持局部变量名在不同函数间的一致性
  • 特殊处理 eval() 和 with 等动态作用域
  • 考虑 source map 的生成需求

3. 代码实现

3.1 测试代码

var globalVar = 'global';
function outer(param) {
    var x = 100;
    var obj = { prop: 'value' };
    
    function inner(y) {
        eval('console.log(x, y)');
        with(obj) {
            console.log(prop);
        }
        var dynProp = 'prop';
        console.log(obj[dynProp]);
    }
    
    return inner;
}

3.2 作用域

ad53d856ed6e41ef8852290c793bce18.png

3.3 作用域分析

class ScopeAnalyzer {
  constructor() {
    this.scopes = new Map();
    this.currentScope = null;
  }

  analyze(ast) {
    // 创建全局作用域
    const globalScope = {
      type: 'global',
      parent: null,
      variables: new Map(),
      hasEval: false,
      hasWith: false,
      hasEvalInChildren: false,
      children: []
    };
    
    this.scopes.set('global', globalScope);
    this.currentScope = globalScope;
    
    // 构建作用域树
    this.visit(ast);
    return this.scopes;
  }

  visit(node) {
    if (!node) return;
    
    switch(node.type) {
      case 'Program':
        this.visitChildren(node.body);
        break;

      case 'FunctionDeclaration':
      case 'FunctionExpression':
        this.handleFunction(node);
        break;
        
      case 'BlockStatement':
        this.handleBlock(node);
        break;
        
      case 'VariableDeclaration':
        this.handleVariableDeclaration(node);
        break;
        
      case 'CallExpression':
        this.handleCallExpression(node);
        this.visitChildren(node.arguments);
        break;
        
      case 'WithStatement':
        this.handleWithStatement(node);
        this.visit(node.body);
        break;
        
      case 'Identifier':
        this.handleIdentifier(node);
        break;
        
      default:
        // 递归访问其他类型节点的子节点
        if (node.type) {
          this.visitNodeChildren(node);
        }
    }
  }

  visitChildren(nodes) {
    if (Array.isArray(nodes)) {
      nodes.forEach(node => this.visit(node));
    }
  }

  visitNodeChildren(node) {
    for (const key in node) {
      if (node.hasOwnProperty(key)) {
        const child = node[key];
        if (Array.isArray(child)) {
          this.visitChildren(child);
        } else if (child && typeof child === 'object' && child.type) {
          this.visit(child);
        }
      }
    }
  }

  handleFunction(node) {
    // 创建新的函数作用域
    const scope = {
      type: 'function',
      parent: this.currentScope,
      variables: new Map(),
      hasEval: false,
      hasWith: false,
      hasEvalInChildren: false,
      children: []
    };
    
    this.scopes.set(node, scope);
    this.currentScope.children.push(scope);
    
    const parentScope = this.currentScope;
    this.currentScope = scope;
    
    // 如果是函数声明,在父作用域中注册函数名
    if (node.type === 'FunctionDeclaration' && node.id) {
      this.addToScope(parentScope, node.id.name, 'function', node);
    }
    
    // 记录参数
    node.params.forEach(param => {
      this.addToScope(scope, param.name, 'parameter', param);
    });
    
    // 访问函数体
    this.visit(node.body);
    
    this.currentScope = parentScope;
  }

  handleBlock(node) {
    // ES6块级作用域
    const scope = {
      type: 'block',
      parent: this.currentScope,
      variables: new Map(),
      hasEval: false,
      hasWith: false,
      hasEvalInChildren: false,
      children: []
    };
    
    this.scopes.set(node, scope);
    this.currentScope.children.push(scope);
    
    const parentScope = this.currentScope;
    this.currentScope = scope;
    
    this.visitChildren(node.body);
    
    this.currentScope = parentScope;
  }

  handleVariableDeclaration(node) {
    node.declarations.forEach(declarator => {
      const name = declarator.id.name;
      this.addToScope(
        this.currentScope,
        name,
        node.kind, // 'var', 'let', 或 'const'
        declarator
      );
      
      // 访问初始化器
      if (declarator.init) {
        this.visit(declarator.init);
      }
    });
  }

  handleCallExpression(node) {
    if (node.callee.type === 'Identifier' && node.callee.name === 'eval') {
      this.markEvalScope(this.currentScope);
    }
    this.visit(node.callee);
  }

  handleWithStatement(node) {
    this.markWithScope(this.currentScope);
    this.visit(node.object);
  }

  handleIdentifier(node) {
    // 记录变量引用
    let currentScope = this.currentScope;
    while (currentScope) {
      const variable = currentScope.variables.get(node.name);
      if (variable) {
        variable.references.push(node);
        break;
      }
      currentScope = currentScope.parent;
    }
  }

  addToScope(scope, name, type, node) {
    scope.variables.set(name, {
      type: type,
      node: node,
      references: []
    });
  }

  markEvalScope(scope) {
    scope.hasEval = true;
    // 向上传播eval标记
    let current = scope;
    while (current) {
      current.hasEvalInChildren = true;
      current = current.parent;
    }
  }

  markWithScope(scope) {
    scope.hasWith = true;
  }
}

4. 生成唯一变量名的工具类

class NameGenerator {
  constructor() {
    this.usedNames = new Set();
    this.counter = 0;
  }

  next() {
    let name;
    
    do {
      name = this.generateName(this.counter++);
    } while (this.usedNames.has(name));
    
    this.usedNames.add(name);
    return name;
  }

  generateName(index) {
    const chars = 'abcdefghijklmnopqrstuvwxyz';
    let name = '';
    
    do {
      name = chars[index % chars.length] + name;
      index = Math.floor(index / chars.length) - 1;
    } while (index >= 0);
    
    return name;
  }
}

5. 变量重新命名


class Mangler {
  constructor(scopeAnalysis) {
    this.scopes = scopeAnalysis;
    this.nameGenerator = new NameGenerator();
    this.renamedMap = new Map();
    this.reservedWords = new Set([
      'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete',
      'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
      'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
      'void', 'while', 'with', 'class', 'const', 'enum', 'export', 'extends',
      'import', 'super', 'implements', 'interface', 'let', 'package', 'private',
      'protected', 'public', 'static', 'yield', 'null', 'true', 'false'
    ]);
  }

  mangle() {
    // 从全局作用域开始处理
    const globalScope = this.scopes.get('global');
    this.mangleScope(globalScope, new Set());
  }

  mangleScope(scope, parentUsedNames) {
    const localUsedNames = new Set(parentUsedNames);
    
    // 如果作用域包含 eval 或 with,跳过重命名
    if (scope.hasEval || scope.hasWith) {
      return localUsedNames;
    }

    // 重命名当前作用域的变量
    for (const [name, varInfo] of scope.variables.entries()) {
      // 跳过已经在父作用域中使用的名称
      if (parentUsedNames.has(name)) continue;
      
      // 跳过特殊标识符和保留字
      if (this.reservedWords.has(name)) continue;
      
      // 生成新名称
      const newName = this.generateSafeName(localUsedNames);
      
      // 记录使用的名称
      localUsedNames.add(newName);
      
      // 执行重命名
      this.renameVariable(name, newName, varInfo, scope);
    }

    // 递归处理子作用域
    for (const childScope of scope.children) {
      this.mangleScope(childScope, localUsedNames);
    }

    return localUsedNames;
  }

  generateSafeName(usedNames) {
    let name;
    do {
      name = this.nameGenerator.next();
    } while (usedNames.has(name) || this.reservedWords.has(name));
    return name;
  }

  renameVariable(oldName, newName, varInfo, scope) {
    const scopeId = scope.type === 'global' ? 'global' : scope;
    this.renamedMap.set(`${oldName}_${scopeId}`, newName);

    if (varInfo.node) {
      this.updateDeclaration(varInfo.node, newName);
    }

    if (varInfo.references) {
      varInfo.references.forEach(ref => {
        this.updateReference(ref, newName);
      });
    }
  }

  updateReference(reference, newName) {
    if (!reference) return;
    
    switch(reference.type) {
      case 'Identifier':
        reference.name = newName;
        break;
        
      case 'MemberExpression':
        if (!reference.computed && reference.property.type === 'Identifier') {
          reference.property.name = newName;
        }
        break;
        
      case 'Property':
        if (!reference.computed && reference.key.type === 'Identifier') {
          reference.key.name = newName;
        }
        break;
    }
  }

  updateDeclaration(declaration, newName) {
    if (!declaration) return;

    switch(declaration.type) {
      case 'VariableDeclarator':
        if (declaration.id && declaration.id.type === 'Identifier') {
          declaration.id.name = newName;
        }
        break;
        
      case 'FunctionDeclaration':
      case 'FunctionExpression':
        if (declaration.id && declaration.id.type === 'Identifier') {
          declaration.id.name = newName;
        }
        break;
        
      case 'Identifier':
        declaration.name = newName;
        break;
    }
  }
}

4. 完整代码

const esprima = require('esprima');

class ScopeAnalyzer {
  constructor() {
    this.scopes = new Map();
    this.currentScope = null;
  }

  analyze(ast) {
    // 创建全局作用域
    const globalScope = {
      type: 'global',
      parent: null,
      variables: new Map(),
      hasEval: false,
      hasWith: false,
      hasEvalInChildren: false,
      children: []
    };
    
    this.scopes.set('global', globalScope);
    this.currentScope = globalScope;
    
    // 构建作用域树
    this.visit(ast);
    return this.scopes;
  }

  visit(node) {
    if (!node) return;
    
    switch(node.type) {
      case 'Program':
        this.visitChildren(node.body);
        break;

      case 'FunctionDeclaration':
      case 'FunctionExpression':
        this.handleFunction(node);
        break;
        
      case 'BlockStatement':
        this.handleBlock(node);
        break;
        
      case 'VariableDeclaration':
        this.handleVariableDeclaration(node);
        break;
        
      case 'CallExpression':
        this.handleCallExpression(node);
        this.visitChildren(node.arguments);
        break;
        
      case 'WithStatement':
        this.handleWithStatement(node);
        this.visit(node.body);
        break;
        
      case 'Identifier':
        this.handleIdentifier(node);
        break;
        
      default:
        // 递归访问其他类型节点的子节点
        if (node.type) {
          this.visitNodeChildren(node);
        }
    }
  }

  visitChildren(nodes) {
    if (Array.isArray(nodes)) {
      nodes.forEach(node => this.visit(node));
    }
  }

  visitNodeChildren(node) {
    for (const key in node) {
      if (node.hasOwnProperty(key)) {
        const child = node[key];
        if (Array.isArray(child)) {
          this.visitChildren(child);
        } else if (child && typeof child === 'object' && child.type) {
          this.visit(child);
        }
      }
    }
  }

  handleFunction(node) {
    // 创建新的函数作用域
    const scope = {
      type: 'function',
      parent: this.currentScope,
      variables: new Map(),
      hasEval: false,
      hasWith: false,
      hasEvalInChildren: false,
      children: []
    };
    
    this.scopes.set(node, scope);
    this.currentScope.children.push(scope);
    
    const parentScope = this.currentScope;
    this.currentScope = scope;
    
    // 如果是函数声明,在父作用域中注册函数名
    if (node.type === 'FunctionDeclaration' && node.id) {
      this.addToScope(parentScope, node.id.name, 'function', node);
    }
    
    // 记录参数
    node.params.forEach(param => {
      this.addToScope(scope, param.name, 'parameter', param);
    });
    
    // 访问函数体
    this.visit(node.body);
    
    this.currentScope = parentScope;
  }

  handleBlock(node) {
    // ES6块级作用域
    const scope = {
      type: 'block',
      parent: this.currentScope,
      variables: new Map(),
      hasEval: false,
      hasWith: false,
      hasEvalInChildren: false,
      children: []
    };
    
    this.scopes.set(node, scope);
    this.currentScope.children.push(scope);
    
    const parentScope = this.currentScope;
    this.currentScope = scope;
    
    this.visitChildren(node.body);
    
    this.currentScope = parentScope;
  }

  handleVariableDeclaration(node) {
    node.declarations.forEach(declarator => {
      const name = declarator.id.name;
      this.addToScope(
        this.currentScope,
        name,
        node.kind, // 'var', 'let', 或 'const'
        declarator
      );
      
      // 访问初始化器
      if (declarator.init) {
        this.visit(declarator.init);
      }
    });
  }

  handleCallExpression(node) {
    if (node.callee.type === 'Identifier' && node.callee.name === 'eval') {
      this.markEvalScope(this.currentScope);
    }
    this.visit(node.callee);
  }

  handleWithStatement(node) {
    this.markWithScope(this.currentScope);
    this.visit(node.object);
  }

  handleIdentifier(node) {
    // 记录变量引用
    let currentScope = this.currentScope;
    while (currentScope) {
      const variable = currentScope.variables.get(node.name);
      if (variable) {
        variable.references.push(node);
        break;
      }
      currentScope = currentScope.parent;
    }
  }

  addToScope(scope, name, type, node) {
    scope.variables.set(name, {
      type: type,
      node: node,
      references: []
    });
  }

  markEvalScope(scope) {
    scope.hasEval = true;
    // 向上传播eval标记
    let current = scope;
    while (current) {
      current.hasEvalInChildren = true;
      current = current.parent;
    }
  }

  markWithScope(scope) {
    scope.hasWith = true;
  }
}


class Mangler {
  constructor(scopeAnalysis) {
    this.scopes = scopeAnalysis;
    this.nameGenerator = new NameGenerator();
    this.renamedMap = new Map();
    this.reservedWords = new Set([
      'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete',
      'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
      'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
      'void', 'while', 'with', 'class', 'const', 'enum', 'export', 'extends',
      'import', 'super', 'implements', 'interface', 'let', 'package', 'private',
      'protected', 'public', 'static', 'yield', 'null', 'true', 'false'
    ]);
  }

  mangle() {
    // 从全局作用域开始处理
    const globalScope = this.scopes.get('global');
    this.mangleScope(globalScope, new Set());
  }

  mangleScope(scope, parentUsedNames) {
    const localUsedNames = new Set(parentUsedNames);
    
    // 如果作用域包含 eval 或 with,跳过重命名
    if (scope.hasEval || scope.hasWith) {
      return localUsedNames;
    }

    // 重命名当前作用域的变量
    for (const [name, varInfo] of scope.variables.entries()) {
      // 跳过已经在父作用域中使用的名称
      if (parentUsedNames.has(name)) continue;
      
      // 跳过特殊标识符和保留字
      if (this.reservedWords.has(name)) continue;
      
      // 生成新名称
      const newName = this.generateSafeName(localUsedNames);
      
      // 记录使用的名称
      localUsedNames.add(newName);
      
      // 执行重命名
      this.renameVariable(name, newName, varInfo, scope);
    }

    // 递归处理子作用域
    for (const childScope of scope.children) {
      this.mangleScope(childScope, localUsedNames);
    }

    return localUsedNames;
  }

  generateSafeName(usedNames) {
    let name;
    do {
      name = this.nameGenerator.next();
    } while (usedNames.has(name) || this.reservedWords.has(name));
    return name;
  }

  renameVariable(oldName, newName, varInfo, scope) {
    const scopeId = scope.type === 'global' ? 'global' : scope;
    this.renamedMap.set(`${oldName}_${scopeId}`, newName);

    if (varInfo.node) {
      this.updateDeclaration(varInfo.node, newName);
    }

    if (varInfo.references) {
      varInfo.references.forEach(ref => {
        this.updateReference(ref, newName);
      });
    }
  }

  updateReference(reference, newName) {
    if (!reference) return;
    
    switch(reference.type) {
      case 'Identifier':
        reference.name = newName;
        break;
        
      case 'MemberExpression':
        if (!reference.computed && reference.property.type === 'Identifier') {
          reference.property.name = newName;
        }
        break;
        
      case 'Property':
        if (!reference.computed && reference.key.type === 'Identifier') {
          reference.key.name = newName;
        }
        break;
    }
  }

  updateDeclaration(declaration, newName) {
    if (!declaration) return;

    switch(declaration.type) {
      case 'VariableDeclarator':
        if (declaration.id && declaration.id.type === 'Identifier') {
          declaration.id.name = newName;
        }
        break;
        
      case 'FunctionDeclaration':
      case 'FunctionExpression':
        if (declaration.id && declaration.id.type === 'Identifier') {
          declaration.id.name = newName;
        }
        break;
        
      case 'Identifier':
        declaration.name = newName;
        break;
    }
  }
}



// 生成唯一变量名的工具类
class NameGenerator {
  constructor() {
    this.usedNames = new Set();
    this.counter = 0;
  }

  next() {
    let name;
    
    do {
      name = this.generateName(this.counter++);
    } while (this.usedNames.has(name));
    
    this.usedNames.add(name);
    return name;
  }

  generateName(index) {
    const chars = 'abcdefghijklmnopqrstuvwxyz';
    let name = '';
    
    do {
      name = chars[index % chars.length] + name;
      index = Math.floor(index / chars.length) - 1;
    } while (index >= 0);
    
    return name;
  }
}

// 创建完整的压缩处理流程
class Minifier {
  constructor() {
    this.scopeAnalyzer = new ScopeAnalyzer();
  }

  minify(code) {
    // 1. 解析代码生成AST
    const ast = esprima.parse(code);
    
    // 2. 分析作用域
    const scopeAnalysis = this.scopeAnalyzer.analyze(ast);
    

    console.log('scopeAnalysis ===> ', scopeAnalysis)

    // 3. 变量名压缩
    const mangler = new Mangler(scopeAnalysis);
    mangler.mangle();
    
    // 4. 生成代码
    return this.generate(ast);
  }

  generate(ast) {
    const generator = new CodeGenerator();
    return generator.generate(ast);
  }
}


// Only showing the modified CodeGenerator class - other classes remain the same
class CodeGenerator {
  generate(ast) {
    return this.generateNode(ast);
  }

  generateNode(node) {
    if (!node) return '';
    
    switch(node.type) {
      case 'Program':
        return node.body.map(stmt => this.generateNode(stmt)).join(';\n');
        
      case 'VariableDeclaration':
        return `${node.kind} ${node.declarations.map(d => this.generateNode(d)).join(', ')};`;
        
      case 'VariableDeclarator':
        return `${this.generateNode(node.id)}${node.init ? ' = ' + this.generateNode(node.init) : ''}`;
        
      case 'FunctionDeclaration':
        return `function ${this.generateNode(node.id)}(${node.params.map(p => this.generateNode(p)).join(', ')}) ${this.generateNode(node.body)}`;
        
      case 'BlockStatement':
        const body = node.body.map(stmt => this.generateNode(stmt)).join(';\n');
        return `{
${this.indent(body)}
}`;
        
      case 'ExpressionStatement':
        return `${this.generateNode(node.expression)};`;
        
      case 'CallExpression':
        return `${this.generateNode(node.callee)}(${node.arguments.map(arg => this.generateNode(arg)).join(', ')})`;
        
      case 'MemberExpression':
        const object = this.generateNode(node.object);
        const property = node.computed 
          ? `[${this.generateNode(node.property)}]`
          : `.${this.generateNode(node.property)}`;
        return `${object}${property}`;
        
      case 'ObjectExpression':
        const properties = node.properties.map(prop => this.generateNode(prop)).join(', ');
        return `{ ${properties} }`;
        
      case 'Property':
        const key = node.computed 
          ? `[${this.generateNode(node.key)}]` 
          : this.generateNode(node.key);
        return `${key}: ${this.generateNode(node.value)}`;
        
      case 'WithStatement':
        return `with(${this.generateNode(node.object)}) ${this.generateNode(node.body)}`;
        
      case 'Identifier':
        return node.name;
        
      case 'Literal':
        if (typeof node.value === 'string') {
          return `'${node.value}'`;
        }
        return String(node.value);
        
      default:
        return '';
    }
  }

  indent(code) {
    return code.split('\n').map(line => `  ${line}`).join('\n');
  }
}



// 使用示例:
const code = `
  var globalVar = 'global';
  function outer(param) {
      var x = 100;
      var obj = { prop: 'value' };
      
      function inner(y) {
          eval('console.log(x, y)');
          with(obj) {
              console.log(prop);
          }
          var dynProp = 'prop';
          console.log(obj[dynProp]);
      }
      
      return inner;
  }
`;

// 使用示例
const minifier = new Minifier();
const result = minifier.minify(code);
console.log(result);

输出

var a = 'global';;
function b(c) {
  var d = 100;;
  var e = { prop: 'value' };;
  function f(g) {
    eval('console.log(x, y)');;
    with(e) {
      console.log(prop);
    };
    var dynProp = 'prop';;
    console.log(e[dynProp]);
  }; 
}