Это третья и последняя часть вводной серии по написанию модульных приложений JavaScript. Часть 1 объяснила, почему желательна модульность, и представила модульную структуру дизайна приложения. В части 2 мы создали простое приложение на основе этой структуры. Мы завершим серию подготовкой нашего проекта к развертыванию.

Вступление

Мы закончили часть 2 работающим модульным веб-приложением. Мы написали наши модули и таблицы стилей как отдельные файлы, связанные вместе через наш index.html и загружаемые нашим app.js. Это эффективный и масштабируемый процесс разработки по всем причинам, обсуждаемым в части 1 этой серии.

Но это процесс, в результате которого было создано восемь отдельных файлов. Наш браузер должен открыть и обработать восемь отдельных HTTP-запросов для получения каждого из этих файлов, и это до того, как мы добавим размещенный шрифт или jQuery. Хотя наши файлы невелики и современные браузеры могут выполнять эти запросы параллельно, в большом приложении совокупная задержка этих запросов может привести к более медленной загрузке страницы. Мы можем уменьшить количество http-запросов за счет конкатенации.

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

Наше демонстрационное приложение включает в себя функцию es6 / es2015: шаблонные литералы. Шаблонные литералы удивительно полезны, но их разделители обратных кавычек вызывают проблемы для некоторых минификаторов кода (включая тот, который мы будем использовать). Кроме того, поддержка синтаксиса es6 / es2015 браузером все еще развивается. Мы хотим транспилировать наш исходный код в традиционный JavaScript каждый браузер понимает es5.

Наконец, представьте, что мы составили наши таблицы стилей с помощью SASS вместо CSS. Браузеры изначально не понимают SASS, поэтому мы хотим преобразовать его в обычный CSS. На самом деле мы не будем переписывать наши таблицы стилей - вместо этого мы будем немного хитрить в демонстрационных целях. И пока мы занимаемся этим, мы должны добавить все необходимые селекторы с префиксом поставщика, чтобы разные браузеры отображали наше приложение должным образом.

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

Хотя наша демонстрация представляет собой интерфейсное приложение, поскольку Gulp зависит от Node.js, который обычно используется в серверных приложениях, читатель должен иметь базовые знания о Node. и NPM и установить их локально. В противном случае обратитесь к официальной документации за инструкциями по установке.

Помимо нескольких специфичных для узла функций (require и pipe), остальная часть нашего кода будет написана на обычном JavaScript для es5. Мы потратим немного времени на командную строку, но вам не обязательно быть ниндзя CLI.

Структура приложения

Часть 2 оставила нам следующую структуру файлов / папок:

|-- /src
|    |
|    |-- /css
|    |    |
|    |    |-- app.css
|    |    |-- background.css
|    |    |-- greeting.css
|    |    |-- quote.css
|    |
|    |-- /js
|         |
|         |-- app.js
|         |-- background.js
|         |-- greeting.js
|         |-- quote.js
|
| index.html

В основном мы будем работать в корне папки нашего проекта; Gulp будет работать со всеми этими /src файлами.

Инициализация

Начните с инициализации нового проекта с помощью NPM. Находясь в корневом каталоге нашего проекта, выполните следующую команду терминала:

npm init -y

Если вы опустите флаг -y, NPM задаст вам ряд вопросов о вашем проекте. -y просто указывает NPM принять все ответы по умолчанию. npm init создает новый package.json файл в корневом каталоге вашего проекта, заполненный ответами на вопросы npm init.

Глоток

Gulp - это средство выполнения задач на Node.js - он выполняет задачи, которые мы напишем позже. Но сам по себе Gulp не может выполнить список желаний нашего вступления. Для этого нам сначала нужно установить некоторые плагины, чтобы снабдить Gulp новыми возможностями.

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

Для глобальной установки Gulp могут потребоваться повышенные привилегии. Вы узнаете, если ваша попытка установки завершится ошибкой! В среде Debian / Ubuntu перед командой установки нужно указать sudo. Повышенные привилегии не требуются для установки local пакета Gulp или каких-либо плагинов, которые нам нужны.

Установите глобальный пакет Gulp с помощью следующей команды (sudo при необходимости):

npm install gulp-cli -g

После этого установите Gulp локально. Убедитесь, что вы все еще находитесь в корневой папке демонстрационного проекта, и выполните следующую команду:

npm install gulp --save-dev

