Версионность ассетов при использовании gulp

assetsПри изменении клиентского кода нужно как-то заставить браузер клиента перезагрузить измененные файлы. Эта проблема достаточно распространенная. Скорее всего, вы столкнетесь с ней вне зависимости от того, какими технологиями пользуетесь при веб-разработке. Этот пример о решении проблемы при создании фронтенд проекта с использованием javascript и gulp.

Решить можно несколькими способами.

Пусть, например, 7e50961489 - это новая версия ассетов. На самом деле, она может быть любой, главное, чтобы отличалась от тех, что были раньше. Можно просто нумеровать - 1, 2, 3 и т.д. Но так не слишком удобно. Дальше станет понятно, почему.

1) Можно при обновлении приложения всегда менять имена файлов:

<script src="/js/lib-7e50961489.js" type="text/javascript"></script>

2) Можно добавлять версию ассетов, например: 

<script src="/js/lib.js?v=7e50961489" type="text/javascript"></script>

3) Можно менять путь к файлу:

<script src="/js/7e50961489/lib.js" type="text/javascript"></script>

Последний вариант хорош тем, что не нужно менять имя файла, а фрагмент пути можно разрулить через конфигурацию nginx. (Таким образом можно не менять файловую структуру).

В этом примере рассмотрим первый вариант.

В одной из предыдущих заметок мы разобрали пример конфигурации gulp-файла для фронтенд проекта. Если раньше вы не работали с gulp, обязательно ознакомьтесь с ней перед дальнейшем чтением.

Для того, чтобы решить поставленную вначале задачу, нам понадобится еще три плагины для gulp: gulp-rev, gulp-rev-replace, gulp-rename

npm install gulp-rev gulp-rev-replace gulp-rename --save

Основную работу делает gulp-rev. В его задачу входит переименование файлов ассетов в соответствии с версией. 

Как и обещал, объясняю, почему версия не просто 1, 2, 3, ... Версия содержит информацию о контрольной сумме файлов. Поэтому файлы без изменений не пересобираются.

Итак, расширим задачу билда CoffeeScript-файлов. Для начала, используем gulp-rev:

gulp.task('coffee-lib', function () {
    gulp.src(['./js/**/*.coffee', '!./js/app/web.coffee'])
        .pipe(coffee())
        .pipe(concat('lib.js'))
        // добавляем версию в имя файла
        .pipe(rev())
        .pipe(gulp.dest('./public/js/'))
        // сохраняем версию в файл - понадобится позже
        .pipe(rev.manifest('rev-manifest-lib.json'))
        .pipe(gulp.dest('./build'))
    ;
});

С самими файлами ассетов разобрались. Дальше нужно, чтобы в местах подключения были задействованы новые файлы. Для этого я создал "шаблонный файл" index.html.dist, который при каждой сборке будет билдится в index.html, причем последний должен быть не под контролем версий.

 <!--index.html.dist-->
....
<script src="js/lib.js"></script>
....

Используем rev-replace для замены путей к подключаемым файлам и rename, чтобы переименовать (точнее, скопировать) index.html.dist в index.html:

gulp.task('template', ['coffee-lib'], function () {
    var manifest = gulp.src([
        './build/rev-manifest-lib.json',
    ]);

    return gulp.src('./public/index.html.dist')
        .pipe(revReplace({
            manifest: manifest,
            replaceInExtensions: ['.dist']
        }))
        // переименуем файл
        .pipe(rename("index.html"))
        .pipe(gulp.dest('./public'))
    ;
});

Обратите внимание на строку в конфиге replaceInExtensions: ['.dist']. По умолчанию, rev-replace не обрабатывает .dist-файлы

Осталось только добавить новую('template') задачу в 'default' task ('coffee-lib' уже должна была быть добавлена раньше):

gulp.task('default', [..., 'template']);