Запуск проекта AngularJS также означает выбор всего набора инструментов, который идет вместе с Angular. В этом посте мы предложим набор инструментов, которые могут понадобиться новому проекту Angular, и представим простую сборку Gulp, которая предоставляет эти функции.
Давайте рассмотрим следующие темы:
- Почему глоток? Цели для базового углового построения
- Зачем использовать препроцессор CSS и почему Sass
- Преимущества модульной системы и почему браузеризация
- Предварительно заполнить угловой шаблонный кеш
- Угловая минимизация JavaScript
- Сокращение количества HTTP-запросов с использованием спрайтов
- Настройте веб-сервер разработки
- Запуск тестов с Karma, качество кода с JSHint и многое другое
Почему стоит выбрать Gulp?
Хотя есть и другие исполнители задач, кажется, что Gulp становится все более и более предпочтительным решением, поскольку он предоставляет следующие функции:
- Это супер быстро, так как он построен для параллелизма с самого начала
- Он предоставляет простой в освоении API (основанный на архитектуре каналов и фильтров), который создает некоторый очень читаемый и обслуживаемый код.
Цели хорошей угловой сборки
В идеале сборка для проекта Angular должна:
- Быть низкой сложности, в идеале не более 100 строк кода
- Быть молниеносным, порядка нескольких секунд
- Не подразумевать никаких зависимостей от неузловых цепочек инструментов
- Будьте абсолютно одинаковыми как в разработке, так и в производстве
- Решить некоторые специфичные для Angular проблемы: англоязычная минимизация Javascript и предварительное заполнение кэша шаблонов
- Обеспечить режим разработки для производительности разработчиков
- Разрешить запускать тесты и проверять качество кода
- Минимизируйте количество HTTP-запросов, необходимых для производства, с помощью CSS и Javascript, а также спрайтов изображений
Рабочая сборка и пример приложения
Чтобы сделать его более конкретным, приведем сборку и применили ее к примеру приложения: приложение TODO из проекта TODO MVC . Вы можете попробовать пример приложения здесь .
Сборка и пример приложения также доступны в этом репозитории Github .
В следующих следующих разделах мы рассмотрим предлагаемый набор инструментов для проектов Angular и пройдемся по сборке Gulp, которая предоставляет эти функции.
Зачем использовать препроцессор CSS и почему Sass
Чтобы процитировать ежегодный радар Технологии Хотя в прошлом году:
Мы считаем, что времена рукописного CSS, для чего угодно, кроме обычной работы, прошли.
Ограничений простого CSS много, и это затрудняет написание поддерживаемых таблиц стилей. Хотя новые стандарты CSS3 устраняют многие из ограничений, широкое внедрение требует времени.
CSS расширенные возможности
Для создания поддерживаемого CSS нам действительно нужно:
- Механизм связывания CSS
- Механизм частичных операций, который позволяет разбивать CSS на логические порции, не генерируя дополнительные http-запросы.
- Возможность определения CSS-переменных ограниченной области
- Возможность встраивания связанных стилей в древовидную структуру
- Возможность определять CSS-функции
Все эти функции обеспечиваются множеством CSS-препроцессоров, доступных сегодня, с трудом, как выбрать один. Двумя основными из них являются Sass и Less .
Многие библиотеки построены поверх этих препроцессоров, которые инкапсулируют часто используемые шаблоны, см., Например, библиотеку Sass Compass .
Sass или Меньше?
Предпроцессор Less может показаться лучшим выбором для сборки на основе узлов, так как это также инструмент на основе узлов.
Но сегодня Sass больше не привязан к инструментальной цепочке Ruby, так как есть реализация Sass на Си ( libsass ), которая позволяет быстро компилировать Sass в среде узла с помощью плагинов, таких как gulp-sass .
Кроме того, индустрия, кажется, сходится вокруг Sass: посмотрите, например, этот пост на css-tricks . В конце концов, три основные причины, по которым я решил выбрать Sass:
- хорошая интеграция с узлом через libsass, нет необходимости устанавливать Ruby
- зная, что наиболее используемая библиотека Sass Compass портируется на libsass для Compass 2, посмотрите эту проблему на Github
- тот факт, что Angular Material Design основан на Sass , поэтому мы, вероятно, найдем его снова в будущем
Задача Build-css
Sass интегрирован в сборку gulp с помощью следующей build-css
задачи:
gulp.task('build-css', ['clean'], function() {
return gulp.src('./styles/*')
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(cachebust.resources())
.pipe(sourcemaps.write('./maps'))
.pipe(gulp.dest('./dist'));
});
Существует поддержка исходных карт, поэтому можно будет отлаживать CSS в браузере и обращаться к исходным источникам Sass. Подробнее о функции очистки кэша позже.
Зачем использовать модульную систему
Существует несколько причин, по которым модульная система, вероятно, необходима для любого нетривиального проекта Javascript:
- Добавление всех зависимостей Javascript в теги сценария в нижней части нескольких html-страниц быстро разваливается по мере того, как кодовая база и команда увеличиваются
- А как насчет объединения и минификации? повторение содержимого тегов скрипта в задачах конкатенации на уровне сборки быстро становится неуправляемым
Помимо решения этих распространенных проблем из коробки, использование модульной системы способствует:
- Разработка хорошо изолированных небольших модулей с четко определенными границами и зависимостями.
- Структурирование кода на небольшие куски, которые сами по себе легко понять
- Упростите и лучше включите тестирование
Но отрасль очень разрознена в том, что касается модульных систем: требуются только некоторые из них: модули JS , CommonJS , browserify , ES6 . Так какой же выбрать?
Почему CommonJS привлекательна
CommonJS — это та же модульная система, которая используется узлом в целом. Это очень просто понять и использовать. Если нам нужен модуль, скажем, например, экспресс , мы просто говорим:
var express = require('express');
Импорт модуля является синхронным и очень интуитивно понятным. Основным преимуществом этого синтаксиса является то, что нет необходимости определять файл конфигурации, в котором объявлены все зависимости: нам нужно только указать на файл ввода, и сами require
вызовы неявно задокументируют дерево зависимостей файлов Javascript.
CommonJS раньше был модульной системой только для бэкэнда, пока не появился browserify , что позволило нам использовать тот же знакомый синтаксис и на внешнем интерфейсе.
Angular теперь официально поддерживает Browserify
Недавно в выпуске Angular 1.3.14 Instant-browserification команда Angular добавила улучшенную поддержку browserify.
А именно, он опубликовал в npm все файлы Angular как модули CommonJS, предоставив им значимый экспорт, который позволяет упростить использование браузера.
Несмотря на то, что модули ES6 будут предпочтительным решением для Angular 2, кажется, что browserify является рекомендуемым решением для проектов Angular 1.
Задание Build-js Gulp
Возвращаясь к нашей сборке, вот задача, которая собирает пакет Javascript нашего приложения:
gulp.task('build-js', ['clean'], function() {
var b = browserify({
entries: './js/app.js',
debug: true,
paths: ['./js/controllers', './js/services', './js/directives'],
transform: [ngAnnotate]
});
return b.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(cachebust.resources())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(uglify())
.on('error', gutil.log)
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./dist/js/'));
});
Почему бы не использовать плагин Gulp-Browserify
Обратите внимание, что browserify используется напрямую, а не через какой-либо плагин, такой как gulp-browserify . Это связано с тем, что команда gulp внесла в черный список несколько плагинов, таких как gulp-browserify, поскольку они предназначены для отдельных процессов сборки, которые трудно интегрировать с другими плагинами gulp.
Вместо этого команда gulp предоставила рецепт того, как использовать browserify: мы просто вызываем его напрямую и направляем его вывод, который включает сцепленные js-файлы, обернутые в модули, и передаем его другим задачам gulp.
Рецепт Gulp был адаптирован для решения одной специфической для Angular проблемы: поддержка минификации.
Javascript Minification
Известно, что Angular необходим дополнительный шаг сборки перед минификацией для поддержки механизма внедрения зависимостей. Возьмем для примера эту строку кода:
angular.factory('todoStorage', function ($http, ) { ...
Если мы не предпримем никаких превентивных мер, это будет сведено к следующему:
a.factory('todoStorage', function (h, ) { ...
Проблема в том, что имя переменной $http
потеряно, поэтому Angular больше не может внедрить его, найдя $httpProvider
идентифицируемый объект путем объединения имени переменной $http
с Provider
. Это приводит к разрыву приложения при минимизации.
Перед минификацией вышеприведенную строку необходимо переписать в альтернативный синтаксис массива, который сохраняет имена строк, но более подробный:
angular.factory('todoStorage', ['$http', function ($http, ) { ... } ]
Исправление угловой проблемы минификации
Один из способов исправить это — использовать везде синтаксис массива, так как он по-прежнему читабелен и позволяет избежать дополнительного шага сборки.
Кроме того, преобразование browserify browserify-annotate
было включено в build-js
задачу выше, чтобы решить эту проблему:
var ngAnnotate = require('browserify-ngannotate');
Обратите внимание, что это преобразование применяет синтаксис массива почти во всех местах, но мне все равно пришлось применять его вручную для resolve
шагов определения маршрута , см. Файл app.js
.
Предварительное заполнение кеша угловых шаблонов
Еще одна специфичная для Angular проблема, которую необходимо решить при сборке, — это предварительное заполнение кэша шаблонов Angular.
Если мы этого не сделаем, то каждый шаблон каждой директивы приведет к отдельному HTTP-запросу, что приведет к недопустимо медленному запуску приложения, так как приложению необходимо дождаться загрузки шаблонов перед отображением страницы.
Сокращение количества HTTP-запросов с использованием спрайтов
Многие из оптимизаций, которые обеспечивает сборка, вращаются вокруг сокращения количества HTTP-запросов, необходимых для начальной загрузки приложения. А как насчет изображений, например, иконок?
Один из способов уменьшить количество HTTP-запросов, связанных с изображениями, состоит в том, чтобы сгруппировать их вместе в спрайте изображений и загрузить только это. В следующем разделе сборки используется gulp.spritesmith
плагин для генерации спрайтов:
gulp.task('sprite', function () {
var spriteData = gulp.src('./images/*.png')
.pipe(spritesmith({
imgName: 'todo-sprite.png',
cssName: '_todo-sprite.scss',
algorithm: 'top-down',
padding: 5
}));
spriteData.css.pipe(gulp.dest('./dist'));
spriteData.img.pipe(gulp.dest('./dist'))
});
Все png
изображения в images
папке преобразуются в один todo-sprite.png
файл. Чтобы иметь возможность использовать спрайты, в файле генерируется миксин Sass _todo-sprite.scss
. Миксин можно использовать следующим образом:
@include sprites($spritesheet-sprites);
Это создаст набор CSS-классов с такими же именами, что и файлы изображений, которые позволяют использовать разные изображения. Например, изображение cal-right-button.jpg
может быть использовано следующим образом:
<span class="cal-right-button"></span>
Зачем использовать Cache Busting?
Удобная практика, которая пригодится как при разработке, так и при производстве, — это предотвращение сохранения в браузере устаревших копий
CSS / Javascript приложения.
Это не позволяет клиентам просматривать устаревшую версию приложения, которая больше не соответствует HTML, и позволяет избежать отчетов об ошибках, которые иногда трудно связать с наличием устаревших ресурсов.
Самый эффективный способ добиться очистки кэша — добавить к каждому файлу CSS / Javascript хэш его содержимого. Таким образом, когда файл изменяется, имя файла также изменяется, и браузер загружает последнюю версию ресурса.
Реализация очистки кеша с помощью Gulp-Cachebust
Плагин gulp-cachebust был использован для реализации функции очистки кэша. Мы видели в вызовах build-css
и build-js
задачах такой вызов:
.pipe(cachebust.resources())
Этот вызов отслеживает файлы CSS и Javascript, имена которых должны быть объединены с хэшем файла. Фактическая замена имен файлов CSS выполняется в задаче сборки, вызывая cachebust.references()
:
gulp.task('build', [ 'clean', 'bower','build-css','build-template-cache', 'jshint', 'build-js'], function() {
return gulp.src('index.html')
.pipe(cachebust.references())
.pipe(gulp.dest('dist'));
});
Запуск испытаний с кармой
Одним из де-факто Угловых инструментов тестирования является Kner Runner, а одной из наиболее часто используемых сред тестирования является Jasmine . Следующая задача Gulp позволяет запускать тесты Jasmine из командной строки на безголовом браузере PhantomJS:
gulp.task('test', ['build-js'], function() {
var testFiles = [
'./test/unit/*.js'
];
return gulp.src(testFiles)
.pipe(karma({
configFile: 'karma.conf.js',
action: 'run'
}))
.on('error', function(err) {
console.log('karma tests failed: ' + err);
throw err;
});
});
Следующая команда запустит все тесты в наборе тестов:
gulp test
Качество кода с помощью JSHint
JSHint является одним из наиболее часто используемых Javascript-линтеров. Следующая задача gulp позволяет интегрировать ее в наш цикл сборки:
gulp.task('jshint', function() {
gulp.src('/js/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
Веб-сервер разработки
После выполнения основной сборки и запуска всех тестов с использованием gulp
задачи по умолчанию полезно иметь возможность запуска локального сервера разработки.
gulp-webserver
Плагин был настроен сделать именно это следующим образом:
gulp.task('webserver', ['watch','build'], function() {
gulp.src('.')
.pipe(webserver({
livereload: false,
directoryListing: true,
open: "<a href="http://localhost:8000/dist/index.html">http://localhost:8000/dist/index.html</a>"
}));
});
Выводы
Благодаря средству выполнения задач Gulp и его богатой экосистеме плагинов можно создавать базовую сборку Angular с наиболее часто используемыми функциями, которые, вероятно, понадобятся проекту Angular, и при этом сохранять сложность сборки сравнительно низкой.
Дайте нам знать ваши мысли о комментариях ниже, как выглядит ваша сборка Angular?