Параметр командной строки --save-dev регистрирует пакет как зависимость разработчика в package.json. Если вы сейчас откроете package.json, вы увидите, что у него есть новое свойство "devDependencies" : {...}, в котором указано «gulp» как его единственная зависимость. Все наши плагины будут зарегистрированы здесь после того, как мы их установим. Давай сделаем это сейчас.

Нам потребуются следующие плагины:

  1. Gulp-concat - объединить (объединить) наши отдельные файлы JavaScript и SASS
  2. Gulp-sass - для преобразования файлов SASS в традиционные .css файлы
  3. Gulp-autoprefixer - для автоматического добавления селекторов с префиксом производителя в наши таблицы стилей на основе правил CanIUse
  4. Gulp-babel - для преобразования любого синтаксиса es6 в традиционный JavaScript es5 с помощью Babel
  5. Babel-preset-es2015 - предустановленная конфигурация Babel
  6. Gulp-sourcemaps - для создания исходной карты JavaScript, чтобы во время отладки мы могли понимать вывод консоли нашего браузера.
  7. Gulp-uglify - минимизировать наш JavaScript.

Мы устанавливаем эти плагины с помощью NPM, снова используя переключатель --save-dev, чтобы добавить каждый модуль в package.json. Вы можете установить каждую по отдельности или все сразу:

npm install gulp-concat gulp-sass gulp-autoprefixer gulp-babel babel-preset-es2015 gulp-sourcemaps gulp-uglify --save-dev

Вы могли заметить, что в корневом каталоге вашего проекта была создана папка node_modules. NPM хранит там все, что требуется для наших зависимостей. Нам не нужно будет ничего трогать в node_modules.

Если вы используете Git для контроля версий, сейчас самое время добавить node_modules/ в ваш .gitignore файл. У нас нет причин отслеживать или отправлять эти файлы в удаленный репозиторий. Если они будут удалены, мы можем легко воссоздать их с помощью одной команды npm install.

Наш объект "devDependencies" теперь должен выглядеть так (номера версий актуальны на дату публикации):

"devDependencies": {
  "babel-preset-es2015": "^6.22.0",
  "gulp": "^3.9.1",
  "gulp-autoprefixer": "^3.1.1",
  "gulp-babel": "^6.1.2",
  "gulp-concat": "^2.6.1",
  "gulp-sass": "^3.1.0",
  "gulp-sourcemaps": "^2.4.1",
  "gulp-uglify": "^2.1.0"
}

Gulp имеет все необходимое для обработки наших файлов JavaScript и SASS. Но прежде чем мы сможем приступить к написанию задач, мы должны создать файл gulpfile, содержащий их. Создайте новый gulpfile.js в корневом каталоге вашего проекта и начните с импорта наших подключаемых модулей:

