为什么建议使用concat再使用uglify,而后者可以同时完成这两个任务?

39

我一直看到的建议是将JS文件准备好并压缩,例如在Yeoman的grunt任务中这里

默认情况下流程为:concat -> uglifyjs。

考虑到UglifyJS可以同时进行拼接和缩小,为什么您需要同时进行这两个操作呢?

谢谢。


我曾经使用concat,但选择仅使用uglify,因为它可以完成所有必要的工作 - 正如你所指出的那样。我猜有些人同时使用两者,要么是为了弥补他们项目的复杂性,要么是因为他们宁愿让uglify只做除串联之外的事情。就我所知,concat还允许使用分隔符,而uglify则不行。 - Wallace Sidhrée
2个回答

49

运行一个基本测试,以查看执行 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 选项中。这会在 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添加2个选项,将uglify sourcemap链接到concat sourcemap,从而处理我上面提到的"缺点"。
    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,我们可以放弃对JS文件使用concat,并在需要时用于其他目的。


1
不错的表现优势展示!我只想指出,我的答案旨在解释为什么有些人仍然选择转换流程(又称concat->uglify)工作流来编译javascript。据我所知,这种工作流选择与性能关系不大,我个人认为它只有在非常复杂的项目中才有意义。 - Wallace Sidhrée
@WallaceSidhrée 谢谢!在一个非常复杂的项目中,在使用 uglify 之前使用 concat 的好处是什么?也许是 concat 中的 process 选项? - Alex Ilyaev
还有一种情况你没有考虑:使用concat但不使用uglify。你可能只在开发模式下使用它。我的个人测试显示,这种方式大约快100倍(例如:40毫秒 vs 4秒),这在你开发项目时可能会带来巨大的好处。 - davethegr8
4
@ davethegr8提到了OP特别询问了“concat”与“uglify”的组合。如果您只需要连接文件而不压缩它们,那么您显然只使用“concat”,但这并不是讨论的用例。无论如何,最好还是开发最终结果。这样可以减少开发和生产之间的摩擦。 - Alex Ilyaev

29
在你提到的例子中,我引用如下内容:文件首先使用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 还支持分隔符,而就 README.md 文件而言,uglify 并不支持。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接