EN
id247
id247
13 июля 2016

Всем привет. Решил что пора пустить БЭМ в свое сердце чуточку глубже. Давно пользуюсь неймингом для css, хотелось бы теперь и разметку делать при помощи БЭМ и возможно использовать bem-components (но это пока не главное).

В общем, закопался в доках, гитхабе и просмотрел кучу докладов, в итоге в голове каша. Надеюсь на ваш совет.

Суть - проекты собираются при помощи gulp. Html в них собирается используя gulp-file-include простейшими конструкциями вида:

@@include("../common/head.html", {
"title": "Заголовок этой страницы"
})

И на выходе несколько статичных страничек, немного отличающихся друг от друга. Ну как обычно в общем.

И все это меня вполне устраивает, я не хочу переходить пока полностью на бэм-стек, а начать с чего-то проще. Собственно, хотелось бы описывать шаблоны через bemjson, bemhtml (bem-xjst?). Чтобы на выходе получались статичные html страницы. Без привязки к js, без i-bem, просто статика html.

Но я не увидел примера как это можно было бы завести на галпе. Чтобы не надо было полностью переделывать всю сборку, а просто заменить таску по сборке html, оставив все остальное на местах. Пните в нужном направлении, пожалуйста.

id247
#id247
13 июля 2016

Посомотрел БЭМинары, очень похоже на то что мне надо вот здесь - https://ru.bem.info/forum/817/ жалко что видео не сохранилось, думаю там было более подробное описание.

Репозиторий с этого БЭМинара очень блихок к тому что мне надо https://github.com/bem-events/beminar-4 едиснтвенное что я не осилил пока, это как генерировать несколько страниц сразу, а не одну index.potter.bemhtml.js

То есть чтобы галп обходил например папку в которой лежат index.bemhtml.js + index.bemjson.js, about.bemhtml.js + about.bemjson.js, contacts.bemhtml.js + contacts.bemjson.js

И на выход отдавал соотвественно index.html, about.html, contacts.html.

Или я вообще ерундой какой-то занимаюсь и БЭМ не про то? :-\

vithar
#vithar
13 июля 2016

Лучше не писать bemjson руками, а генерировать его из bemtree.

Посмотрите этот вебинар:

zxqfox
#zxqfox
13 июля 2016

Чтобы друг друга не путать, хочу обратить внимание на демку bem-xjst: http://bem.github.io/bem-xjst/ куда можно закидывать bemhtml и bemjson.
Слева вы можете положить bemjson, справа bemhtml, и снизу будет результат в виде html.

Еще есть такая демка с шаблонами из bem-components, шаблоны для page удобно использовать, чтобы не писать свои для стандартной обвязки doctype-html-head-body
https://jsfiddle.net/qfox/30oejvdw/ — шаблоны и bemjson лежат в секции js, но можно сразу потыкать в компоненты, если отключить prettify html.

То есть чтобы галп обходил например папку в которой лежат index.bemhtml.js + index.bemjson.js, about.bemhtml.js + about.bemjson.js, contacts.bemhtml.js + contacts.bemjson.js

Чтобы это сделать вам не нужно иметь кучу разных bemhtml шаблонов для каждой страницы, достаточно одного общего bemhtml со всеми возможными шаблонами блоками и по одному bemjson файлу для страницы, чтобы сгенерировать на базе этого bemjson скрипты, стили и html (либо только html, если остальное не нужно).

Технически bemhtml файлы будут выглядеть примерно так:

// Подключение BEMHTML
BEMHTML.compile(function(){
  // Шаблоны из файлов
});

Сгенерировать такие файлы можно с помощью метода generate модуля bem-xjst: https://github.com/bem/bem-xjst/#generatestring-or-function
Или с помощью gulp-bem-xjst модуля: https://github.com/gulp-bem/gulp-bem-xjst

const concat = require('gulp-concat');
const bemhtml = require('gulp-bem-xjst').bemhtml;

gulp.task('bemhtml', function() {
  return gulp.src('blocks/**.bemhtml.js') // Собираем все шаблоны без учета порядка
    .pipe(concat('all.bemhtml.js')) // Склеиваем в один файл
    .pipe(bemhtml()) // Компилируем и добавляем ядро
    .pipe(gulp.dest('dist')); // Сохраняем в dist
});

И на выход отдавал соотвественно index.html, about.html, contacts.html.

После чего из всех bemjson (index.bemjson.js, about.bemjson.js) файлов можно сделать html:

const path = require('path');
const thru = require('through2');
const _eval = require('node-eval');

