【问题标题】:Why is it recommended to use concat then uglify when the latter can do both?当后者可以同时使用时,为什么建议使用 concat 然后 uglify ?
【发布时间】:2014-03-20 20:43:59
【问题描述】:

我不断看到有关将 JS 文件准备好用于生产的建议是 concat 然后 uglify。

例如 here,在 Yeoman 的繁重任务中。

默认流程是:concat -> uglifyjs。

考虑到 UglifyJS 可以同时进行连接和缩小,为什么你会同时需要两者?

谢谢。

【问题讨论】:

  • 我曾经使用concat,但选择仅使用uglify,它可以满足您的所有需求 - 正如您所指出的。我猜有些人同时使用这两种方法来补偿他们项目的复杂性,或者因为他们宁愿让uglify 只做它除了连接之外的事情。 concat 还允许使用分隔符,据我所知,uglify 不支持。

标签: gruntjs uglifyjs grunt-contrib-concat


【解决方案1】:

运行基本测试以查看在执行 concat 和然后执行 uglify 与仅执行 uglify 之间是否存在性能差异。

package.json

{
  "name": "grunt-concat-vs-uglify",
  "version": "0.0.1",
  "description": "A basic test to see if we can ditch concat and use only uglify for JS files.",
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-concat": "^0.5.0",
    "grunt-contrib-uglify": "^0.6.0",
    "load-grunt-tasks": "^1.0.0",
    "time-grunt": "^1.0.0"
  }
}

Gruntfile.js

module.exports = function (grunt) {

    // Display the elapsed execution time of grunt tasks
    require('time-grunt')(grunt);
    // Load all grunt-* packages from package.json
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        paths: {
            src: {
                js: 'src/**/*.js'
            },
            dest: {
                js: 'dist/main.js',
                jsMin: 'dist/main.min.js'
            }
        },
        concat: {
            js: {
                options: {
                    separator: ';'
                },
                src: '<%= paths.src.js %>',
                dest: '<%= paths.dest.js %>'
            }
        },
        uglify: {
            options: {
                compress: true,
                mangle: true,
                sourceMap: true
            },
            target: {
                src: '<%= paths.src.js %>',
                dest: '<%= paths.dest.jsMin %>'
            }
        }
    });

    grunt.registerTask('default', 'concat vs. uglify', function (concat) {
        // grunt default:true
        if (concat) {
            // Update the uglify dest to be the result of concat
            var dest = grunt.config('concat.js.dest');
            grunt.config('uglify.target.src', dest);

            grunt.task.run('concat');
        }

        // grunt default
        grunt.task.run('uglify');
    });
};

src中,我放了一堆JS文件,包括未压缩的jQuery源代码,复制了几次,分散到子文件夹中。比普通网站/应用程序通常拥有的要多得多。

事实证明,在这两种情况下,连接和压缩所有这些文件所需的时间基本相同。
除了concat 上使用sourceMap: true 选项时也是如此(见下文)。

在我的电脑上:

grunt default      : 6.2s (just uglify)
grunt default:true : 6s   (concat and uglify)

值得注意的是,两种情况下生成的main.min.js 是相同的。
此外,uglify 在合并文件时会自动使用正确的分隔符。

唯一重要的情况是将sourceMap: true 添加到concat options
这会在main.js 旁边创建一个main.js.map 文件,并导致:

grunt default      : 6.2s (just uglify)
grunt default:true : 13s  (concat and uglify)

但是如果生产站点只加载min版本,这个选项就没用了。

uglify 之前使用concat 确实发现了一个主要的缺点
当其中一个 JS 文件发生错误时,sourcemap 将链接到串联的 main.js 文件而不是原始文件。而当uglify 完成整个工作时,它链接到原始文件。

更新:
我们可以向uglify 添加另外两个选项,将uglify 源映射链接到concat 源映射,从而处理我上面提到的“缺点”。

    uglify: {
        options: {
            compress: true,
            mangle: true,
            sourceMap: true,
            sourceMapIncludeSources: true,
            sourceMapIn: '<%= paths.dest.js %>.map',
        },
        target: {
            src: '<%= paths.src.js %>',
            dest: '<%= paths.dest.jsMin %>'
        }
    }

但这似乎非常不必要。

结论

我认为可以肯定的结论是,如果我们使用 uglify,我们可以将 concat 用于 JS 文件,并在需要时将其用于其他目的。

【讨论】:

  • 很好地展示了性能优势!我只想指出,我的回答是为了解释为什么有些人仍然选择transformation flow(又名concat -&gt; uglify)工作流来编译javascript。这种工作流程选择与性能无关 - afaik - 我个人认为它只在非常复杂的项目中才有意义。
  • @WallaceSidhrée 谢谢!在一个非常复杂的项目中,在uglify 之前使用concat 有什么好处?也许是 concat 中的 process 选项?
  • 还有另一种情况你没有考虑:使用 concat 但不使用 uglify。您可能只会在开发模式下使用它。我的轶事测试表明,这大约是 100 倍的速度(例如:40 毫秒与 4 秒),这在您处理项目时可能是一个巨大的好处。
  • @davethegr8 OP专门询问了concatuglify的组合。如果您只需要连接文件而不压缩它们,那么您是对的,您显然只使用concat,但这不是讨论的用例。而且最好以任何方式在最终结果上进行开发。减少开发和生产之间的摩擦。
【解决方案2】:

在您提到的示例中(我在下面引用),文件首先与concat 连接,然后由uglify 进行丑化/缩小:

{
  concat: {
    '.tmp/concat/js/app.js': [
      'app/js/app.js',
      'app/js/controllers/thing-controller.js',
      'app/js/models/thing-model.js',
      'app/js/views/thing-view.js'
    ]
  },
  uglifyjs: {
    'dist/js/app.js': ['.tmp/concat/js/app.js']
  }
}

同样可以通过以下方式实现:

{
  uglifyjs: {
    'dist/js/app.js': [
      'app/js/app.js',
      'app/js/controllers/thing-controller.js',
      'app/js/models/thing-model.js',
      'app/js/views/thing-view.js'
    ]
  }
}

通常,任务clean 将在写入临时文件夹(在本例中为concat)的任务之后运行,并删除该文件夹中的任何内容。有些人还喜欢在compass 之类的任务之前运行clean,以删除诸如随机命名的图像精灵之类的东西(每次任务运行时都会新生成)。即使是最偏执的人,这也会让车轮保持转动。

这完全取决于偏好和工作流程,与何时运行 jshint 一样。有些人喜欢在编译之前运行它,有些人喜欢在编译后的文件上运行它。

具有大量 JavaScript 文件的复杂项目 - 或者具有越来越多的同行和贡献者,可能会选择在 uglify 之外连接文件,以使内容更具可读性和可维护性。我认为这就是 Yeoman 选择转换流程背后的原因。

uglify 根据项目的配置可能会非常慢,因此首先将其与concat 连接可能会有一些小收获 - 但这必须得到确认。

concat 也支持分隔符,uglify 不支持 README.md 文件。

concat: {
  options: {
    separator: ';',
  }
}

【讨论】:

    猜你喜欢
    • 2017-12-04
    • 2019-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-21
    • 1970-01-01
    • 1970-01-01
    • 2016-07-07
    相关资源
    最近更新 更多