polymerjs vulcanize

Проекте, над которым я сейчас работаю, имеет виджетоподобную клиентскую архитектуру. Причем виджеты системы могут использовать любую библиотеку для своей реализации, например, ReactJS, PolymerJS, VueJS, d3JS и другие. Несколько виджетов системы реализованы, как раз, как вэб-компоненты на базе PolymerJS.

Поэтому предлагаю вашему вниманию один из подходов для оптимизации polymer-виджетов.



Содержание:


1. Описание проблемы
2. Какие сложности возникают?
3. Как их можно решить?
4. Библиотека vulcanize-polymer-module
4.1. Структура
4.2. Описание bower.json
4.3. Описание package.json
4.3.1. Установка утилит
4.3.2. Настройка RollupJS
4.4. vulcanize-utils.js
5. Выводы

1. Описание проблемы


Одним из главных проблем polymer-приложений — это множественная подгрузка, используемых компонентов и всех зависимых компонентов, которые в свою очередь, могут состоять из вспомогательных стилей, behavior-ов, подгружаемых так же дополнительно. В результате консоль в network-разделе будет «засыпана» данными файлами. В виду всего этого, первая загрузка такого виджета может быть достаточно долгой, в зависимости от количества используемых составных вэб-компонентов.

Для этих целей в polymer-приложениях применяется вулканизация. Подразумевается, что у данного приложения, есть точка входа в виде, например, index.html, в котором разворачивается главный компонент-контейнер, например <App/>. В данном файле подключается само ядро polymer и файл компонента-контейнера <App/> и далее иерархически подключаются все используемые компоненты, которые сами являются отдельными html-файлами. Сам процесс вулканизации заключается в «склеивании» всех используемых компонентов и ядра polymer в один файл, который и будет в итоге являться точкой входа для index.html.

2. Какие сложности возникают?


  1. Первой сложностью является то, что у меня не polymer-приложение, а несколько составных компонентов(назовем их умными компонентами — УК), которые обернуты в виджет системы, то есть нет единой точки входа.
  2. Второй сложностью — что в течении работы с приложением может быть и не вызвана вовсе страница с данными виджетами, и соответсвенно ни один из polymer-компонентов будет просто не нужен в текущей сессию работы, не говоря уж о самом ядре polymer.
  3. Третьей — один УК использует один набор атомарных (paper-, iron- и другие) компонентов(назовем их глупыми компонентами — ГК), а другой — другой набор. Причем могут быть пересечения, то есть два разных УК используют одни и те же ГК.

3. Как их можно решить?


Если рассмотреть первую сложность, я бы мог вулканизировать отдельно каждый УК, но тогда, если взять третью проблему возможно дублирование одних и тех же ГК, и точно будет дублирование ядра polymer, если рассмотреть ситуацию, при которой за одну сессию были использованы как минимум два УК. Поэтому необходимо другое решение.

Если рассмотреть вторую сложность, нужно сделать так, что бы ядро polymer и ГКы загружались только один раз, при первом обращении к одному из УК, и на момент обращения ко второму, нет необходимости загружать заново все, а только загрузить динамически сам УК.
Если рассмотреть третью сложность, то нам нужно составить список всех используемых глупых компонентов в умных, который в итоге мы и будем вулканизировать вместе с самим polymer.

4. Библиотека vulcanize-polymer-module


Все выше сказанные тезисы я оформил в виде библиотеки vulcanize-polymer-module.
Хочу рассказать о ней подробней.

4.1. Структура

vulcanize-polymer-module/
+-- imports.html
+-- vulcanize-utils.js
+-- rollup.config.js
+-- bower.json
L-- package.json


4.2. Описание bower.json

В нем мы описываем все ГК, которые нам необходимы, как зависимости, включая так же само ядро polymer.

Например, раздел dependencies может выглядеть так:

