javascript

Сборка бандла AngularJs приложения с Gulp

  • вторник, 1 августа 2017 г. в 03:14:44
https://habrahabr.ru/post/334488/
  • JavaScript


День добрый.


Расскажу про отличный 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'));
});

И после запуска таска получаем на выходе, вот такой отличный билд, который можно отправить на ваш хост:
image


Но! В случае разработки с клиентским фрейморком (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 еще раз.