Войти с помощью github
Форум /

Может кому-нибудь и пригодится. За пару месяцев работы с БЭМом я подметил для себя 3 вида сборки бандлов:

  • когда изменяется непосредственно bemjson
  • когда изменяется bemhtml блока
  • когда изменяются скрипты и стили

Причем только первые два вида могут влиять на конечную структуру html и скрипты и стили. Третий вид изменяет только подключаемые скрипты и стили. Общее здесь - скрипты и стили. Они могут быть модифицированы в любом из вариантов. Из этого следует, что будет как-то нерационально генерить каждый раз html при изменении скриптов или стилей.

А ведь они, в принципе, правятся довольно частенько.

Следовательно нужна такая сборка, которая не делала бы лишних дейтсвий: например, разработчик изменил css, а enb взял бы и пересобрал только css. Еще лучше, если бы эта работа была сопряжена с галповским вотчем.

Сейчас у нас на работе есть подобная сборка, но она не такая гибкая, как хотелось бы. К тому же начальник на днях надоумил меня написать свою. Изначально мне эта идея показалась бредом (зачем писать, когда уже есть и работает), но потом посидев вечер и ночь перед компьютером, у меня стало получаться что-то работоспособное. Потом я стал немного допиливать, допиливать и вот допилил, думаю я. Дим, если читаешь эти строки, знай - я тебе реально благодарен)) Честно!

Так вот, работает эта сборка в 4 режимах в связке с вотчем и устроена слегка костыльно. Сначала о режимах:

full - пересобирает абсолютно все nofull - пересобирает измененные бандлы и все скрипты и стили nofull_css - "по умолчанию" пересобирает только стили nofull_js - "по умолчанию" пересобирает только скрипты

Сама костыльность заключается вот в чем. Для того чтобы узнать изменился бандл или нет, в папке с ним создается файл формата .uptime_[0-9]+ в названии которого находится дата последней модификации bemjson файла. Далее при обходе бандла скрипт смотрит на время модификации bemjson и на этот чудо файл и путем просто сравнения решает, изменялся ли bemjson файл или нет. Ну и далее все ясно: генерить новый html или идти дальше. "По умолчанию" я выделил в кавычки вот почему, эти режимы могут пересобирать измененные бандлы, но генерить они будут помимо html, или css или js. Поэтому их лучше и должно использовать при изменении css или js. Пожалуй, на этом все.

PS. Самое главное) После этой небольшой проделанной работы, хотелось бы выразить огромную благодарность всем участникам форума, которые отвечали на мои бестолковые вопросы ( отдельная благодарность Владимиру Гриненко за его ангельское терпение и постоянный труд по развитию и донесению идеологии БЭМа массам)), а также моему начальнику Диме, который несмотря на свою загруженность, помогал и помогает мне в освоении БЭМа, (и не только БЭМа), и без которого вряд ли бы эта сборка была написана. Спасибо вам, ребята, огромное!

Сначала gulpfile, как этим всем пользоваться

var gulp = require('gulp');
var watch = require('gulp-watch');
var enb = require('enb');
gulp.task('watch', function (cb) {
    watch('desktop.bundles/**/*.bemjson.js', function () {
        enb.make( [], { mode: 'nofull' } );
    });
    watch( '*.blocks/**/*.js', function () {
        enb.make( [], { mode: 'nofull_js' } );
    });
    watch( '*.blocks/**/*.css', function () {
        enb.make( [], { mode: 'nofull_css' } );
    });
    watch( '*.blocks/**/*.bemhtml', function () {
        enb.make( [], { mode: 'full' } );
    });
});

Собственно make.js

var path = require('path');
var fs = require('fs');
var platforms = ['desktop'];
var techs = {
    // essential
    fileProvider: require('enb/techs/file-provider'),

    // optimization
    borschik: require('enb-borschik/techs/borschik'),

    // css
    sass: require('enb-sass/techs/css-sass'),

    // js
    browserJs: require('enb-js/techs/browser-js'),

    // bemtree
    // bemtree: require('enb-bemxjst/techs/bemtree'),

    // bemhtml
    bemhtml: require('enb-bemxjst/techs/bemhtml'),
    bemjsonToHtml: require('enb-bemxjst/techs/bemjson-to-html')
},
enbBemTechs = require('enb-bem-techs'),
levels = [
    'common.blocks',
    'jquery.blocks',
    'desktop.blocks'
];

function getUpdateTime( file ) {
    var mtime = 0;
    try {
        mtime = +new Date(fs.statSync( file ).mtime)/1000;
    } catch(e) {
        return 0;
    } finally {
        return mtime;
    }
}

function createUpdadeFile( path, time ) {
    fs.closeSync(fs.openSync( path+'.uptime_'+time, 'w'));
}

function isUpdatingFile( path, time ) {
    var files = fs.readdirSync( path );
    var up_files = [];
    for( var i = 0; i < files.length; i++ ) {
        var file = files[i];
        if( /^\.uptime_[0-9]+$/.test( file ) ) {
            if( file !== '.uptime_'+time ) {
                fs.unlinkSync( path+file );
            } else {
                up_files.push( file );
            }
        }
    }
    if( up_files.length === 0 ) {
        createUpdadeFile( path, time );
        return true;
    }
    return false;

}