gulp.task('pages', ['bemhtml'], function() {
  const BEMHTML = require('./dist/all.bemhtml.js');
  return gulp.src('pages/*.bemjson.js') // Собираем все bemjson файлы страниц
    .pipe(thru.obj(function(file, _, cb) {
      // Применяем шаблоны и переименовываем в html
      try {
        file.content = new Buffer(BEMHTML.apply(_eval(file.data)));
      } catch (e) {
        // Сохраняем ошибку, чтобы проще было отлаживать
        console.error(e);
        file.content = new Buffer(String(e.stack));
      }
      file.path = path.join(path.dirname(file.path), path.basename(file.path).split('.')[0] + '.html');
      this.push(file);
      cb(file);
    }))
    .pipe(gulp.dest('dist')); // Сохраняем в dist
});

Или я вообще ерундой какой-то занимаюсь и БЭМ не про то? :-\

По ерунде — никто никогда не даст внятного ответа чем кому бы то ни было нужно заниматься в конкретный момент времени. Даже не пытайся задавать этот вопрос)

UPD: Кстати, 28-го числа будет БЭМинар как раз на тему сборки на свежевыпеченных инструментах: https://events.yandex.ru/events/bemup/28-july-2016/

id247
#id247
14 июля 2016

Спасибо огромное за советы.

Демку bem-xjst я конечно же видел, БЭМинар тоже жду.

Запилил сборку по советам, получилось так - https://github.com/id247/bemhtml/

Страницы сайта в итоге лежат в
desktop.bundles/index/index.bemjson.js
desktop.bundles/about/about.bemjson.js
....

Но меня естественно не устраивает, то что страницы в очень многом дублируют друг друга.

Например блок меню есть и там и там, и отличается незначительно.

            {
                block: 'menu',
                content: [
                    {   
                        url: 'index.html', 
                        content: 'О методологии' 
                    },
                    { 
                        url: false, 
                        content: 'Узнать больше' 
                    },
                    { 
                        url: 'http://bem.info', 
                        attrs: { 
                            target: '_blank' 
                        }, 
                        content: 'bem.info' 
                    }
                ]
            }




            {
                block: 'menu',
                content: [
                    {   
                        url: false, 
                        content: 'О методологии' 
                    },
                    { 
                        url: 'about.html', 
                        content: 'Узнать больше' 
                    },
                    { 
                        url: 'http://bem.info', 
                        attrs: { 
                            target: '_blank' 
                        }, 
                        content: 'bem.info' 
                    }
                ]
            }

Я так понимаю что для решения этой проблемы как раз нужен bemtree? Чтобы брать сырые данные ( например объект с ссылками для меню) и на их основе генерировать уже bemhtml для конкретных страниц.

zxqfox
#zxqfox
14 июля 2016

@id247 Можно использовать bemtree, тогда в сборке надо будет кое-что доделать, а можно js функции, т.к. bemjson.js можно представить в виде commonjs-модулей.

Например, так:

const partials = require('./bemjson-partials.js');

module.exports = {
  block: 'page',
  content: [
    partials.menu(),
    {
      block: 'content'
    },
    partial.footer()
  ]
};

И т.д. ;-)

zxqfox
#zxqfox
14 июля 2016

Если что, вариант с BEMTREE, конечно, более элегантен.
BEMTREE-файл надо собирать так же, как и BEMHTML, отдельным зависимым шагом/таском, и после просто так же загружать и применять перед BEMHTML require('./all.bemhtml.js').apply(require('./all.bemtree.js').apply(file.data)).

id247
#id247
14 июля 2016

Ну файлы реквайрить это не спортивно )))

В итоге вот такой bemtree для менюшки на коленке набросал

var data = [
    {   
        url: 'index.html', 
        content: 'О методологии' 
    },
    { 
        url: 'about.html', 
        content: 'Узнать больше' 
    },
    { 
        url: 'http://bem.info', 
        attrs: { 
            target: '_blank' 
        }, 
        content: 'bem.info' 
    }    
];

block('menu').content()(function() {
    var ctx = this.ctx;
    var active = ctx.active;
    return data.map(function(link){
        if (link.url.indexOf(active) === -1){
            return link;
        }
        return Object.assign({}, link, {url: false});
    });
});

Осталось теперь вынести данные во вне и пробрасывать их на этапе сборки во все bemtree, я так понимаю.

id247
#id247
14 июля 2016

Не, что-то я видимо упустил. Или под вечер уже голова не варит.

Вот есть две точки входа
desktop.bundles/index/
desktop.bundles/about/

Из которых надо получить 2 разные страницы соотвественно. На каждой странице подсветить активный пункт меню.

Есть общие данные для менюшки, например такие:

