company_banner

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

    Мы продолжаем нашу колонку по теме ASP.NET 5 публикацией от Дмитрия Сикорского ( DmitrySikorsky) — руководителя компании «Юбрейнианс» из Украины. В этой статье Дмитрий подробнее рассказывает о сценариях применения с ASP.NET 5 популярного средства Gulp. Предыдущие статьи из колонки всегда можно прочитать по ссылке #aspnetcolumn — Владимир Юнев
    До появления ASP.NET 5 я никогда не использовал такие инструменты, как Gulp, поэтому пришлось уделить некоторое время и разобраться, что же это такое, когда я создал свой первый проект на этой платформе (правда, тогда там еще был Grunt, но это не важно). Не стану вдаваться в базовые вещи, которые уже и так везде достаточно подробно описаны (подразумеваю, что в вашем проекте уже есть Gulpfile.js и вы можете выполнять задания из него, используя диспетчер выполнения задач Visual Studio 2015), а сразу перейду к делу и на практике покажу, как можно использовать Gulp для автоматизации всего-всего в вашем проекте на ASP.NET 5.


    В статье будут приведены фрагменты файла 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 и другие темы.

    Об авторе


    Сикорский Дмитрий Александрович
    Компания «Юбрейнианс» (http://ubrainians.com/)
    Владелец, руководитель
    DmitrySikorsky
    Microsoft 260,89
    Microsoft — мировой лидер в области ПО и ИТ-услуг
    Поделиться публикацией
    Комментарии 13
    • 0
      Правильно ли я понимаю, что Visual Studio 2015 подхватывает Task'и только из одного gulp-файла на весь Solution?
      Вот, скажем, если у меня в солюшене несколько ASP.NET проектов и в каждом по gulp-файлу, то как их засунуть в Task Runner?

      image
      • +1
        Task Runner Explorer в выпадающем списке отображает все проекты, где есть gulpfile.js, и позволяет переключаться между ними в любой момент. Т. е. вы сможете выполнять задания из всех своих gulp-файлов.
        • 0
          Ну если вы это видели в живую, то ок. У меня просто так и не вышло скормить студии больше одного gulp-файла.
          • 0
            Не только видел, но и использовал в собственном решении. Безо всяких дополнительных действий или сложностей. В моем решении все проекты были физически размещены на одном уровне в файловой системе, а в решении находились в различных папках. Может быть это имеет значение.
      • 0
        Мне кажется если в солюшене выбрать проект ка запускающий (set as startup project) то галп подхватит текущий галп файл.
        • –7
          Хоть бы преимущества этой жуйни перед msbuild'ом написали. А то зачем-то переводят всех на новую технологию (которая, кстати, в студии пока весьма криво работает), и никому не говорят зачем. Притом технология на JavaScript. Фу.
          • +5
            Вас никто не заставляет переходить, живите с msbuild'ом и будьте счастливы.
            К сожалению столкнулся на работе с подобным вашему отношением к javascript и различным технологиям на его основе. На ваше и подобным вам горе или счастье пока c# в браузере не выполняется а .net надстроки вокруг javascript весьма сильно устарели, так как многим бекэнд разрабочтикам не понять нужд фронтэнда и поэтому у них нет возможности, знаний и желания сделать что-то действительно удобное и полезное. Также нету желания совершенствовать существующие инструмены для фронтэнда (кстати не у всех в .net мире такое же представление как у вас, вот взять например автора этой статьи). Также для расширения инструментов в .net уже у фронтэнд разрабочиков нету знаний и желания разбираться как в .net коде что-то подкрутить. Вот поэтому с приходом node.js у многих разработчиков знающих js появилась возможность поправить положение и судя по ежедневным статьям о различных технологиях (например упомянутой gulp) и инструментах на базе js, дела идут очень успешно, так что это win-win для всех. Вы будете фокусироваться на бекэнд разработке и использовать инструменты которые вам удобны, а фронтэнд разработчики позаботятся о своей части.
            • +1
              Вообще говоря заставляют, без project.json достаточно проблемно использовать vNext.
          • +3
            Чтобы избавиться от проблем с глубокой вложенностью в node_modules на Windows можно использовать npm 3 версии. Он все пакеты ставит в один node_modules, а не в каждый пакет отдельно.
            • +1
              Дотнетчики знакомятся с Галп, называя новой технологией. А фронтендеры уже отказываются от Галп по причине негибкости и большого количества ручного труда на сложных проектах в пользу Webpack, который в отличии от Галп встраивается в проект и следит за всеми зависимостями.
              • 0
                ждём через пару лет анонс поддержки в студии «новой технологии» webpack
              • +1
                Для меня это странно звучит. Эти инструменты перекрывают функционал друг друга где-то на 80%, но 20% все равно остается. Где-то нужно просто забандлить все в один файл, а где-то приходится манипулировать кучей ресурсов, чтобы в итоге построить подходящий билд.
                У меня везде Webpack вызывается внутри таска в Gulp, наиболее удобный вариант для меня.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое