Готовим ASP.NET 5: подробнее про работу с Gulp

Мы продолжаем нашу колонку по теме ASP.NET 5 публикацией от Дмитрия Сикорского (DmitrySikorsky) — разработчика из Украины. В этой статье Дмитрий подробнее рассказывает о сценариях применения с ASP.NET 5 популярного средства Gulp. Предыдущие статьи из колонки всегда можно прочитать по ссылке #aspnetcolumn — Владимир Юнев

До появления ASP.NET 5 я никогда не использовал такие инструменты, как Gulp, поэтому пришлось уделить некоторое время и разобраться, что же это такое, когда я создал свой первый проект на этой платформе (правда, тогда там еще был Grunt, но это не важно). Не стану вдаваться в базовые вещи, которые уже и так везде достаточно подробно описаны (подразумеваю, что в вашем проекте уже есть Gulpfile.js и вы можете выполнять задания из него, используя диспетчер выполнения задач Visual Studio 2015), а сразу перейду к делу и на практике покажу, как можно использовать Gulp для автоматизации всего-всего в вашем проекте на ASP.NET 5.

b6b3fd354c8a4922af7bd04573a84421.png


В статье будут приведены фрагменты файла Gulpfile.js тестового проекта AspNet5Gulpization, который целиком лежит тут: https://github.com/DmitrySikorsky/AspNet5Gulpization.

Вступление


Вы наверняка знаете, для чего используется новая папка wwwroot. На самом деле, с ее появлением я немного по-новому взглянул на скрипты, стили и картинки. А именно, как и серверный код сайта, теперь я разделяю их на исходники и готовые к публикации объекты.

Подготовка

aspnetcolumngithubСовет! Вы можете попробовать все самостоятельно или загрузив исходный код из GitHub https://github.com/DmitrySikorsky/AspNet5Gulpization.

Для начала, нам необходимо перечислить в нашем Gulp-файле все пакеты, которые мы будем использовать в своих заданиях (и убедиться, что все они присутствуют в package.json):

var gulp = require("gulp"),
  autoprefixer = require("gulp-autoprefixer"),
  concat = require("gulp-concat"),
  del = require("del"),
  minifyCss = require("gulp-minify-css"),
  rename = require("gulp-rename"),
  runSequence = require("run-sequence"),
  sass = require("gulp-sass"),
  tsc = require("gulp-tsc"),
  uglify = require("gulp-uglify");


Далее, очень удобно представлять пути где лежат исходные и результирующие файлы в виде объекта, чтобы иметь возможность редактировать их все в одном месте:

var paths = {
  frontend: {
    scss: {
      src: [
        "styles/*.scss"
      ],
      dest: "wwwroot/css"
    },
    ts: {
      src: [
        "scripts/*.ts"
      ],
      dest: "wwwroot/js"
    }
  },
  shared: {
    bower: {
      src: "bower_components",
      dest: "wwwroot/lib"
    }
  }
}


И наконец, опишем основную Gulp-задачу, которая будет производить перестроение всех скриптов и стилей, их обработку и копирование в результирующую папку:

gulp.task(
  "rebuild",
  function (cb) {
    runSequence(
      "clean",
      "build",
      "minify",
      "delete-unminified",
      "rename-temp-minified",
      "delete-temp-minified",
      cb
    );
  }
);


Пакет runSequence позволяет выполнять задачи одну за другой, последовательно, что в нашем случае очень важно. Также, следует обязательно передавать функцию обратного вызова cb в качестве последней задачи последовательности, чтобы вызывающий код мог быть уведомленным о завершении процесса в целом.

Кстати, чтобы не выполнять задание rebuild вручную, можно либо заставить студию выполнять его при сборке или открытии проекта, либо прямо в Gulp-файле дописать еще одно задание (и выполнять его автоматически при открытии проекта), которое будет устанавливать слушателей на изменение исходных файлов и выполнять задание, когда такое изменение будет обнаружено.

Скрипты


Я очень любил (и продолжаю) JavaScript за его простоту и изящество. Теперь я полюбил еще и TypeScript. (Это замечательный инструмент, рекомендую обратить на него внимание.) Все ts-файлы я обычно храню в папке Scripts, которая игнорируется при публикации проекта. Это исходники клиентских скриптов. Я настроил несколько заданий в моем Gulp-файле, которые сначала компилируют TypeScript в JavaScript, затем сжимает его, затем склеивают в один файл и, наконец, копируют полученный файл в папку wwwroot/js, откуда он и используется в приложении. (Если вы не используете TypeScript, то можно просто пропустить этап его превращения в JavaScript — остальные задания будут работать без изменений.)

По умолчанию, Visual Studio 2015 компилирует ts-файлы в момент их сохранения и складывает полученные js-файлы в ту же папку. Нам это поведение не нужно, поэтому отключаем компиляцию TypeScript в настройках проекта.

Вот так выглядит задание, компилирующее TypeScript:

gulp.task(
  "frontend:build-ts", function (cb) {
    return gulp.src(paths.frontend.ts.src)
      .pipe(tsc())
      .pipe(gulp.dest(paths.frontend.ts.dest));
  }
);


А вот так можно сжать полученный JavaScript и склеить его в один результирующий файл:

