Другой IncludeJS

в 18:04, , рубрики: javascript, Веб-разработка, сборка проекта, метки:

Только не смейтесь. Наверное это самая лучшая вводная фраза — так как ну на самом деле, «Как? Ещё один сборщик скриптов?». Да, в посте пойдёт речь о ещё одной «личной наработке ». Постараюсь не просто вбросить ещё одну либу, а поразмышлять над тем, чем же это решение могло бы быть лучше миллиона других. Возможно у меня не получится донести до вас, все как есть, но я попытаюсь, а вы не судите строго. Это будет вторая статья в серии о компонентах и MVP. Если интересно можете ознакомиться с первой.

Проблема компонент

Часто компоненты/виджэты помимо скриптов состоят из других ресурсов — html разметки, стилей, картинок, компонентов. И вот, хотелось бы получить сборщик этих самых ресурсoв. Во время разработки указываете путь к директории с компонентами/библиотеками, подключаете нужное, а во время сборки html склеится в один, стили и javascript тоже, картинки скопируются в наше приложение — «Кушать подано». Прошу не пинать больно, такие «конструкторы» уже наверняка существуют, но подходящего я не нашёл — и к тому же, моей целью было не создать конкурирующий продукт, а сделать нужную мне вещь для себя.

Здесь(github) эту вещь можно скачать/глянуть на апи. Того, кому эта тема показалась интересной, и кто готов уделить 10 минут на эту неумелую писанину, приглашаю под кат.

Немного истории, или что такое правильный велосипед

Если честно, мне немного неловко называть вещи «правильными» или писать «как надо что-то делать» — ведь это все субъективно, поэтому так к этому и относитесь.
Когда перед нами стоит задача, мы оцениваем какие инструменты у нас есть для её решения, а каких нету. Также думаем, что могло бы ускорить процесс. И вот если мы решили немножко, совсем чуть-чуть, подправить существующий инструментарий — всё, первый шаг в сотню миль к велосипеду сделан. Должно быть решено ещё много подобных задач, где по кирпичику будем выстраивать функционал. Не всё переживёт поставленные задачи, где-то мы плюнем и возьмём готовые библиотеки. А где-то просто отпадёт нужда в этих самых «чуть-чуть доделать», и тогда подобно воде, которой некуда течь, превратится в болото. Но болото, это не плохо, а напротив — оно даёт жизнь миллионам видам.
Но если инструментарий пережил, это множественное «чуть-чуть доделывание», то в результате, мы получим «Вещь», где мы узнаем каждую запятую или наоборот, будущий Я удивится и посмеётся над Я настоящим. Где уже с лёгкостью добавляем, изменяем, правим функционал. Где ощущается ветер свободы, когда эта «Вещь» делает, то что ты хочешь, и так, как ты этого хочешь. Прошу прощение за это лирическое отступление.

А сама история — банальна

  • Надоело писать весь скрипт в одном файле? — Разбиваем на несколько.
  • Файлов стало больше и надоело все подключать в index.html? — Немного динамики: создаём <script> и вставлаем в dom
  • Скрипты требуют стили и ajax? Вот тэг link с атрибутом href и xmlhttprequest с onreadystatechange
  • Скриптам надо знать, когда вложенные/рекурсивные ресурсы загружены? Сейчас используется дерево ресурсов и eval. За eval не ругайтесь, так как первое, он упрощает нам обработку зависимостей, и таким образом нам не надо нигде больше указывать (в никаких package.json файлах), что это за модуль и что он требует. А также не надо строить конструкций вида «define». И второе, цель ведь у нас будет — собрать приложение, вот там-то этот eval уже будет отсутствовать (кроме ленивых скриптов, см. ниже). Хотя должен отметить, что даже если и не собирать приложение, то в этом eval-e нет ничего криминального, так как используется по назначению, одинаково быстр как и подключение скрипта через тэг и не является проблемой безопасности (если злой человек смог дойти до возможности использовать этот eval, то собственно он ему уже даже и не нужен).
  • Нужен ленивый скрипт, который будет доступен сразу по требованию (без никаких IDeferred)? Схема простая: XMLHTTRequest → __defineGetter__ → eval. Из примера:
    include.lazy({'ui.notification':'path'}), и как только мы вызовем ui.notification./**eval происходит сейчас, далее вызываем функцию*/show('Hello World'); Обращу внимание, на то, что модуль должен быть с учётом того, как и что возвращает функция eval.
  • Нужна удобная обработка путей и маршрутизация? "utils.js" — путь относительный к родительскому. "/utils.js" — путь относительный к приложению. .js({ dom: "zepto" }) — перед этим зарегистрируем наш маршрут dom: .cfg({dom: "/scripts/dom/{name}.js"}).