module.exports = function(config, sa) {

    // создаем папки merged в сборках бандлов
    platforms.forEach(function (platform) {
        var node = path.join(platform + '.bundles', 'merged');

        if (!fs.existsSync(node)) {
            fs.mkdirSync(node);
        }
    });

    var isFull = true;
    var noFull = 'all';


    config.mode( 'full', function() {
        isFull = true;
    });

    config.mode( 'nofull', function() {
        isFull = false;
    });
    config.mode( 'nofull_js', function() {
        isFull = false;
        noFull = 'js';
    });
    config.mode( 'nofull_css', function() {
        isFull = false;
        noFull = 'css';
    });



    config.nodes('*.bundles/*', function(nodeConfig) {
        var timeUpdateBem = getUpdateTime( path.dirname(nodeConfig.getPath())+'/'+path.basename(nodeConfig.getPath())+'/'+path.basename(nodeConfig.getPath()) + '.bemjson.js' );

        // если нет bemjson файла - выходим отсюда
        if( timeUpdateBem == 0 ) {
            if( path.basename(nodeConfig.getPath()) !== 'merged' ) {
                console.log( 'error: bundle ' + path.basename(nodeConfig.getPath()) + ' no bemjson-file' );
            }
            return;
        }
        if ( !isFull ) {
            isFull = isUpdatingFile( path.dirname(nodeConfig.getPath())+'/'+path.basename(nodeConfig.getPath())+'/', timeUpdateBem );
        }
        if( !isFull ) {
            nodeConfig.addTechs([
                [enbBemTechs.levels, { levels: levels }],
                [techs.fileProvider, { target: '?.bemjson.js' }],
                [enbBemTechs.bemjsonToBemdecl,  { target: '../merged/?.bemdecl.js' }],
                [enbBemTechs.deps, { bemdeclFile: '../merged/?.bemdecl.js', target: '?.deps.js' }],
            ]);
        } else {
            nodeConfig.addTechs([
                // essential
                [enbBemTechs.levels, { levels: levels }],
                [techs.fileProvider, { target: '?.bemjson.js' }],
                [enbBemTechs.bemjsonToBemdecl,  { target: '../merged/?.bemdecl.js' }],

                [enbBemTechs.deps, { bemdeclFile: '../merged/?.bemdecl.js', target: '?.deps.js' }],
                [enbBemTechs.files],

                // bemhtml
                [techs.bemhtml, { sourceSuffixes: ['bemhtml'], forceBaseTemplates: true, target: '../merged/?.bemhtml.js' }],

                // html
                [techs.bemjsonToHtml, { bemhtmlFile: '../merged/?.bemhtml.js', target: '../merged/?.html' } ],
            ]);
            nodeConfig.addTargets([ '../merged/?.html']);
        }
    });

    config.node('desktop.bundles/merged', function(nodeConfig) {
        var dir = path.dirname(nodeConfig.getPath());
        var bundles = fs.readdirSync(dir);
        var bemdeclFiles = [];
        var depsFiles = [];
        var targetsArr = [];
        var techsArr = [
            [techs.sass, {
                target: 'main.css',
                sourcemap: false,
                autoprefixer: {
                    browsers: ['ie >= 10', 'last 2 versions', 'opera 12.1', '> 2%']
                }
            }],
            [techs.borschik, { source: 'main.css', target: 'main.min.css', tech: 'cleancss', minify: true }],
            [techs.browserJs, { includeYM: false, target: 'main.js' }],
            [techs.borschik, { source: 'main.js', target: 'main.min.js', minify: true }],
        ]
        bundles.forEach(function (bundle) {
            if (bundle === 'merged' || bundle === '.bem') return;
            var node = path.join(dir, bundle);
            var target_bemdecl = bundle + '.bemdecl.js';
            var target_deps = bundle + '.deps.js';
            bemdeclFiles.push( target_bemdecl );
            depsFiles.push( target_deps );
            nodeConfig.addTechs([[enbBemTechs.provideDeps, { node: node, source: target_deps, target: target_deps }],]);
        });
        nodeConfig.addTechs([
            [enbBemTechs.mergeDeps,  { sources: depsFiles }],
            [enbBemTechs.files, { depsFile: '?.deps.js' } ],
            [enbBemTechs.levels, { levels: levels }],
        ]);
        if( noFull === 'css' ) {
            nodeConfig.addTechs([
                techsArr[0],
                techsArr[1],
            ]);
            targetsArr.push( 'main.min.css' );;
        } else if( noFull === 'js' ) {
            nodeConfig.addTechs([
                techsArr[2],
                techsArr[3],
            ]);
            targetsArr.push( 'main.min.js' );;
        } else {
            nodeConfig.addTechs([
                techsArr[0],
                techsArr[1],
                techsArr[2],
                techsArr[3],
            ]);
            targetsArr.push( 'main.min.css' );
            targetsArr.push( 'main.min.js' );
        }
        nodeConfig.addTargets(  targetsArr );
    });


};