Пример конфигурации Gulp.js

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

Конфиг рассчитан на использование SCSS (или SASS) и Vue.js. Однако, если вы хотите просто упаковать скрипты и стили, он тоже должен сработать.

TLDR

Итак, в корне проекта создаем файл gulpfile.js:

/**
 * Example of gulp configuration
 * @link https://github.com/13DaGGeR/gulpfile-example
 * @version 0.1.2
 */
const css_syntax = 'scss', // sass|scss
    source_dir = 'src/', // must contain js and (sass or scss) directories
    destination_dir = 'public/'; // will receive result files in js and css subdirectories respectively

const gulp = require('gulp'),
    sass = require('gulp-sass'), // to preprocess scss and sass
    path = require('path'), // to resolve absolute paths
    glob = require('glob'), // to find files by mask
    sourcemaps = require('gulp-sourcemaps'), // to generate .map files
    log = require('fancy-log'), // to show our logs in gulp's format
    webpack = require('webpack'),
    uglifyJsPlugin = require('uglifyjs-webpack-plugin'), // to minify js
    postcss = require('gulp-postcss'), // for most css needs, uses packages below
    autoprefixer = require('autoprefixer'), // for browser compatibility
    atImport = require('postcss-import'), // for @import usage
    cssnano = require('cssnano') // to minify css
;
const {VueLoaderPlugin} = require('vue-loader');

function dev_styles() {
    let processors = [
        atImport,
        autoprefixer({browsers: ['last 15 version']}),
    ];
    return gulp.src(source_dir + css_syntax + '/*.' + css_syntax)
        .pipe(sass({outputStyle: 'expand'}))
        .pipe(postcss(processors))
        .pipe(gulp.dest(destination_dir + 'css'))
}

function prod_styles() {
    let processors = [
        atImport,
        autoprefixer({browsers: ['last 15 version']}),
        cssnano
    ];
    return gulp.src(source_dir + css_syntax + '/*.' + css_syntax)
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'expand'}))
        .pipe(postcss(processors))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest(destination_dir + 'css'))
}

