@SevInf: По аналогии с
LevelBuilder
: Инстанс конструктора создается вызовоамBEM.defineTech()
. Непосредственно для создания класса используется методcreateClass()
конструктора. Базовый билдер:setCreateSuffixes()
addCreateSuffixes()
setBuildSuffixes()
addBuildSuffixes()
setDependencies()
addDependencies()
buildChunk(callback)
build(callback)
create(callback)
setDefaultMakeMethod()
Расширение билдера делается методами самого билдера. МетодaddParam()
автоматически генерирует методы для установки параметоров технологии (set<Param>()
и/илиadd<Param>()
). Пара примеров: Технологияimport-files
, собирающая выходной файл путем импортов из исходных:На базе не можно делать технологию css:
Или js-i:
Из которой в свою очередь можно сделать browser.js:
@scf2k: Я бы оторвал set/add у названий методов чтобы короче было. @diunko: Если оторвать set/add, как их различать?
@tadatuta: я правильно понимаю, что Было:
Стало:
и, грубо говоря, весь профит от
TechBuilder
в возможности не писатьfunction
иreturn
?@scf2k: И в одном файле. @arikon: @tadatuta В конкретном случае — да. Но ещё упрощается процесс расширения существующих технологий (создаёшь объект-билдер из готовой технологии и зовёшь его хелперы), и технологии начинают предоставлять свои конкретные хелперы для конфигурирования себя.
@arikon
build(callback)
create(callback)
Куда применится переданный в эти методыcallback
? Будет использован в качествеgetCreateResult()
иgetBuildResult()
?@arikon: Не очень понял про
addParam(name, {})
. Что можно передавать в качестве{}
, какие там могут быть ключи, помимоset
?this.params
в контексте какого объекта?@SevInf:
Если существующая технология написана с использованием билдера - то как-то так:
Если речь про технологии, написание как через
techMixin
- я не думаю, что это нужно. Если все-таки нужно - надо продумать, сейчас ответить не могу.Да. С чего-то надо начинать строить самые базовые технологии.
По аналогии с
LevelBuilder
думал сделатьset
и/илиadd
Немного некорректно выразился.
params
в итоговом классе технологии предлагаю сдеалать свойством прототипа или статическим свойством класса. Тогда инстанс сможет получить параметры черезinstance.params
илиinstance.__self.params
Сигнатура для
add
:Типичные варианты использования:
@arikon: Если у меня есть готовая технология, лежащая в пакете. Как мне расширить её?
Не думаю, что это нужно. Перепишем все технологии на использование билдера.
@SevInf:
Думаю, логично будет сделать по аналогии с
LevelBuilder
:extend
создает и возвращает точную копию билдера, используемого при созданииOldTech
. Последующими вызовами его можно доконфигугрировать, не затрагиваяOldTech
@SevInf: Чем больше погружаюсь в эту задачу, тем больше кажется что все проблемы которые мы хотим решить билдером можно в принципе решить более внятной иерархией классов технологий с правильными абстрактными классами и виртуальными методами в нужных местах. B в этом случае @tadatuta прав, профит от билдера будет не очень большим. По настоящему сделать API более гибким помог бы пересмотр flow базовой технологии, например в такую сторону:
create
как часть сборки уходит, остается одинbuild
;build
принимает на вход декларацию, список уровней и опциональный префикс;buildFile(prefix, suffix, callback)
внутри каллбека происходит подбор исходных файлов и сборка выходного. Возвращает промис на строку/буфер которая попадет в выходной файл;getFile(prefix, suffix)
,getFilesFromLevels(levels, suffix)
и тому подобные. Эти методы могут вызваться толко внутри каллбекаbuildFiles
и в момент вызова происходит следующее:mtime
сохраняются в кеше для текущего собираемого выходного файла;buildFile
с тем же префиксом и суффиксом все эти операции поднимаются из кеша, уровни повторно сканируются, новые списки файлов сравниваются с предыдущими и если они не изменились со времени прошлой сборки то вызов каллбекаbuildFiles
не происходит.Профит:
create
не вызывается в процессе сборки и не выносит людям мозгget<Something>SuffixesMap
Пара примеров (просьба воспринимать это скорей как псевдокод, чем реальное предложение API). Вот как в этом случае может выглядеть гипотетический
js-i
:А вот так
html
(попутно он сможет научиться собираться на нескольких уровнях, напримерdesktop.blocks
иtouch.blocks
и для нескольких бандлов сразу):Для подобной базовой технологии билдер может все также иметь описанное выше API. Либо, новая базовая технология может изначально иметь builder-style API. @arikon, @tadatuta, @nar, нужен фидбек. Есть ли смысл в предложении в принципе, есть ли смысл в предложении в рамках bem-tools 1.0.0?
@arikon: Сразу непонятно со сборкой
html
и другими технологиями, которые сейчас выполняются какcreate()
. Что в этом случаеdecl
иlevels
? Как будут выражаться и удовлетворяться зависимости для сборкиhtml
сразу в нескольких бандлах и уровнях?@narqo: Кажется, я уже сильно выпал из контекста всего, что около внутренностей bem-tools. Я правильно понимаю, что технологии перестают быть завязаны на декларации в уровнях и становятся нормальными сборщиками? (по крайней мере везде используются суффиксы конечных файлов, а не названия технологий) — про что-то такое мы говорили в одном из PR на gh. Еще не очень понимаю пример с html. Сейчас сборка html-файла, это: 1) прочитай bemjson, 2) прочитай bemhtml, 3) выполни bemhtml.apply(bemjson). У тебя в примере получается, что файлы для
this.getFile
будут искаться в уровнях пришедших из параметаlevels
. Это немного странно (опять же, в текущем Мире, может что-то поменялось):для сборки
bemhtml
нужно будет передавать набор уровней с блоками (common.blocks
,desktop.blocks
, и т.д.), для сборкиhtml
— набор уровней (на самом деле один уровень?) с бандлами (desktop.bundles
).За счет чего уйдет
get*SuffixesMap
? Те же самые данные, ты сейчас хардкодишь вthis.buildFile(outputPrefix, 'js', fn)
иthis.getFilesFromLevels(levels, ['js'])
. В остальном, мне эта идея нравится больше чемTechBuilder
, который я, пока, не понимаю :(@SevInf:
Про
html
: Побочный эффект от такого способа объединения - однаcreate
-команда может собирать файлы на нескольких уровнях. Гипотетический вызовhtml
может выглядеть так:levels=desktop.bundles, touch-phone.bundles;
decl
- список страниц которые надо собрать. Например[{block: "index"}, {block: "page1"}, ...]
В итоге соберетсяdesktop.bundles/index/index.html, desktop.bundles/page1/page1.html, touch-phone.bundles/index/index.html, touch-phone.bundles/page1/page1.html
. Если такая возможность считается бесполезной, то есть альтернативный вариант:build
принимает на вход только путь к текущему собираемому бандлу.create
- она продолжает работать также как и раньше - читает нужные исходники и пишет нужные выходные файлы.build
читает декларацию из бандла явно.build
-технологий нужна возможность получить список исходных уровней для бандла. Ныне покойныйbundleBuildLevels
отлично бы для этого подошел.По сути да. В предлагаемом варианте технология - это просто что-то что переводит набор входных файлов в набор выходных. При этом, как и откуда брать исходники решает сам автор модуля, никакого фиксированного flow в базовой технологии не предполагается.
Я больше всякие штуки вроде
weakSuffixes
имел ввиду. Ту же задачу можно было бы решить не созданием новой, довольно странной и неочевидной сущности в базовой технологии, а созданием альтернативного flow для суммарных технологий. Данные отbuildSuffixes
в том или ином виде все равно останутся, но пропадает необходимость их явно декларировать для того чтобы они попали в кеш - если для сборки нам надо прочитать что-то по нестандартному пути мы просто делаем это без всяких костылей.На самом деле, этот альтернативный вариант теперь мне нравиться больше чем изначальное предложение. Примеры псевдокода для тех же
js-i
иhtml
:Надо только договориться насчет
bundleLevel.getSourceLevels()
.В тред призываются @bem/owners и @zxqfox
Я, в целом, со согласен с @SevInf, и, как мне казалось, говорил про тоже самое. Ведь сами технологии — это плоский список трансформаций чего-то, описанный для уровня, а чаще и для всего проекта в целом, он будет знать что и из чего он собирает, и просто делать это.
Кроме этого, я еще хотел, но озвучивал по своему, вынести всяческие getFile куда-нибудь в bem-levels, или вообще отдельно, в bem-fs, может. Т.к. уровни у нас отвечают в т.ч. за именования в них — возможно, в нем эти getFile будут вполне ожидаемы. В любом случае, getFile, разбросанные по базовым уровням, технологиям, и т.д. — это зло. Реализации технологий (bemjson, bemhtml, deps) — это кирпичики, они всегда лежат в одинаковых местах и их можно провайдить одинаково в указанные технологии. Можно читать директории блоков и заранее узнавать про них, можно их даже читать. Опять же, если у нас будет 1 провайдер — у нас появится возможность из памяти провайдить как прочитанные с диска, так и собранные файлы реализаций технологий. Скажем, если мы говорим, что собираем html из bemjson, а bemjson у нас нет, но есть bemyaml, который может быть 1к1 трансформирован в bemjson технологией, которая заявила, что может это сделать — то мы делаем это, и сразу отдаем результат в технологию bemjson, а параллельно можем записать и bemjson, а можем и не писать — это становится не важно.
Есть еще мнение, что работать c getFile можно не напрямую из технологий, а из низкоуровневых модулей, типа bem-techs, может быть из базового класса технологии, да. А на высоком уровне, где мы будем описывать само преобразование lambda, у нас будет интерфейс аля Array или даже jQuery, которые позволят собирать все фильтры, мапы, редьюсы, лениво подгружать все нужное и отдавать в build самих технологий при необходимости. Т.е. если завернуть всю логику трансформаций в некую абстрактную коллекцию — сами лямбды станут легковесными и понятными, как раз для домохозяек. Скорость работы при этом не должна упасть, разве что на 5-10%, за счет создания небольшого кол-ва функций и их call'ов.
И последнее мнение — построение графа (deps) тоже можно описать технологией, обернуть в коллекцию, возможно даже с аналогичным интерфейсом, но несколько другой логикой внутри. Все таки с графом работаем.
В общем случае есть два вида преобразований: λt→t, λt→λ. Вторая, мне кажется, сводится к первой. Если вдаваться в подробности, то в одну λ может попадать разное кол-во входных данных, условно — λt[], λtw, λλt, которые тоже должны сводится к первой. Не суть.
Условно, технологии описывают преобразования: bemtree.js(bemtree[все кусочки со всех блоков]) → bemtree, если надо bemhtml.js(bemhtml[все кусочки со всех блоков]) → bemhtml, если надо [bemyaml(data.yaml) или bemtree(data.json) → bemjson], bemhtml(bemjson) → html Собрав все, а это можно сделать при старте прочитав make и levels, это подграф из всех известных исходников и целей. Узлы — реализации или данные (условно, term), хорды — преобразования (условно, lambda). Поскольку могут быть случаи, когда возможно собрать из разных источников — жедательно где-то указывать порядок, либо же брать из порядка декларации/подключения технологий.
может быть лучше так?
Или вообще через provide, как в
ym
.@zxqfox, правильно ли я понял, что технология
js
в твоем предложении будет выглядет как то так:При этом, сам
allJS
- продукт работы другой технологии. СамbuiltJS
в последствии может быть использован как исходник для какой-то другой технологии, напримерjs+bemhtml
?@SevInf ± да. условно:
@SevInf Конечно же, частоиспользуемые вещи надо будет засахарить.
reduce
слегка сносит мозг. Да и закешировать тоже или предоставить интерфейс через какой-то модуль, например, тот же bem-core, который предоставляет сами технологии. Можно представить all как DB про БЭМ-объекты. Например, блоки — записи, технологии — поля, но могут быть представлены как записи, библиотеки — таблицы. Грубо, конечно, но близко к истине. В таком случае многое вообще становится не нужно, достаточно будет предоставить простой интерфейс (взять, положить, и т.д.) и описать язык взаимодействия.@zxqfox, да, кажется что это движение в правильном направлении. Сами сборщики на низком уровне тогда очень просто можно было бы описывать декларативно, a-la Rake/Jake/модульная система:
@SevInf :+1: Да, это именно то, о чем я думаю
Кстати, заодно и хороший повод термин "технология" заменить на что-нибудь более очевидное, например "task" или "build step":)
Название надо придумать, да.
Task
ок в совокупности сbuilder
.Может быть возможность асинхронно провайдить тоже нужна? Какой тогда интерфейс лучше? UPD: promises.
Кажется, для асинхронности самым правильным, удобным и легкочитаемым будет возврат промиса.
@SevInf Да, согласен. Все время про них забываю. :aerial_tramway:
@SevInf А почему
bem.builder
?@zxqfox, никакой конретной причины нет, просто чтобы псевдокод продемонстрировать. Если остановились на том, что это таск то наверное правильнее будет
bem.task()
@SevInf завтра же сменю ник на @zqfox ;-) Только ведь в таком виде
task
не сходится с командами, которые выбрасываются вBEM.api
и вcoa
. И еще, ноды полезны дляdeps
,mergedBundles
, еще для чего-то? Илиmerged
как-то проще можно будет собрать?Хотя, да, конечно можно будет проще:
да ладно, я учусь потихоньку)
С командами, возможно, действительно не очень сходится. Есть пока слабо оформившаяся идея, сделать возможность объявлять некоторые таски вызываемыми как комманды:
Таким образом
make
можно будет вызвать из командной строки, передать ей опции и аргументы. Для того чтобы позватьmake
нам надо собрать,js
,css
иhtml
. Дляjs
в свою очередь нуженallJs
. ДляalJsl
нуженall
и так далее. Но идея очень сырая, и я не уверен что это мысль в правильном направлении. Пока такая штука больше вопросов вызывает, чем дает ответов:В общем, хотелось бы подтверждения или опровержения негодности этой идеи.