[
    {   
        url: 'index.html', 
        content: 'Главная' 
    },
    { 
        url: 'about.html', 
        content: 'О компании' 
    }
]

Как это подружить при помощи bemtree?

tadatuta
#tadatuta
14 июля 2016

Попробую ответить не на конкретный вопрос, а в целом. Если после ответа останутся какие-то частные вопросы — сможем углубиться в детали.

Есть два подхода, которые можно представить как map (один исходный бандл создает один HTML на выходе) и reduce (один бандл может порождать несколько HTML-страниц. Этот вариант можно рассматривать как data-driven подход, когда за количество сгенеренных страниц будут отвечать только данные, подаваемые на вход).

Первый вариант реализован в https://github.com/tadatuta/bem-bemtree-static-project-stub + см. ридми (https://github.com/tadatuta/bem-bemtree-static-project-stub/blob/master/README.ru.md).

Второй — в статике в https://github.com/tadatuta-studio/tam-maslov-ru/blob/master/generate.js или в динамике в https://github.com/tadatuta/bem-express.

Я в целом считаю, что второй вариант более удобный: собрать вообще все возможные шаблоны в одну кучу, а потом написать скрипт, который будет обходить модель (по сути — массив JSON-объектов), описывающую страницы, вызывать шаблонизацию с каждый айтемом модели и результат записывать в файл.

id247
#id247
15 июля 2016

Благодарю за советы. В общем и целом что-то начало прорисовываться.

Пока получилось вот так https://github.com/id247/bemtree

Собираю все bemthml блоков в один all.bemhtml.js, исходные данные положил в файлик /data/data.js

Для каждой страницы создаю отдельный .template.js и на их основе генерирую .bemtree.js и далее уже html-ки

index.template.js:

const vars = {
    meta: {
        title: 'Главная',
    },
    menu: {
        active: 'index'
    },
}

block('root').replace()(function() {
    this.data = this.ctx.data;
    return {
        block: 'page',
        title: vars.meta.title,
        styles: [
            'index.min.css'
        ],
        scripts: [
            'index.min.js'
        ],
    };
});


block('page').content()(function() {
    return {
        block: 'menu',
        active: vars.menu.active,
        content: this.data.links
    };
});

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

Мне очь нравится БЭМ подход, хотелось бы сделать удобную сборку которую можно будет показать любому верстальщику и сказать - "чувак, хватит тратить время и писать html ручками, смотри сюда"

nicothin
#nicothin
8 июля 2017

Ха, это же почти то, что я искал все эти годы
Немного жаль, что на винде репозиторий не заводится сразу, но, немного пошаманив, таки, завел: https://github.com/nicothin/bemtree
Остается неясным:

  1. почему в задачах после использования «трубы» с bemxjst игнорируется путь, по которому нужно писать файл (вне зависимости от указанного в .pipe(gulp.dest('temp/')), файл пишется в корень проекта), подскажите это я где-то туплю или... https://github.com/nicothin/bemtree/blob/master/gulpfile.js#L13-L18
  2. есть ли инструмент, которому я могу скормить шаблоны страниц и получить список всех блоков, элементов и модификаторов, использованных на проекте?
MacFiss
#MacFiss
9 июля 2017

@nicothin , отвечу на ваши вопросы:
1) Сразу оговорюсь, у меня macos, и по какой-то причине у меня возникла проблема именно такая же. Методом, часового тыка, было определено, что в потоке, перед тем как подать склеенный файл, необходимо было ему задать chmod. Как сделал я:

.pipe(concat({path: 'all.js', stat: { mode: '0777' }}))
.pipe(bemhtml())

2) Предположим что его нету. Напишите сами парсер, который сканит директории с вашим bemjson и собирает содержимое при помощи FS, в глобальный буферный объект, который в последующем чекается циклом на ваши предпочтения.

Вообще в течении месяца планирую выложить свой сборщик, который может работать с двумя шаблонизаторами: 1) bem-xjst 2) pug
Если будете отслеживать, то для меня лишний стимул выложить пораньше :)

nicothin
#nicothin
9 июля 2017

.pipe(concat({path: 'all.js', stat: { mode: '0777' }}))

а для задачи создания bemtree как справились? похожий фокус там не срабатывает.

tadatuta
#tadatuta
9 июля 2017

@nicothin

есть ли инструмент, которому я могу скормить шаблоны страниц и получить список всех блоков, элементов и модификаторов, использованных на проекте?

Подозреваю, что среди bem-sdk найдется что-то, из чего нужный инструмент можно очень быстро собрать, но для этого нужно полностью понимать задачу: нужно знать, что на вход, кроме шаблонов (и точно ли именно в собранных шаблонах нужно искать ответ)?