【问题标题】:Mangle nested classes and variables with UglifyJS使用 UglifyJS 处理嵌套类和变量
【发布时间】:2013-04-26 10:39:20
【问题描述】:

我使用 UglifyJS 来缩小一组连接的文件,效果很好,但还不够好。构建的库使用命名空间,因此类、函数和常量存储在根命名空间变量中:

(function() {
  var root = { api:{}, core:{}, names:{} };

  /* util.js file */
  root.names.SOME_LONG_NAMED_CONST='Angel';

  /* Person.js file */
  root.core.Person = function(name) { this.name = name };

  /* API.js with the functions we want to expose */
  root.api.perform = function(param_for_api) { /* do something */ }

  window.lib_name.perform = root.api.perform;

})();

缩小到不那么最小的版本

(function(){var a={api:{},core:{},names:{}};a.names.SOME_LONG_NAMED_CONST="Angel",a.core.Person=function(a){this.name=a},a.api.perform=function(){},window.lib_name.perform=a.api.perform})();

我理解 uglify 可能认为 root var 是一种必须保持原样且不能更改的数据结构。有没有办法让 UglifyJS 破坏根命名空间中的嵌套名称?

【问题讨论】:

    标签: javascript minify uglifyjs uglifyjs2


    【解决方案1】:

    我一直在尝试使用 UglifyJS2 的 --mangle-props 并且可以告诉你:'它弄得一团糟'。

    正如有人指出的那样:'开发人员应该决定要破坏哪些属性,而不是 uglifyjs'

    我正在使用这个选项来解决这个问题:

    --mangle-props
    --mangle-regexp="/_$/"
    

    正则表达式匹配任何末尾带有下划线的属性。

    您要求破坏根命名空间中的嵌套名称。所以,你的代码:

    (function() {
      var root = { api:{}, core:{}, names:{} };
    
      root.names.SOME_LONG_NAMED_CONST_='Angel';
    
      root.core.Person_ = function(name) { this.name = name };
    
      root.api.perform_ = function(param_for_api) {  }
    
      window.lib_name.perform = root.api.perform;
    })();
    

    会导致:

    (function() {
        var n = {
            api: {},
            core: {},
            names: {}
        };
        n.names.a = "Angel";
        n.core.b = function(n) {
            this.name = n;
        };
        n.api.c = function(n) {};
        window.lib_name.perform = n.api.c;
    })();
    

    命令: uglifyjs --beautify --mangle --mangle-props --mangle-regex="/_$/" -- file.js

    如果你想破坏第一级根命名空间(api, core, names),只需在它们上面加上下划线(api_, core_, names_),你就可以控制;)

    附注:当您修改其他 js 文件可用的属性时,您应该使用相同的命令将所有文件一起修改,因此所有文件都将使用相同的标识符。

    【讨论】:

    • 不错。我更喜欢前导下划线(我的 C++ 时代的残余物),并使用 /^_/
    • 我明白了,但我是用下划线结尾的,因为这样更容易输入(代码完成效果更好)。
    【解决方案2】:

    最新版本的 uglify(今天)具有对象属性修改功能,请参阅 v2.4.18。它还支持保留文件,以排除您不想损坏的对象属性和变量。看看吧。

    使用--mangle-props 选项和--reserved-file filename1.json filename2.json 等......

    【讨论】:

    • 我认为将 uglify 告诉 mangle only these 而不是 mangle except this 更容易,因为在后者中,您创建的每个新关键字都可能被意外损坏(忘记插入排除列表)
    【解决方案3】:

    正如@Jan-Misker 在他的回答中解释的那样,属性名称修改不是一个好主意,因为它可能会破坏您的代码。

    但是,您可以通过将属性名称定义为局部变量来解决此问题,并将所有 .properties 修改为 [keys],以减小文件大小:

    (function() {
      var API = 'api';
      var CORE = 'core';
      var NAMES = 'names';
      var SLNC = 'SOME_LONG_NAMED_CONST';
    
      var root = {};
      root[API]={};
      root[CORE]={};
      root[NAMES]={};
    
      /* util.js file */
      root[NAMES][SLNC] ='Angel';
    
      /* Person.js file */
      root[CORE].Person = function(name) { this.name = name };
    
      /* API.js with the functions we want to expose */
      root[API].perform = function(param_for_api) { /* do something */ }
    
      window.lib_name.perform = root[API].perform;
    
    })();
    

    因为现在所有的属性都变成了一个局部变量,uglify js 会破坏/缩短变量名,因此你的整体文件大小会减小:

    !function(){var a="api",b="core",c="names",d="SOME_LONG_NAMED_CONST",e={};e[a]={},e[b]={},e[c]={},e[c][d]="Angel",e[b].Person=function(a){this.name=a},e[a].perform=function(){},window.lib_name.perform=e[a].perform}();
    

    但是,减小文件大小并不意味着您将在真实服务器上获得更短的下载时间,因为通常我们的 http 传输是 gzip 压缩的,大部分重复将由您的 http 服务器压缩,它比人类做得更好。

    【讨论】:

      【解决方案4】:

      除了@JanMisker 的观点(这是完全有效的)之外,重写属性是不安全的,因为它们可能会暴露给缩小范围之外的代码。

      虽然自执行函数是有作用域的,如果代码只有

      (function() {
        var root = { api:{}, core:{}, names:{} };
        root.names.SOME_LONG_NAMED_CONST='Angel';
        alert(root.names.SOME_LONG_NAMED_CONST); // some code that does something
      })();
      

      确实,在函数之外,没有办法访问根对象,所以重写属性名是安全的,下面的代码会产生同样的结果:

      (function() {
        var a = { b:{}, c:{}, d:{} };
        a.d.e='Angel';
        alert(a.d.e);
      })();
      

      但即使您在私有范围内,您也可以访问,更重要的是从外部范围分配给变量!想象一下:

      (function() {
        var root = { api:{}, core:{}, names:{} };
        root.api.perform = function(param_for_api) { /* do something */ }
        window.lib_name = root.api;
      })();
      

      您不仅公开了一个函数,而且公开了一个带有函数的对象。并且从任何可见窗口的地方都可以看到该功能。

      因此,例如,在 javascript 控制台中编写以下代码会产生不同的结果,无论是否缩小:

      window.lib_name.perform(asdf);
      

      通过缩小你必须写:

      window.lib_name.f(asdf);
      

      或类似的东西。

      请记住,在您的缩小范围之外总是有代码。

      拥有绝对最小的 JS 并不是那么重要,但如果出于某种原因这很重要(例如:外星人绑架了你的继女,而让她回来的唯一方法是将其缩小到 100 个左右字符以下) ),您可以手动将不需要的长属性名称替换为较短的属性名称,只需确保它不会在任何地方公开,并且不会通过关联数组表示法 (root['api']) 访问。

      【讨论】:

      • 虽然继女没有被绑架 :) 嵌套变形的需要对于缩小至关重要。我认为代码大小可以减少 20% 以上。请注意,我故意使用 window.lib_name.perform = root.api.perform 所以缩小仍然可以工作
      • 问题在于动态类型和基本上关于 javascript 中对象的所有内容都非常宽容,以至于 uglifyjs 不能 100% 确定仅通过查看脚本就可以确定您公开的内容。 @JanMisker 还提出了关于字符串的观点。它们可以动态构建,然后用作关联数组的索引器。 uglifyjs 无法安全地实现缩小。您必须手动执行此操作。
      • 闭包可以做到(ADVANCED_OPTIMIZATIONS)。
      【解决方案5】:

      当您最小化 Javascript 时,您只能更改变量的名称,api、core 和名称不是变量而是对象的属性。如果这些被最小化器更改,您可能会得到意想不到的结果。如果在你的代码中你会调用

      root["api"].perform = function()...
      

      甚至是类似的东西

      function doIt(section, method, argument) {
          root[section][method](argument);
      }
      doIt('api','perform', 101);
      

      所有完全合法的 JS,但最小化程序永远无法弄清楚发生了什么。

      【讨论】:

      • 是的,我理解你的论点。还有什么我可以做的吗?
      • @Jan 开发者可能会决定这一点,而不是 uglifyjs。我可以为此配置 uglifyjs:'不要破坏字符串',或者,'不要触摸我告诉你的变量'。我一直在使用混淆器,它完美地丑化了所有东西。
      猜你喜欢
      • 2018-02-14
      • 1970-01-01
      • 1970-01-01
      • 2014-12-11
      • 2020-07-02
      • 2013-03-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多