【问题标题】:Uglify JS - compressing unused variablesUglify JS - 压缩未使用的变量
【发布时间】:2013-06-05 12:30:39
【问题描述】:

Uglify 有一个“压缩”选项,可以删除未使用的变量...

但是,如果我将一些函数存储在这样的对象中......

helpers = {
    doSomething: function () { ... },
    doSomethingElese: function () { ... }
}

...如果从未访问过 helpers.doSomething(),有没有办法删除它?

猜想我想授予压缩器更改我的对象的权限。

如果可能的话,有什么想法吗?或者任何其他可以提供帮助的工具?

【问题讨论】:

  • 我正打算使用谷歌闭包编译器,但我做了一个小测试,使用高级优化(导出助手对象),它不会删除对象内部的函数,即使没有用过的。如果您希望这些工具中的任何一个摆脱未使用的代码,您可能需要重写代码(直接声明函数而不是使用辅助对象)
  • Google 闭包编译器会按照您的要求执行 - 但这意味着您需要使用 JSDoc 注释正确记录您的代码。

标签: javascript gruntjs uglifyjs uglifyjs2


【解决方案1】:

使用像 Uglify2 或 Esprima 这样的静态分析器来完成这个任务有点不简单,因为有很多情况会调用一个难以确定的函数。为了显示复杂性,有这个网站:

http://sevinf.github.io/blog/2012/09/29/esprima-tutorial/

这至少会尝试识别未使用的功能。但是,该网站上提供的代码不适用于您的示例,因为它正在寻找 FunctionDeclarations 而不是 FunctionExpressions。它还在寻找 CallExpression 作为标识符,同时忽略作为您的示例使用的 MemberExpression 的 CallExpression。那里还有一个范围问题,它没有考虑不同范围内具有相同名称的函数 - 完全合法的 Javascript,但是使用该代码会失去保真度,因为它会错过一些未使用的函数,认为它们是在它们被调用时被调用的不是。

要处理范围问题,您可以使用 ESTR (https://github.com/clausreinke/estr) 来帮助确定变量的范围,并从中找出未使用的函数。然后你需要使用类似 escodegen 的东西来删除未使用的函数。

作为您的起点,我已对该网站上的代码进行了调整,以适用于您提供的非常具体的情况,但请注意,这将存在范围问题。

这是为 Node.js 编写的,因此您需要使用 npm 获取 esprima 才能使用提供的示例,当然还需要使用 node 执行它。

var fs = require('fs');
var esprima = require('esprima');

if (process.argv.length < 3) {
  console.log('Usage: node ' + process.argv[1] + ' <filename>');
  process.exit(1);
}


notifydeadcode = function(data){
    function traverse(node, func) {
        func(node);
        for (var key in node) {
            if (node.hasOwnProperty(key)) {
                var child = node[key];
                if (typeof child === 'object' && child !== null) {
                    if (Array.isArray(child)) {
                        child.forEach(function(node) {
                            traverse(node, func);
                        });
                    } else {
                        traverse(child, func);
                    }
                }
            }
        }
    }

    function analyzeCode(code) {
        var ast = esprima.parse(code);
        var functionsStats = {};
        var addStatsEntry = function(funcName) {
            if (!functionsStats[funcName]) {
                functionsStats[funcName] = {calls: 0, declarations:0};
            }
        };

        var pnode = null;
        traverse(ast, function(node) {
            if (node.type === 'FunctionExpression') {
                if(pnode.type == 'Identifier'){
                    var expr = pnode.name;
                    addStatsEntry(expr);
                    functionsStats[expr].declarations++;
                }
            } else if (node.type === 'FunctionDeclaration') {
                addStatsEntry(node.id.name);
                functionsStats[node.id.name].declarations++;
            } else if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
                addStatsEntry(node.callee.name);
                functionsStats[node.callee.name].calls++;
            }else if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression'){
                var lexpr = node.callee.property.name;
                addStatsEntry(lexpr);
                functionsStats[lexpr].calls++;
            }
            pnode = node;
        });
        processResults(functionsStats);
    }

    function processResults(results) {
        //console.log(JSON.stringify(results));
        for (var name in results) {
            if (results.hasOwnProperty(name)) {
                var stats = results[name];
                if (stats.declarations === 0) {
                    console.log('Function', name, 'undeclared');
                } else if (stats.declarations > 1) {
                    console.log('Function', name, 'decalred multiple times');
                } else if (stats.calls === 0) {
                    console.log('Function', name, 'declared but not called');
                }
            }
        }
    }
    analyzeCode(data);
}

// Read the file and print its contents.
var filename = process.argv[2];
fs.readFile(filename, 'utf8', function(err, data) {
  if (err) throw err;
  console.log('OK: ' + filename);
  notifydeadcode(data);
});

所以如果你把它放在像 deadfunc.js 这样的文件中,然后像这样调用它:

node deadfunc.js test.js

test.js 包含的地方:

helpers = { 
    doSomething:function(){ },
    doSomethingElse:function(){ } 
};
helpers.doSomethingElse();

你会得到输出:

OK: test.js
Function doSomething declared but not called

最后要注意的一件事:尝试查找未使用的变量和函数可能是一个兔子洞,因为您会遇到像 eval 和从字符串创建的函数这样的情况。您还必须考虑 apply 和 call 等。我认为这就是为什么我们今天的静态分析器中没有此功能。

【讨论】:

  • 感谢您的有趣回答和深思。
  • 很高兴,我希望您能够以某种方式满足您的需求。然而,一个选项,我不知道为什么我没有想到这一点,但您可能会考虑使用像伊斯坦布尔这样的代码覆盖工具,然后也许有一种方法可以使用 escodegen 使伊斯坦布尔自动化,从而消除不存在的死函数'不满足任何覆盖范围!这可能是一个很好的项目,但可能需要几天的修补。 gotwarlost.github.io/istanbul
猜你喜欢
  • 2013-11-04
  • 2018-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
  • 2014-05-16
相关资源
最近更新 更多