Веб-компоненты с LibJS

в 23:33, , рубрики: html, javascript, web components, Веб-разработка, велосипед, метки: ,

Веб компоненты с LibJSПо факту, LibJS это связка нескольких библиотек. О Mask и Include я уже вкратце рассказал, а сегодня завершит трилогию CompoJS(@github).

Постановка задачи

  1. разработка компонент вне основного проекта
  2. «dev»-независимая маршрутизация — route('compo','file:///d:/dev/compo/{name}/lib/{name}.js');
  3. легкое внедрение в проект — include('compo','mycomponent');
  4. инициализация прямо из макета — <myComponent <!-- settings --> /> (без javascript-a)

Итого 2 строчки кода, (3) и (4), и компонент подключен в проект вместе с ресурсами и другими компонентами. Больше не надо копировать картинки и стили — все под капотом. Хочу отметить, что разрабатываю в основном мобильные приложения, так что «под капотом» должно быть что-то легкое, но очень быстрое. В статье также будет приведен пример компоненты с использованием библиотеки @PrismJS

Компонентное проектирование

Начнем пожалуй с организации кода. Веб компоненты с LibJS
Так выглядит «средне статистическая» структура. Компонент может находится как в директории проекта, если это проект-специфический компонент, так и вне проекта, если это для повторного использования. Компонент может быть как маленький, так и вплоть до целого приложения. Разработка таким образом в разы ускоряется и замечательно распределяется(делегируется). Все сводится собственно к разработке компонент, которая у меня начинается в {component}/dev/* — здесь создается и тестируется компонент. Теперь от идеи до реализации —

Загрузка

@github IncludeJS
@github IncludeJS.Builder
С прошлой статьи про IncludeJS я переделал сборщик под node.js и включил в проект. Что нам эти вещи дают:

  • все то, что может AMD — асинхронное подключение скриптов,
  • свободный стиль декларации «модуля» и без дополнительных манифестов,
  • ленивые скрипты,
  • подключение стилей,
  • загрузка данных через XMLHTTPRequest,
  • возможность собрать проект —
    • скопировать все ресурсы внешних компонентов в рабочий каталог проекта.
    • подправить пути к новым ресурсам
    • объединить все скрипты и стили.
    • ленивые скрипты и загруженные данные встроить в html.

Инициализация Компонентов

В качестве макета выступает MaskJS — он удобный, очень быстрый и поддерживает «кастомные контролы», на которых в свою очередь и базируются компоненты.

interface ICustomControl{
     attribute String tagName;
     IAppendChild render(JsonTemplateData values, IAppendChild container, Object context);
}

Здесь интересным аргументом является context — через него происходят подписки на события и в него же строится дерево компонентов. Важным событием является DOMInsert, это вот почему мы можем иметь такой макет:

scroller { /** content */ }

И после вставки в DOM наш компонент Scroller сам обновится, подпишется на события мыши, нарисует скроллы. То есть, происходит по смыслу тот же $('.placeholder').initScroller(), но уже без нашего участия.

Класс «Компонент»

Всю документацию приводить не буду, а остановлюсь на главных моментах.

  • $

    Работает в паре с jQuery, Zepto — после рендеринга компоненты доступен ".$" с обернутыми HTMLElement(s)

  • События

    В конструкторе можно указать перечень событий которые будут делегированы элементам

    this.events = {
        "touchEnd: header > .touchMe": this.onTouchMe,
         ...
    }
    

    В ключе, как видим, указывается тип события и через двоеточие selector (какому элементу делегируем событие)

  • Дочерние элементы и компоненты

    В конструкторе можно указать перечень элементов или других компонентов, которые будут выбраны после рендеринга в DOM

    this.compos = {
        touchMe: '$: button.touchMe', //'[how: ][who]'
        ...
    }
    

    $: — выбирает елемент через this.$.find
    compo: — ищет дочерний компонент по css подобному селектору. Доступны (tagName, id, class)
    пусто — указывает, что надо выбрать через this.$[0].querySelector
    Такой подход удобный тем, что нужные элементы/компоненты всегда под рукой, и видно что наш компонент использует.

Пример
Постановка задачи

В приложении надо «подсветить» исходный код, и так, что б можно было его свернуть.

Участков много? Самое ТО для компоненты!

  1. Найдем хорошую библиотеку — PrismJS
  2. Создадим где-то папку с нашей компонентой, скопируем туда библиотеку #Prism с её стилями, найдем картинки "+","-"
  3. Напишем код компоненты:
    include
        .js('prism/prism.lib.js')
        .css(['style/main.css', 'prism/prism.lib.css'])
        .done(function() {
            mask.registerHandler('highlight', Class({
                Base: Compo,
                Extends: CompoUtils,
                Construct: function(){
                    this.compos = {
                        header: '$: header'
                    }  
                },            
                events: {
                    'click: header': function(e) {
                        this.compos.header.toggleClass('closed');
                    }
                },
                render: function(values, container, cntx) {
                    var code = this.nodes.content,
                        attr = this.attr;
                    /** Готовим container */
                    this.addClass('highlight');
                    this.tagName = 'div';
                    /** Готовим структуру компоненты */
                    var t = 'header; pre class="language-#{language}" > code class="language-#{language}";';
                    this.nodes = mask.compile(t);
                    
                    Compo.find(this.nodes, 'code', null, 'node').nodes = {
                        /** Передаем код который должен быть подсвеченным */
                        content: code
                    };
                    /** Отдаем родителю завершить рендеринг */
                    Compo.prototype.render.call(this, this.attr, container, cntx);
                    /** Подсвечиваем код*/
                    Prism.highlightElement(this.$.find('code').get(0));
                }
            }))
    });
    
  4. Добавляем файл стилей:
    .highlight {
        background: rgb(222,222,222);
       /* ... */
    }
    .highlight > header {
        height:50px;
        background:url(../images/collapse.png) 0 0 no-repeat;
        cursor: pointer;
       /* ...*/
    }
    .highlight > header.closed {
        background-image: url(../images/expand.png);
    }
    .highlight > header.closed + pre {    
        display: none;
    }
    
  5. Подключаем:
    /*...*/
    highlight language="javascript" > "var a = 10;"
    /*...*/
    

    И больше никаких скриптов для инициализации не нужно.А если мы разработали highlighter вне главного проекта, и потом просто «заинклудили», то при релизе Include.Builder сам сможет собрать нужные скрипты, стили и скопировать наши "+" "-" картинки в каталог приложения.
    Результат

  6. А нужно целые файлы показать и подсветить?
    /** ... */
    render: function(){
        /** ... */
        $.get(this.attr.src, function(response){  
            var _code = this.$.find('code').get(0);
            _code.innerText = response;
            Prism.highlightElement(_code);
        }.bind(this)
    }
    
    highlight lang="javascript" src="example/script.js";
    

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

Здесь, @CompoJS, можно посмотреть дополнительные примеры и описания.

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

Удачи.

Автор: tenbits

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


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