function webpackTask(is_dev) {
    log('webpack started');
    let postCssLoader = {
        loader: 'postcss-loader',
        options: {
            plugins: function () {
                let modules = [
                    autoprefixer({browsers: ['last 15 version']})
                ];
                if (!is_dev) {
                    modules.push(cssnano);
                }
                return modules;
            }
        }
    };
    let config = {
        watch: !!is_dev,
        mode: is_dev ? 'development' : 'production',
        entry: glob.sync(source_dir + '/js/*.js').reduce((acc, cur) => {
            acc[path.basename(cur, '.js')] = path.resolve(cur);
            return acc
        }, {}),
        output: {
            path: path.resolve(destination_dir + 'js'),
            publicPath: '/js/',
            filename: '[name].js'
        },
        module: {
            rules: [
                {test: /\.css$/, use: ['vue-style-loader', 'css-loader', postCssLoader],},
                {test: /\.scss$/, use: ['vue-style-loader', 'css-loader', 'sass-loader', postCssLoader],},
                {test: /\.sass$/, use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax', postCssLoader],},
                {
                    test: /\.vue$/,
                    loader: 'vue-loader',
                    options: {
                        loaders: {
                            'scss': ['vue-style-loader', 'css-loader', 'sass-loader', postCssLoader],
                            'sass': ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax', postCssLoader]
                        }
                    }
                },
                {test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/}
            ]
        },
        resolve: {alias: {vue$: 'vue/dist/vue.min.js'}, extensions: ['*', '.js', '.vue', '.json']},
        plugins: [
            new VueLoaderPlugin()
        ]
    };
    if (!is_dev) {
        config.devtool = 'source-map';
    }
    return webpack(config, (err, stats) => {
        let hasError = !!err;
        if (!err && stats.compilation.errors.length) {
            err = stats.compilation.errors;
            hasError = true
        }
        if (hasError) {
            log.error('ERROR:', err);
        } else {
            log('webpack finished');
        }
    });
}

exports.default = function () {
    webpackTask();
    return prod_styles();
};
exports.dev = function () {
    dev_styles();
    gulp.watch(source_dir + css_syntax + '/**/*.' + css_syntax, dev_styles);
    webpackTask(1);
};

Необходимые пакеты:

yarn add -D gulp gulp-sass gulp-sourcemaps webpack uglifyjs-webpack-plugin gulp-postcss autoprefixer postcss-import cssnano vue-loader vue-template-compiler @babel/core babel-loader sass-loader css-loader postcss-loader vue

Запуск

Текущий конфиг предполагает 2 варианта запуска:

  1. gulp dev -- быстро компилирует при изменениях файлов, нужен для отладки стилей/скриптов
  2. gulp -- минифицирует и создает сорсмапы, отрабатывает один раз и сравнительно медленно. Предполагается запустить один раз перед последним тестированием перед коммитом.

Оба варианта запускаются в консоли в корневой директории проекта, либо используя пресет из IDE (ex. PhpStorm)

Мотивация

Теперь разберем, что используется и зачем.

  1. Зачем вообще обрабатывать стили и скрипты? -- вы когда либо приходили к мысли "о, клевая штука, жаль, поддерживается только современными браузерами", или "почему css каскадный но в исходниках нельзя использовать наследование", или "опять забыл приписать -webkit- и все поехало на %некий_браузер%"? Вот как раз, чтобы избавить себя от таких проблем, разработчики автоматизировали обработку исходников, и обернули все это в относительно удобные инструменты. Нам остается только подключить, использовать и радоваться жизни. Хвала опенсорсу!
    Кроме описанных задач, мы решим задачу минификации и упаковки файлов, что приведет нас к ускорению загрузки страниц.
  2. Что мы получим при использовании этого конфига -- по одному файлу скрипта и стиля на каждую точку входа.
  3. Что понимается под точкой входа -- файл scss или js, содержащий import-ы необходимых нам библиотек и созданных нами файлов.
  4. gulp.js -- это потоковая система сборки. Потоковая означает, что обрабатываемые ресурсы рассматриваются как поток данных, а-ля пайпы в linux. Мы указываем источник(-и), набор преобразований, и результирующий файл(-ы).
  5. scss / sass -- "Syntactically awesome style sheets", эдакие css на стероидах. Кратко: позволяет использовать принципы хорошего кода в css. Нужны переменные -- есть, инкапсуляция -- в разумных пределах. Субъективно всегда предпочитал синтаксис другого предпроцессора: less, но т.к. плотно использую twitter bootstrap, особого смысла в зоопарке не увидел и перешел на scss.
  6. webpack -- упаковщик js. Изначально создан для того, чтобы сделать из кучи файлов js один большой. Из коробки поддерживает sourcemap-ы, используем uglifyjs-webpack-plugin для минификации и babel-loader для транслитерации современного js-а в js твоего дедушки (в исходниках используем стрелочные функции, но код работает в ie7).
  7. postcss -- фреймворк для работы с css. В нашем случае использует autoprefixer (для, очевидно, автоматической простановки всяких там -moz- и -webkit-), postcss-import для упаковки множества файлов в один и cssnano для минификации css.
  8. Почему не делать все на модулях gulp -- на момент написания конфига модули gulp-а постигла участь npm -- сотни дублирующих пакетов, часто заменяющих друг друга, часто несовместимых друг с другом. webpack я подключил, когда не нашел адекватного решения упаковки файлов, при условии использования нативного синтаксиса подключения файлов в js. post-css -- потому что на данный момент нельзя с помощью gulp одновременно упаковать файлы и сгенерировать сорсмапы. Вполне возможно, что все таки можно, но тратить время на танцы с бубном мне не хочется.
  9. Почему не делать все через webpack -- webpack не предполагает схему работы вообще без js. Предложенный конфиг можно использовать для подготовки стилей как классического backend-heavy приложения так и для SPA на vue.js.
  10. Почему не используется пакет webpack для gulp -- при такой схеме webpack будет запускаться после каждого обновления скриптов, это долго. В предложенной мной схеме webpack запускается единожды при запуске gulp и сам следит за изменениями файлов. Так зачем же тут gulp -- чтобы не плодить много конфигов в корне проекта.
updated 2019-05-31 13dagger