dependencies
"dependencies": {
    "polymer": "Polymer/polymer#^2.0.0",
    "polymer-redux": "^1.0.0",
    "iron-flex-layout": "PolymerElements/iron-flex-layout#^2.0.0",
    "paper-button": "PolymerElements/paper-button#^2.0.0",
    "paper-badge": "PolymerElements/paper-badge#^2.0.0",
    "paper-icon-button": "PolymerElements/paper-icon-button#^2.0.0",
    "paper-input": "PolymerElements/paper-input#^2.0.0",
    "paper-item": "PolymerElements/paper-item#^2.0.0",
    "paper-checkbox": "PolymerElements/paper-checkbox#^2.0.0",
    "paper-tabs": "PolymerElements/paper-tabs#^2.0.0",
    "paper-listbox": "PolymerElements/paper-listbox#^2.0.0",
    "iron-a11y-keys": "PolymerElements/iron-a11y-keys#^2.0.0",
    "iron-list": "PolymerElements/iron-list#^2.0.0",
    "iron-icons": "PolymerElements/iron-icons#^2.0.0",
    "paper-progress": "PolymerElements/paper-progress#^2.0.0",
    "vaadin-split-layout": "vaadin/vaadin-split-layout#^2.0.0",
    "vaadin-grid": "^3.0.0",
    "iron-pages": "PolymerElements/iron-pages#^2.0.0",
    "iron-collapse": "PolymerElements/iron-collapse#^2.0.0",
    "iron-overlay-behavior": "PolymerElements/iron-overlay-behavior#^2.0.0",
    "vaadin-context-menu": "^3.0.0"
  }




Так как я совместно с polymer использую redux, я включил библиотеку polymer-redux.

4.3. Описание package.json

В нем прописаны зависимости, необходимые нам для сборки, в частности RollupJS, который используется для промежуточной очистки кода выходного файла. Так же описаны команды, используемые для вулканизации, давайте рассмотрим их подробнее.

scripts
"scripts": {
    "build": "rollup -c",
    "vulcanize": "vulcanize imports.html  --inline-scripts --inline-css --strip-comments",
    "run-vulcanize": "npm run vulcanize > imports.vulcanize.html",
    "vulcanized": "vulcanize imports.html  --inline-scripts --inline-css --strip-comments | crisper  --html imports.vulcanized.html --js imports.vulcanized.js > imports.vulcanized.html",
    "html-minifier": "html-minifier imports.vulcanized.html --remove-optional-tags --collapse-whitespace --preserve-line-breaks -o imports.vulcanized.min.html",
    "build-all": "npm run vulcanized && npm run build && npm run html-minifier"
  }


Команды, в порядке и приоритетности их использования:
  • build-all — основная команда, которая и запускает весь процесс вулканизации.
  • vulcanized — выполняет саму вулканизация, то есть объединение всех компонентов и ядра в один файл, затем разбивает всю сборку отдельно на .js и .html файлы.(Используется утилита vulcanize и crisper)
  • build — очистка кода js-файла от комментариев.(используется RollupJS)
  • html-minifier — минификация html-файла.(используется html-minifier)

4.3.1. Установка утилит

Как видим используется множество дополнительных утилит, которые нам необходимо предварительно установить в систему.

Установка утилит
npm install -g vulcanize
npm install -g crisper
npm install -g html-minifier


4.3.2. Настройка RollupJS

Так как rollup используется только для очистки js-кода я использую только один плагин к нему- rollup-plugin-cleanup. Плагин rollup-plugin-progress используется для визуализации процесса сборки.

rollup.config.js
import progress from 'rollup-plugin-progress';
import cleanup from 'rollup-plugin-cleanup';

export default {
	entry: 'imports.vulcanized.js',
	dest: 'imports.vulcanized.js',

	plugins: [
		cleanup(),
		progress({
		}),
	]
};


4.4. vulcanize-utils.js

Для решения второго требования был написан утилитарный метод loadVulcanized, который загружает УК, но перед этим загружает вулканизированный файл, причем делает это один раз и в случаи повторного вызова загружает только сам УК.
Рассмотрим подробнее его параметры.

loadVulcanized = function(url, urlVulcanized, controller, html, store)
  • url — путь к умному компоненту. Является обязательным.
  • urlVulcanized -путь к вулканизированной сборке. По умолчанию — путь к данной сборке — ../vulcanize-polymer-module/imports.vulcanized.min.html
  • controller — в моем случаи это контроллер системного виджета. Опционально.
  • html — html-объект умного компонента. Имеет смысл, если задан контроллер.
  • store — redux store. Опционально.


5. Выводы


Конечно можно использовать polymer-cli с параметром build, но при сборке с его помощью подразумевается, что билдится polymer-проект, а так как мы используем компоненты не в одном контейнере <App/>, то собирать каждый УК придется по отдельности и файлы сборки будут иметь дублирование ядра polymer и составных ГК. Поэтому подход, описанный в статье имеет достаточную эффективность в системах использующих несколько UI-библиотек совместно, за счет единой точки входа для всех УК на базе polymer.

Один из возможных минусов можно рассмотреть, как избыточность ГК в вулканизированном файле, так как в нем собраны все ГК всех УК, используемых в системе, но при этом не все УК могут быть использованы за сессию работы, в виду чего не все ГК будут использованы в загруженом вулканизированном файле.