gulp.task(
  "frontend:minify-js", function (cb) {
    return gulp.src(paths.frontend.ts.dest + "/*.js")
      .pipe(uglify())
      .pipe(concat("aspnet5gulpization.min.js.temp"))
      .pipe(gulp.dest(paths.frontend.ts.dest));
  }
);


При склейке многих js-файлов в один возможна ситуация, когда они будут добавлены в результирующий файл в неправильном порядке и вы получите сообщения, что что-то не определено, т. к. определение будет расположено после вызова. Если так случится, можно легко задать порядок следования скриптов в результирующем файле вручную. Можно даже перечислить в необходимом порядке те скрипты, которые должны следовать первыми, а затем последним элементом массива просто указать ту же самую папку, и Gulp догадается, что нужно сначала обработать явно указанные файлы из папки, а затем все остальные.

Кстати, если вдруг при восстановлении пакетов NPM вы получаете сообщение об ошибке (опционально, с формулировкой, состоящей из бессвязной последовательности символов), или же Visual Studio 2015 просто падает при попытке восстановить пакеты, возможно это связано с глубиной вложенности вашего проекта в файловой системе. (Частично, я нашел информацию об этом тут: https://github.com/Microsoft/nodejstools/issues/336.) Потратив некоторое время, я просто создал пустой проект в менее вложенной папке, скопировал туда свой package.json, восстановил там пакеты и затем перенес их вместе с папкой node_modules в свой проект. Также, в процессе падения студии в папку npm-cache может попасть испорченный пакет, поэтому стоит иметь это в виду и при необходимости его оттуда удалить.

Стили


Относительно недавно я решил попробовать SCSS. Основной целью было иметь возможность редактировать такие вещи, как цвета, в одном месте, а не с использованием поиска и замены. Также я решил, что было бы здорово, заодно, разделять огромные css-файлы на части, чтобы их было удобнее сопровождать (на мой взгляд, сопровождать стили, избегая при этом их замусоривания, достаточно сложно, т. к. результат изменений сложно поддается тестированию и не всегда очевиден с первого взгляда). Использование SCSS дало хороший результат.

Аналогично TypeScript, мой Gulp-файл содержит задания для компиляции SCSS в CSS, добавления вендорных префиксов, сжатия, склейки и копирования полученного файла в папку wwwroot/css.

Компиляция SCSS в CSS выглядит следующим образом:

gulp.task(
  "frontend:build-scss", function (cb) {
    return gulp.src(paths.frontend.scss.src)
      .pipe(sass())
      .pipe(gulp.dest(paths.frontend.scss.dest));
  }
);


Ну и сжатие, склейка (с одновременной расстановкой вендорных префиксов):

gulp.task(
  "frontend:minify-css", function (cb) {
    return gulp.src(paths.frontend.scss.dest + "/*.css")
      .pipe(minifyCss())
      .pipe(autoprefixer("last 2 version", "safari 5", "ie 8", "ie 9"))
      .pipe(concat("aspnet5gulpization.min.css.temp"))
      .pipe(gulp.dest(paths.frontend.scss.dest));
  }
);


Библиотеки


Если в проекте на ASP.NET 5 вам потребуется, например, JQuery, скорее всего вы загрузите его при помощи Bower, и, в отличии от NuGet, который использовался ранее, вы получите немного больше, чем просто файл jquery.min.js и парочки других. В папке bower_components будет создана папка jquery, в которой, кроме упомянутого выше файла, будет несжатый вариант библиотеки, а также ее исходники (которые, само собой, при публикации будут игнорироваться).

Если задуматься, мы можем использовать эти библиотеки как минимум двумя способами.

Во-первых, можно просто подключить на страницу файл jquery.min.js, предварительно скопировав его в папку wwwroot/lib/jquery. Так поступил я (не знаю, возможно правильнее использовать сервисы, вроде Google Hosted Libraries, чтобы в некоторых случаях браузер брал закешированный при посещении другого сайта вариант библиотеки, а не скачивал ее вновь, но чаще всего я так не делаю).

Во-вторых, можно взять несжатый вариант библиотеки (jquery.js) и обработать его таким же образом, каким обрабатываются другие скрипты в приложении. Т. е. в результате добавить его в единственный общий js-файл, таким образом уменьшив количество запросов к веб-серверу.

Вот задание, копирующее необходимые файлы для трех библиотек:

gulp.task(
  "lib-copy", function (cb) {
    var lib = {
      "/jquery": "/jquery/dist/jquery*.{js,map}",
      "/jquery-validation": "/jquery-validation/dist/jquery.validate*.js",
      "/jquery-validation-unobtrusive": "/jquery-validation-unobtrusive/jquery.validate.unobtrusive*.js"
    };

    for (var $package in lib) {
      gulp
        .src(paths.shared.bower.src + lib[$package])
        .pipe(gulp.dest(paths.shared.bower.dest + $package));
    }

    cb();
  }
);


Выводы


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

Авторам


Друзья, если вам интересно поддержать колонку своим собственным материалом, то прошу написать мне на vyunev@microsoft.com для того чтобы обсудить все детали. Мы разыскиваем авторов, которые могут интересно рассказать про ASP.NET и другие темы.

e7578d232485631d1803f17748521681.jpg

Об авторе


Дмитрий Сикорский,
Украина
DmitrySikorsky

© Habrahabr.ru