Почему рекомендуется использовать concat, а затем uglify, когда последний может делать и то, и другое?

Я продолжаю видеть рекомендацию по подготовке JS-файлов к производству для объединения, а затем для удаления.

Например, здесь, в одной из основных задач Йомана.

По умолчанию поток следующий: concat -> uglifyjs.

Учитывая, что UglifyJS может выполнять как конкатенацию, так и минимизацию, зачем вам нужно и то, и другое одновременно?

Спасибо.


person Francisc    schedule 20.03.2014    source источник
comment
Раньше я использовал concat, но решил использовать только uglify, который делает все, что нужно, как вы указали. Я предполагаю, что некоторые люди используют оба, чтобы компенсировать сложность своих проектов или потому, что они предпочитают, чтобы uglify делал только то, что он делает, кроме конкатенации. concat также позволяет использовать разделители, чего uglify, насколько мне известно, нет.   -  person Wallace Sidhrée    schedule 21.03.2014


Ответы (2)


Выполнение базового теста, чтобы увидеть, есть ли разница в производительности между выполнением 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, несколько раз скопировал, разложил по подпапкам. Гораздо больше, чем обычно есть на обычном сайте/приложении.

Оказывается, время, необходимое для объединения и сжатия всех этих файлов, практически одинаково в обоих сценариях.
За исключением использования параметра sourceMap: true и для concat (см. ниже).

На моем компьютере:

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

Стоит отметить, что результат main.min.js одинаков в обоих случаях.
Кроме того, uglify автоматически заботится об использовании правильного разделителя при объединении файлов.

Единственный случай, когда это имеет значение, — это добавление sourceMap: true к concat options.
Это создает файл main.js.map рядом с main.js и приводит к следующему результату:

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

Но если рабочий сайт загружает только версию min, этот вариант бесполезен.

Я обнаружил серьезный недостаток использования concat перед uglify.
Когда в одном из JS-файлов возникает ошибка, sourcemap будет ссылаться на объединенный файл main.js, а не на исходный файл. Когда uglify выполнит всю работу, он будет ссылаться на исходный файл.

Обновление:
Мы можем добавить еще 2 параметра в 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 %>'
        }
    }

Но это кажется крайне ненужным.

Вывод

Я думаю, можно с уверенностью заключить, что мы можем отказаться от concat для файлов JS, если мы используем uglify, и использовать его для других целей, когда это необходимо.

person Alex Ilyaev    schedule 12.12.2014
comment
Хорошая попытка показать преимущество в производительности! Я просто хотел бы отметить, что мой ответ должен был объяснить, почему некоторые люди все еще выбирают преобразование (он же concat -> uglify) рабочего процесса в javascript компиляцию. Этот выбор рабочего процесса имеет мало общего с производительностью — на самом деле — и я лично думаю, что это имеет смысл только в очень сложных проектах. - person Wallace Sidhrée; 19.12.2014
comment
@WallaceSidhrée Спасибо! Какая польза от использования concat перед uglify в очень сложном проекте? Возможно, опция process в concat? - person Alex Ilyaev; 19.12.2014
comment
Есть еще один случай, который вы не рассмотрели: использование concat, но не uglify. Вы, вероятно, будете использовать это только в режиме разработки. Мои неподтвержденные тесты показывают, что это примерно в 100 раз быстрее (пример: 40 мс против 4 с), что может быть огромным преимуществом, пока вы работаете над своим проектом. - person davethegr8; 30.12.2014
comment
@ davethegr8 ОП конкретно спросил о сочетании concat с uglify. Вы правы, если вам нужно только объединять файлы, а не сжимать их, вы, очевидно, будете использовать только concat, но это не обсуждаемый вариант использования. А также лучше развиваться на конечном результате в любом случае. Меньше трений между разработчиком и производством. - person Alex Ilyaev; 30.12.2014

В упомянутом вами примере, который я цитирую ниже, файлы сначала объединяются с помощью 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) и удаляют все содержимое этой папки. Некоторым людям также нравится запускать clean перед такими задачами, как compass, чтобы удалить такие вещи, как спрайты изображений со случайными именами (которые заново генерируются каждый раз при запуске задачи). Это заставило бы колеса вращаться даже у самых параноиков.

Это все вопрос предпочтений и рабочего процесса, как и когда запускать jshint. Некоторым нравится запускать его перед компиляцией, другие предпочитают запускать его на скомпилированных файлах.

Сложные проекты с невероятным количеством файлов JavaScript или с постоянно растущим числом коллег и участников могут решить объединить файлы за пределами uglify просто для того, чтобы сделать их более читабельными и удобными в сопровождении. Я думаю, что это и послужило причиной выбора Yeoman потока трансформации.

uglify может быть заведомо медленным в зависимости от конфигурации проекта, поэтому может быть небольшой выигрыш в объединении его с concat первым, но это должно быть подтверждено.

concat также поддерживает разделители, которых uglify нет в отношении README.md файлов.

concat: {
  options: {
    separator: ';',
  }
}
person Wallace Sidhrée    schedule 20.03.2014