/* gulpfile.js */
var gulp         = require('gulp'),
    concat       = require('gulp-concat'),
    sass         = require('gulp-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    sourcemaps   = require('gulp-sourcemaps'),
    uglify       = require('gulp-uglify'),
    babel        = require('gulp-babel');

Мы построим наши задачи ниже этих модульных заданий.

Обработка CSS

Мы хотим, чтобы Gulp объединял наши отдельные таблицы стилей, преобразовывал SASS в CSS, добавлял все необходимые селекторы с префиксом поставщика и выводил результат в виде единого файла. Но подождите - на самом деле у нас нет SASS. К счастью для нас, традиционный CSS также является действительным SASS, поэтому мы можем подделать его, просто переименовав наши расширения файлов. В папке /src/css/ нашего проекта переименуйте все .css файлы в .scss:

|-- /src
|    |
|    |-- /css
|    |    |
|    |    |-- app.scss
|    |    |-- background.scss
|    |    |-- greeting.scss
|    |    |-- quote.scss

Et voilà: SASS!

Мы наконец готовы написать нашу первую задачу Gulp. Задачи Gulp - это методы, которые принимают два аргумента: имя задачи (в виде строки) и анонимную функцию обратного вызова:

// example
gulp.task('taskName', function () {
    // do stuff
});

Создайте styles задачу в нашем gulpfile.js под назначениями модулей:

/* gulpfile.js */
var gulp         = require('gulp'),
    concat       = require('gulp-concat'),
    sass         = require('gulp-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    sourcemaps   = require('gulp-sourcemaps'),
    uglify       = require('gulp-uglify'),
    babel        = require('gulp-babel');

// process stylesheets
gulp.task('styles', function () {
  gulp.src('src/css/**/*.scss')
    .pipe(concat('quote-app.scss'))
    .pipe(sass().on('error', sass.logError))
    .pipe(autoprefixer({
      browsers: ['last 2 versions']  // config object
    }))
    .pipe(gulp.dest('dist/css'));
});

В обратном вызове задачи мы сначала указываем gulp.src() расположение наших .scss файлов. Странный на вид подстановочный знак /**/ предписывает Gulp искать совпадающие файлы в /src/css и его подпапках. Это особенно удобно, если вы организовали папки проекта по функциям. Затем мы .pipe() эти исходные файлы через каждый из наших подключаемых модулей, попутно обрабатывая их:

  1. Отдельные .scss исходные файлы сначала объединяются в один quote-app.scss файл.
  2. затем этот файл преобразуется в стандартный .css файл
  3. затем добавляются селекторы с префиксом поставщика на основе объекта конфигурации (наш нацелен на две самые последние версии браузера)
  4. наконец, обработанный файл CSS записывается в /dist/css/ (Gulp создаст папки, если они еще не существуют)

Задача №1: выполнено. Запустите его со своего терминала: gulp styles

Gulp создал новый файл /dist/css/quote-app.css - посмотрите его в своем редакторе. Все наши отдельные .scss файлы были объединены, преобразованы в .css и с префиксом поставщика (в частности, свойства flexbox):

В index.html мы теперь можем заменить четыре отдельные ссылки на наши исходные /src/css/ файлы одной <link> на наш новый /dist/css/quote-app.css:

<!-- ===================== css ====================== -->
<link rel="stylesheet" href="dist/css/quote-app.css">

Если вы внесете изменения в исходные /src/css файлы, эти изменения не распространятся на рабочий /dist/css/quote-app.css файл, пока вы повторно не запустите gulp styles. Это раздражает; мы узнаем, как автоматизировать этот процесс после работы с нашим JavaScript.

Обработка JavaScript

Прежде чем мы напишем задачу для обработки наших файлов JavaScript, нам нужно решить потенциальную проблему. В предыдущем разделе мы передали строку ('src/css/**/*.scss') в gulp.src(), которая захватила все файлы SASS, найденные в src/css и его подпапках. Gulp хватает их независимо от порядка. Это не обязательно проблема с SASS / CSS, тем более что мы разместили наши селекторы в пространстве имен. Но порядок имеет решающее значение для нашего JavaScript - наши модули должны быть сначала инициализированы, чтобы их общедоступные методы существовали для вызова app.js.

Мы обеспечиваем определенный порядок файлов, передавая в gulp.src() массив, состоящий из имен наших исходных файлов. Создайте новый массив под нашими импортированными модулями:

/* gulpfile.js */
var gulp         = require('gulp'),
    concat       = require('gulp-concat'),
    sass         = require('gulp-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    sourcemaps   = require('gulp-sourcemaps'),
    uglify       = require('gulp-uglify'),
    babel        = require('gulp-babel');

// ordered array of javascript source files
var sourceJS = [
    'src/js/background.js',
    'src/js/greeting.js',
    'src/js/quote.js',
    'src/js/app.js'        // must come last!
];

Теперь добавьте задачу scripts под существующей задачей styles:

/* gulpfile.js */
/* ... snip ... */
// process scripts
gulp.task('scripts', function () {
  gulp.src(sourceJS)       // <-- note new array
    .pipe(sourcemaps.init())
    .pipe(concat('quote-app.min.js'))
    .pipe(babel({
      presets: ['es2015']  // babel config object
    }))
    .pipe(uglify())
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('dist/js'));
});

После передачи массива исходных файлов в gulp.src мы .pipe() их от одного плагина к другому, как мы это делали в нашей задаче styles:

  1. Сначала инициализируем плагин sourcemaps
  2. затем мы объединяем наши отдельные файлы в один quote-app.min.js файл
  3. Затем Babel переносит любой JavaScript с es6 / es2015 в es5, используя нашу предустановку Babel.
  4. затем мы минимизируем транспилированный код
  5. сгенерированная исходная карта добавляется в конец нашего кода
  6. и, наконец, обработанный файл записывается в /dist/js/

Задача №2: выполнено. Теперь запустите его в своем терминале: gulp scripts

Откройте только что созданный /dist/js/quote-app.min.js в своем редакторе, чтобы увидеть эффект uglify:

Этот неразборчивый беспорядок функционально эквивалентен всему нашему исходному исходному коду. И если вам кажется, что это забавно, посмотрите строку 2 - эта чушь и есть карта источников.

Теперь мы можем заменить отдельные ссылки на каждый файл нашего приложения и модуля одним тегом <script>:

<!-- ================ our javascript ================== -->
<script src="dist/js/quote-app.min.js"></script>

Возможно, вы заметили, что общий размер наших четырех исходных файлов (5,3 КБ) на самом деле меньше, чем наш уменьшенный quote-app.min.js (12,3 КБ). Это не кажется правильным - что происходит? Наш минифицированный файл больше, потому что мы добавили к нему карту источников. Если мы опустим sourcemaps, наш уменьшенный файл весит около 1,6 КБ.

Просмотр файлов на предмет изменений

Повторный запуск задач Gulp вручную после каждого незначительного изменения кода добавляет раздражающее прерывание нашего рабочего процесса. Глоток должен облегчить жизнь. И это так, благодаря одной из величайших функций Gulp: наблюдателям. Наблюдатель автоматически запускает задачи в ответ на события. Мы напишем тот, который следит за нашими исходными .scss файлами на предмет изменений и запускает нашу styles задачу в ответ.

Но сначала мы создадим default задачу, содержащую нашего наблюдателя. Добавьте его после нашей scripts задачи:

/* gulpfile.js */
/* ... snip ... */
// default task contains our watcher
gulp.task('default', ['styles'], function() {
  // watch sass source files and convert on changes
  gulp.watch('src/css/**/*.scss', ['styles']);
});

Конструкция этой задачи немного отличается от наших предыдущих задач. Обратите внимание на дополнительный массив, переданный в .task() в качестве второго параметра - думайте об этом как о списке задач, которые мы будем использовать в нашей задаче default. Внутри обратного вызова задачи gulp.watch() принимает два параметра: путь к файлам, которые мы хотим просмотреть, и массив задач, которые будут запускаться при изменении этих файлов (у нас есть только одна задача, наша styles задача).

Задачи Gulp default могут быть выполнены только с gulp - нам не нужно указывать имя задачи. Запуск этой задачи запускает наблюдателя - он будет выполнен, но не завершится. Он продолжает работать, наблюдая за дополнительными изменениями в наших файлах:

Так что давайте что-нибудь изменим. Текст нашей цитаты в настоящее время становится красным (color: #F33) при наведении - давайте вместо этого попробуем голубовато-лавандовый. Внесите следующие изменения в /src/css/quote.scss:

#quote > a:hover {
    color: #AAF;
}

Следите за окном своего терминала и сохраните файл - Gulp автоматически повторно запустит нашу styles задачу в ответ на событие сохранения. Поскольку эта задача генерирует новый /dist/css/quote-app.css, вы можете перезагрузить окно браузера, чтобы увидеть изменения:

Красиво, действительно. Теперь мы можем генерировать готовый к работе CSS автоматически и без прерывания рабочего процесса.

Подводя итоги

В конце части 2 у нас было прекрасно функциональное модульное приложение. Мы могли бы оставить все как есть; в конце концов, мы достигли наших проектных целей. Но в процессе мы представили возможность снижения производительности.

Предвидя эти и другие проблемы во введении, мы использовали Gulp для создания файлов, готовых к производству, при подготовке к развертыванию. Мы добавили селекторы с префиксом производителя в наш CSS, перенесли наши SASS и es6 в CSS и es5 JavaScript, а также уменьшили наши скрипты для повышения производительности. Наконец, мы добавили наблюдателя для автоматизации обработки CSS в ответ на изменения в наших исходных таблицах стилей.

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

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

Заключение

На этом мы завершаем серию статей о написании модульного JavaScript. Он описывает метод планирования и создания модульных приложений, который легко понять и реализовать и не требует экзотического синтаксиса, библиотек или фреймворков. Это делает его подходящим для начинающих программистов на JavaScript.

Вы можете спросить: «Почему не использовать фреймворк?» Это справедливый вопрос - например, Angular имеет модульную конструкцию. Я ничего не имею против фреймворков (и люблю Angular), но время дорого. Модульная структура дизайна, описанная в этой серии статей, может быть использована кем угодно сразу - это не требует, чтобы вы сначала потратили время на изучение синтаксиса и методов фреймворка.

И, наконец, модульный дизайн - это больше процесс. Фреймворк или отсутствие фреймворка, приложения, которые вы создаете, выиграют от прочной основы модульного мышления. Надеюсь, вы сможете воплотить эти идеи в своих собственных проектах.