Сборка

Собственно, если другие загрузчики скриптов делают акцент на возможности загрузки, то я предам большего значения именно «варки». Сборщик написан тоже на javascripte и он тоже на гитхабе в /lib/builder/. Правда он написан для консольного приложения, которое я пока что не могу выложить в люди, но оно больше ничего не делает, как запускает батники на сборку проекта, создаёт релизные файлы и копирует картинки. Под капотом этого приложения находится Qt с WebKit-ом. Если тема будет вам интересна, то я постараюсь поднапрячся и побыстрее закончить, а пока можно протестировать сам сборщик на gh-pages.

Сборка относительно простая — передаём index.html, и с него начинаем строить дерево ресурсов. Вначале загружаем все скрипты, по очереди заходим в них, парсим, загружаем зависимости, потом зависимости зависимостей и т.д. — таким образом, строим дерево. Далее удаляем на ветках повторяющиеся ресурсы и выравниваем дерево в список. Первыми идут те которые ничего не требуют, последними, те которые были в нашем index.html. Скрипты соединим и сжимаем по желанию, стили тоже. А вот данные, которые были загружены через .load() и ленивые скрипты записываются в тело index.html в тэг <script type='include/lazy|load' id='id' />. Почему туда, а не в тело кода как strings? Так не теряется наглядность и легче рефакторить, даже собранный проект.

Пример

index.html
<!DOCTYPE html>
<script src='lib/include.js'></script>
<script src='script/main.js'></script>
<script type='text/template' id='layout'>
    header {  ...  }
    dialog state='visible' {
         h3 > '#{title}'
         div > 'Description'
    }
</script>

script/main.js

include
.cfg({
  /** Плохо иметь в проекте такие хардкорные пути. Это  здесь для примера,  настройки конфига можно держать в отдельном файле и только для конкретной дэв.машины */
  components: 'file:///c:/dev/compo/{name}/main.js',
  lib: 'file:///c:/dev/libs/{name}/lib/{name}.js'
})
.js({
   components: 'dialog',
   lib: 'mask'
})
.ready(function(){
    document.body.appendChild(mask.render('#layout',{ title: 'My Title' });
});

Вот собственно и все — никаких стилей, картинок. Все за нас сделает наш отдельный модуль. А грубо набросанным он будет выглядеть так:

componets/dialog/main.js

include
.css('style.css')
.load('view.html')
.js({ lib: ['mask','class'] })
.done(function(response){
    mask.registerHandler('dialog', Class({
        render: function(values, conainer){
              /** строим wrapper */
             var wrapper = mask.compile(response.load[0]);

             mask.find('.container', wrapper).nodes = this.nodes;
             mask.renderDom(wrapper, values, container);
             
             /** вешаем обработчик на закрытие */
            container.querySelector('.dialog .close').addEventListener(function(){
                 container.querySelector('.dialog').style.display = 'none';
            },false);
        }
    });
});

componets/dialog/view.html
div.dialog> div.wrapper > div.cell{
     div.header > div.close;
     div.container;
}

componets/dialog/style.css

.dialog, .overlay{
    position:fixed; top:0px; left:0px; width:100%; height:100%;
}
.dialog > .wrapper {
   display:table; height:100%; margin:0 auto;
}
.dialog .cell {
  display:table-cell; vertical-align:middle;
}
.dialog .close {
    width:16px; height:16px; background:url(image/close.png) 0 0 no-repeat; cursor: pointer;
}
.dialog .close:hover { ... }
.dialog .close:active { ... }

Это конечно очень простой и неправильный пример диалогового окна, но не это главное — главное, что бы вы увидели, как просто подключается отдельный ресурс, с картинками, стилями и html разметкой. Здесь же немножко показал MaskJS в действии, а в следующей статье к ним подключится библиотека компонент, и это совсем будет торт. Хотя это снова же субъективное мнение, возможно все это ерунда, но она помогает приятно разрабатывать html5 приложения. Если знаете похожие инструменты, поделитесь ими. Также хочу предупредить, что пока это все очень сырое, но я буду продолжать над этим всем работать — приносит море удовольствия. Если бы такая штука была полезна кому-то, тогда было бы больше мотивации. Но если нет, и того хватит, что есть ;)

Удачи.

Автор: tenbits

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js