Также как небольшое неудобство, можно рассмотреть тот факт, что после добавления нового компонента необходимо запустить сборку заново, после чего сделать обновление репозитория(push), а другие пользователи должны обновить данную библиотеку через bower update.

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

Так что форкайте, милости просим.
Поделиться с друзьями
-->

Комментарии (6)


  1. i360u
    01.08.2017 10:16

    1. Названия веб компонентов должны содержать дефис и закрывающий тег по стандарту: не <App/> как в Реакте, а <my-app></my-app> (Или <app-shell>, если уж говорить о какой-то общепринятой декомпозиции). Если у вас в одном коде декларации компонентов в разных стилях для разных декомпозиционных библиотек — это ад. И само использование в одном проекте разных инструментов делающих одно и то-же — безумие.
    2. Подгрузка приложения по частям — это скорее преимущество, если вы работаете с http/2 и server push, вулканизация не нужна как таковая, она может понадобится только в отдельных узких местах.
    3. "Глупые компоненты" — это другое: это компоненты содержащие разметку без логики. Iron и paper — не глупые компоненты, а скорее UI-примитивы и утилиты.
    4. Polymer-компонент (как и любой веб-компонент) может сам содержать ссылки на все свои зависимости (вкючая ядро Polymer), и если он не вызван на странице — ничего лишнего к ней не подключается.

    точно будет дублирование ядра polymer

    Polymer подключается из одного места, там ничего не дублируется.


    В общем, статья про то, как создать франкенштейна. Не делайте так.


    1. kolesoffac
      01.08.2017 10:39

      1. По <App/> — это было описано для «схематичного» описания решаемой проблемы. И данные инструменты делают не одно и тоже, а просто расширяют возможности использования системы для решения специфичных задач.
      2. Возможное преимущество, но не всегда
      3. Я специально написал, что я их так назвал в контексте статьи, что бы разделить по типу
      4. Для динамической прогрузки компонентов через importHref, ядро должно быть подгружено раньше

      А по поводу не делайте так, я написал в выводах, что статья для специфичной задачи и иллюстрирует один из подходов и делать так везде не обязательно. И даже не нужно.


      1. i360u
        01.08.2017 11:27

        1. Они (библиотеки) решают одну задачу: декомпозицию вашего вью. Плюс стандартный набор из всяких байндингов и репитеров. Не одно и то-же могут тут делать только сами виджеты, специфичные задачи — только у них. Смешивать это все в одну кашу стоит только при миграции на другой стек и только временно, например, постепенно заменяя React-компоненты веб-компонентами.
        2. А когда не?
        3. Это устоявшаяся терминология. Переопределять ее на время написания статьи… Крайне странно.
        4. Я говорю не о динамической загрузке, а о том, что если браузер не встретит в разметке текущего вида вызова веб-компонента — он просто не импортит его зависимости. А если вы собираете бандл вулканайзом — то да — может загрузиться куча ненужного.

        Вы просто описали плохой паттерн который не стоит воспроизводить а значит и развивать специфичный для него тулчейн.


        1. kolesoffac
          01.08.2017 11:43

          1. Мнение имеет право быть: но в данный момент это не каша, а расширение технологических возможностей системы, не мешающие друг другу, а только увеличивающие эффективность системы в целом. Можно по разному смотреть на ситуацию. Может быть со стороны это кажется кашей, но на практике достаточно гибкое и эффективное решение в использовании различных технологических подходов использования нескольких render-engine'ов в одной обертке системных виджетов.
          2. Мнения могут быть разные, но можно сказать одно — вы пробовали когда нибудь копировать тысячу мелких файлов с флешки? И сранить тоже самое когда их запихать в архив — производительность будет на лицо.
          3. ГК — который не делает никакой логики, а просто рендерит переданый ему стейт. Поэтому так и назвал. Мне кажется, нет противоречий.
          4. В каждом подходе есть + / -.

          Я не против Вашего мнения, и никого не принуждаю его использовать. Может быть и обратная ситуация, данный подход покажет человеку как делать не надо и что тоже является большим плюсом статьи!


  1. RuLeZzzBlaBlaBla
    01.08.2017 10:46

    Я конечно ничего не знаю о полимере, но складывается ощущение, что webpack должен решать такую задачу с меньшими усилиями, чем rollup


  1. kolesoffac
    01.08.2017 10:49

    А Rollup в данном подходе и не хэдлайнер. Думаю, что это можно решить и через webpack, но я использовал именно Rollup.