Сборка бандла AngularJs приложения с Gulp
- вторник, 1 августа 2017 г. в 03:14:44
День добрый.
Расскажу про отличный gulp плагин для сборки вашего веб приложения в один большой ассет, который может путешествовать отдельно на любой хост в удобном виде, а также если вы используете непрерывное развертывание, то можно собирать ассеты прямо в Jenkins например.
Итак сначала начнем с перечисления всех необходимых плагинов:
var gulp = require('gulp'),
clean = require('gulp-clean'), //для чистки папки dist
inject = require('gulp-inject'), //этим мы будем инжектить исходники проекта поотдельности для рабаты в dev режиме
bundle = require('gulp-bundle-assets'), //тот самый очень полезный плагин
config = require('load-gulp-config'), //для загрузки json конфигураций на выходе из предыдущего плагина
htmlreplace = require('gulp-html-replace'); //этим мы будем вставлять считанныйе из json бандлы в наш html
bundle-assets — отличный плагин т.к он позволяет создать отделную dist директорию со всеми шрифтами, картинками, стилями и т.д, которые необходимы для работы приложения. Пример использования плагина можно посмотреть по ссылке.
Коротко говоря на выходе мы получаем вот такой bundle.result.json, все исходини уже минимизированы и могут располагатся где вам угодно:
{
"main": {
"styles": "<link href='main-8e6d79da08.css' media='screen' rel='stylesheet' type='text/css'/>",
"scripts": "<script src='main-5f17cd21a6.js' type='text/javascript'></script>"
},
"vendor": {
"scripts": "<script src='vendor-d66b96f539.js' type='text/javascript'></script>",
"styles": "<link href='vendor-23d5c9c6d1.css' media='screen' rel='stylesheet' type='text/css'/>"
}
}
Все что вам нужно это создать bundle.config файл который будет считан плагином. Вот как выглядит конфиг в нашем проекте:
module.exports = {
bundle: {
main: {
scripts: [
'js/app.js', // точка входа AngularJs приложения
'js/investigator/**/*.js' // все файлы проекта
],
styles: ['css/investigator/**/*.css'] // все стили проекта
},
vendor: {
scripts: [
'js/plugin/jquery-touch/jquery.ui.touch-punch.min.js',
... // огромная куча скриптов других разработчиков
'js/plugin/filesaver/FileSaver.js'
],
styles: [
'css/**/*.css'
]
}
},
copy: [
'img/**/*.{png,svg,ico,jpeg,jpg,gif}',
'manifest.json',
'includes/**/*.html',
'fonts/**/*.{woff,svg,ttf,eot}',
'i18n/**/*.json',
..., // статические конфиги картинки шрифиы index.html и т.д
'js/investigator/countries/countries.json'
]
};
И таск build-bundles, который соберет это все в 'дистрибутив'
gulp.task('clean', function() {
return gulp.src('dist')
.pipe(clean({force: true}))
.pipe(gulp.dest(''));
});
gulp.task('build-bundles', ['clean'], function () {
return gulp.src('bundle.config.js')
.pipe(bundle())
.pipe(bundle.results('./'))
.pipe(gulp.dest('./dist'));
});
И после запуска таска получаем на выходе, вот такой отличный билд, который можно отправить на ваш хост:
Но! В случае разработки с клиентским фрейморком (AngularJs в моем случае), этот плагин не дает возможности внедрять выходные файлы в ваши html.
Вот здесь описан сбособ внедрения ссылок на выходные исходники на стороне сервера с помощью Hogan (hjs).
Потому опишу способ внедрения этих исходников тем же Gulp и с помощью gulp-html-replace. Коротко говоря, мы должны считать наш bundle.result.json описаный выше, и сказать html-replace, что нужно внедрять и куда. Знать 'куда?' плагин будет после того как мы добавим нужную разметку в наш index.html.
Сначала таск:
gulp.task('build-release', ['build-bundles'], function () {
var bundleResult = config.util.readJSON('bundle.result.json'); //считаваем результат от bundle-assets
gulp.src(['index.html'])
.pipe(htmlreplace({
js: {
src: null, //оставляем null т.к на выходе из bundle-assets мы получаем link тег целиком
tpl: bundleResult.main.scripts //в качестве шаблона указывает готовый тег с нужной ссылкой
},
jsvendor: {
src: null,
tpl: bundleResult.vendor.scripts
},
css: {
src: null,
tpl: bundleResult.main.styles
},
cssvendor: {
src: null,
tpl: bundleResult.vendor.styles
}
}))
.pipe(gulp.dest('dist/'));
});
и вот один из инжектов в index.html
<!-- build:js -->
<script src="js/app.js"></script>
<!-- inject:js -->
<script src="/js/investigator/common.js"></script>
<script src="/js/investigator/apiVersion/apiVersionController.js"></script>
<script src="/js/investigator/apiVersion/apiVersionService.js"></script>
//... many many project controllers, directives etc
<!-- endinject -->
<!-- endbuild -->
Как видите здесь используется тег-комментарий build:js в Gulp таске у нас указан соответствующий объект. По аналогии у вас должны быть теги под vendor и под сss: , и .
На место этих тегов и будут внедрены файлы предварительно собранные в bundle-assets.
Также можете обратить внимание, что внутри тега есть тег . Я использую его, что бы внедрять все контроллеры, директивы и все остальные собственные файлы проекта по отдельности:
gulp.task('inject-separately', function () {
var targets = gulp.src(['index.html'], { base: '' });
var sources = gulp.src(['js/investigator/**/*.js', 'css/investigator/**/*.css'], { read: false });
return targets.pipe(inject(sources))
.pipe(gulp.dest(''));
});
Преимущество этого подхода в том, что здесь не нужно использовать watch. Лично меня напрягает watch. Я часто люблю сохранять файл после того как закончу писать какую то мысль, а исходников в проекте очень много и иногда, замечаю задержку при сборке билда и к тому же в любом случае приходится совсем немного, но все таки ждать.
На многих проектах я встречал подход при котором на prod сервере, была сборка минифицированых бандлов, а в при dev разработке все сливалось в один файл но без минификации. В любом случае сборка идет и небольшая задержка есть, и к тому же, при таком подходе я получу ошибку типа:
```NullRefException at main.js.1234:12```
а внедряя файлы поотдельности изменения тут же подхватываются браузером и я получаю точное название файла и номер строки текста программы, где произошло исключение.
Единственное, что при создании нового файла в проекте, нужно запустить inject-separately еще раз.