Шлях джесая, частина перша: typescript, webpack, jsx, custom elements...

Ну що, мальчиші і хлопці, а чи не пора вам пізнати джесай до? Не, ну вам то ще рано, а я ось, мабуть, почну. Але ви можете приєднатися.

У низці останніх статей про jsx/react та інше я спостерігав намотування соплів на кулак та інші непотребства в обговореннях, мовляв як же нам без бабеля, а навіщо нам jsx та інше малодушничание. Будемо припиняти. Сьогодні ми будемо будувати webpack, прикрутимо до нього typescript і jsx без реакта, зберемо на tsx custom element без бабеля, зарегаем його і виведемо на сторінці. Багато хто з вас сьогодні подорослішають, а деякі, можливо, навіть стануть чоловіками.
Якщо ви боїтеся що світ для вас ніколи вже не буде колишнім — не бійтеся, він колишнім ніколи і не був, так що заходьте…

Це перша стаття з передбачуваного циклу, будемо йти по наростаючій, щоб не розірвати незміцнілі мізки. Я дам вам цікавих ідей, розраховую що ви дасте мені цікаве спілкування. Це звичайно навряд чи, але я радикальний оптиміст.

Почнемо зі структури проекту. Вона у нас проста (поки що), у міру викладу матеріалу буде трошки ускладнюватися. На даний момент маємо:
  • сорцы у src
  • конфіги в корені (npm, webpack, typescript і tslint)
  • в директорію build кладеться збірка, звідки вона перекочовує в dist — тут вже будуть лежати пакети, готові до дистрибуції та деплою (але не в цій статті — це на майбутнє)
У сорцах все простенько:
  • assets — всяка статика (поки тільки index.html)
  • client — власне наші исходники теми цієї статті. На даний момент лише набір кастомних елементів (один, якщо бути точним), яких ми накидаем для полегшення собі життя.
На цьому зі структурою все, рухаємо далі. Так, виглядає це так:



Тепер пройдемося по webpack чий конфіг вдало помістився в скріншот (webpack ми потім звичайно викинемо, але поки нехай буде). Статей за його налаштуванні мережі — море, я пройдуся тільки по значущим. Як бачимо, підключення модулів (resolve) пошук починається з tsx, потім ts і тільки в разі безнадії шукаються js файли. Індексні файли вказані для того що б при підключенні модуля вказувати тільки директорію — просто зручний угоди для себе, не більше. Далі бачимо мінімально необхідний для зручності набір плагінів, насправді вони зараз за великим рахунком і не потрібні, але нехай будуть. Огляд плагінів можна подивитися тут: Webpack + React. Як зменшити бандл в 15 разів або в самій документації по вебпэку.
Процес складання у нас поки вкрай простий — тягнемо ts/tsx файли і транпайлим їх в js. Відповідно нам потрібний тільки лінтер і компілятор typescript. А як же бабель? — запитають самі юні слухачі. Бабель не потрібен.
Будуємо завантажувачі — власне все примітивно:

,tslint: {
emitErrors: true
,failOnHint: true 
}
,module: {
preLoaders: [
{test: /\.tsx?$/, loader: "tslint"}
]
,loaders: [
{test: /\.tsx?$/, loader: 'ts'}
]
}

Після того як примітивність процесу стала нам очевидна, можна прописати в залежності package.json (ми ж порядні програмісти, а порядні програмісти оформляють проекти npm модулями). Залежностей небагато:

package.json
"devDependencies": {
"ts-loader": "^1.3.2",
"tslint": "^4.0.2",
"tslint-loader": "^3.2.1",
"typescript": "^2.1.4",
"webpack": "^1.14.0",
"webpack-bundle-tracker": "0.1.0"
},

тут все просто — typescript і його завантажувач для webpack, лінтер з завантажувачем, сам webpack (що б тягнути в конфіги його вбудовані плагіни) і один плагинчик для трекания процесу складання. А як же реактив? Реактив теж не потрібен.
Так, typescript у нас другий — він смачніше, а webpack — перший, другий ще не готовий. Власне тут все.
Але! Конфіг webpack вийшов лаконічним з тієї причини що конфіги typescript компілятора і линтера ми винесли в корінь проекту окремими файлами. Можна їх засунути і в конфіг вебпэка, але окремим файлом — рекомендований шлях. Хіба Мало, раптом вирішите поміняти систему складання, а у вас раз — і все готово до переїзду.
Так, а тепер поглянемо в опції компілятора typescript:

tsconfig.json
{
"compilerOptions": {
"jsx": "react"
,"jsxFactory": "dom.createElement"
,"lib": ["ES2017", "DOM"]
,"target": "ESNext"
,"module": "commonjs"
,"removeComments": true
,"noImplicitAny": false
}
}

Так, тут теж трохи цікаво. Перша опція — jsx. Тут ми вказуємо як саме ми хочемо обробляти jsx (так, typescript вже вміє jsx, одна з причин чому нам не потрібен бабель). Варіантів всього два — не обробляти взагалі і обробляти так само як реактив. Ну ок, кажемо, що хочемо як реактив. А як у нас обробляє jsx реактив (а точніше бабель)? А він зустрілися в коді теги перетворюють до такого вигляду:

було:
<div id="test">
some text
<some_tag>
another text
</some_tag>
</div>

стало:
React.createElement("div", { id: 'test'},
"some text", 
React.createElement("some_tag", null, "another text")
)

Тобто сигнатура проста — перший параметр — ім'я тега, другий — об'єкт з атрибутами тега null, всі інші — childs елемента в порядку їх оголошення у верстці. Як бачимо — childs — це або рядок для текстових нод або вже створений елемент. Ок. Ми скористаємося можливістю замінити функцію створення елементів (React.createElement) на свою (так, так можна, тому нам не потрібен реактив), для цього вкажемо в налаштуванні jsxFactory ім'я нашої функції (у нашому випадку dom.createElement). Тобто на виході отримаємо ось таке:

dom.createElement("div", { id: 'test'},
"some text", 
dom.createElement("some_tag", null, "another text")
)

Можна замість налаштування jsxFactory вказати reactNamespace — тут ми вказуємо ім'я об'єкта, який надає метод createElement. Тобто з цієї налаштуванням наш конфіг виглядав би так:

{
"compilerOptions": {
"jsx": "react"
,"reactNamespace": "dom"
,...
}
}

Але я віддаю перевагу явне замість неявного, плюс я вважаю правильніше прив'язуватися до технології jsx а не до react (є думка [моє] що jsx залишиться а react кане в лету). Не суть. Ви можете поступати у відповідності зі своїми переконаннями, це нормально. Так, і надалі цю функцію створення елементів я буду називати складальником dom (в сенсі builder). В цій статті поговоримо про правила складання.

Залишилися налаштування не так критичні з точки зору теми статті, пройдемося побіжно.
Ми вказуємо модні либы що б мати можливість використовувати сучасні можливості javascript (наприклад деструкцію або метод includes масивів)
Вказуємо target ESNext — це говорить компілятору що ми хочемо отримати код який юзає реалізовані фішки ES стандарту. Якщо вам треба в Safari або старі браузери — вкажіть тут що-небудь простіше.

Інші параметри — як спеції за смаком. Формат модулів, вирізати коменти.
Особливу увагу зверніть на noImplicitAny — не дозволяти використовувати за умовчанням any — треба б додати репресій до режиму линтера, але ми цього не робимо. Так, це заноза в дупі, в кінці статті я поясню чому так, а через одну статтю розповім як її виймати.
До речі про линтере — його конфіг я розглядати не буду — дивіться самі, до теми він не відноситься. Але взагалі вважаю хорошою практикою влаштувати диктат линтера, так що би крок у бік — червона рядок і провал складання. Але це на смак.
Допитливі юнаки звернули увагу на відсутність тайпингов — абсолютно вірно, нам вони не потрібні, але з'являться в подальшому (в майбутніх статтях) — коли ми почнемо юзати сторонні либы і будувати бек (так, так, тому у нас сорцы в директорії client, тому що потім буде і server)

А з конфіг на цьому все. Приступимо до коду.

Як я вже говорив на початку статті, по ходу статті ми створимо і зарегаем свій кастомный елемент і виведемо його на сторінці. Зайдемо з кінця — подивимося що ми хочемо отримати:

src/assets/index.html
<!doctype html>
<html>
<head>
<title>jsx light</title>
<meta charset="utf-8"/>
</head>
<body>
<script src="elements.js"></script>
<x-hello-world></x-hello-world>
</body>
</html>


Хе-хе. Так, старий добрий хелловорд. Тобто планується опис елемента і його реєстрацію отримати в elements.js і поспостерігати його поява у верстці у браузері.
Ну ок.
В конфіги вебпэка ми вказали що збирати elements.js ми будемо з src/client/index.ts. Але якщо ви поглянете у файл то побачите що там нічого цікавого, тому давайте спочатку заглянемо в реалізацію кастомного елемента, розташовану у файлі:

src/client/elements/helloword/index.tsx
declare var customElements;

import {dom} from '../inc/dom';
import {WAElement} from '../inc/';

export const HelloWorld = function(dom: any){
return class HelloWorld extends WAElement {

constructor(){
super();
}

connectedCallback() {
this.appendChild(
<div>hello, world!</div> 
);
}

static register() {
customElements.define('x-hello-world', HelloWorld);
}

};
}(dom);


Ну що ж, розглянемо детальніше.
Першим рядком ми оголошуємо глобал customElements, який надається згідно з першою версією стандарту (яка насправді друга версія бо першою версією була нульова). Стандарт ще не прийнятий тому цього глобал немає в тайпингах typescript, доводиться просто оголошувати.
Є ще інший шлях — можна підрихтувати надається typescript-му lib.d.ts (або соотв. обраним target), а саме тайпинг об'єкта document, покласти його локально в проект і підключати в своєму коді. У цьому випадку можна у коді вказувати document.customElements і радіти благ суворої типізації. Дочекатися моменту, коли стандарт візьмуть і просто видалити свій саморобний тайпинг, підправивши заодно один рядок у tsd.d.ts, залишивши весь інший код без змін. І це правильний шлях, принаймні в своєму експериментальному виявляли у своєму житті таку я зробив так. Але тут у нас стаття з закосом під туторіал, так що спрощую і роблю НЕПРАВИЛЬНО, але зрозуміло, з поясненням того, що це неправильно.

Далі йдуть два імпорту — dom і WAElement. Хто такі? dom — це якраз наш складальник dom (такий react для custom elements), те, що ми обговорювали при розгляді налаштувань компілятора typescript — jsxFactory. Її ми розглянемо окремо і докладно, зараз пробіжимося по WAElement.

Один з найбільш смачних моментів вебкомпонент v1 — можливість рівно успадковуватися від нативних класів елементів. Це дає нам можливість розширювати їх, не втрачаючи або не реалізовуючи повторно вже наявний у елементів функціонал. В кінці статті є посилання, в тому числі і на статтю де цей момент розглянуто детальніше. Правда воно поки ще толком не працює і нормально отнаследоваться вийшло тільки від HTMLElement, але я думаю ситуація виправиться.
Так ось. Уявімо собі як це буде. Ми пишемо свої розширення стандартних тегів, свій, унікальний функціонал — реалізуємо в своїх класах, стандартний функціонал вже реалізований в нативних елементах. І ось написавши пару десятків тегів ми починаємо помічати, що частина функціоналу у них спільна, але при цьому в нативних елементах відсутня. Логічно такий функціонал звести в один клас і надалі вже отнаследоваться від нього. WAElement — саме такий клас-прокладка між нашими класами і HTMLElement, причому поки що порожній, але нехай полежить.

Ok. Давайте пройдемося далі за кодом і потім повернемося до реалізації dom.
Новий тег в вебкомпонентах реєструється за допомогою виклику document.customElements.define(name, class), на вході очікується клас, що реалізує поведінку нового елемента. А значить повертати наш модуль повинен клас нашого нового тега. Як бачимо, саме це і відбувається, але досвідчений погляд побачить не прямий експорт класу а IIFE виклик. Навіщо? Як бачимо, при цьому виклику передається в скоп класу наш dom складальник. Справа в тому що в той момент коли лінтер дивиться наш код, виклику dom, який трансформується наша html верстка — ще немає. І імпорт dom без подальшого використання призведе до unused variable помилку. Цю справу можна припинити, давши директиву лінтеру в самому коді, але тоді можна проґавити справжні unused. Та й як то дратують директиви линтера в коді. Тому я імпортують dom і явно передаю його у скоп класу. А ось після того, як буде перетворена верстка — у коді класу з'являться виклики dom.createElement і пазл складеться.
Друга причина, по якій ви можете захотіти зробити так само — це використання декількох збирачів dom в одному модулі. Припустимо ви експортуєте з модуля кілька класів, і ці класи використовують два (або більше) угоди щодо будівництва dom (що це таке буде докладніше у наступній статті, а поки просто уявіть що хочете будувати dom за різними правилами). Так от, в налаштуваннях typescript ми можемо вказати тільки одне ім'я. Але використовувати можемо скільки нам заманеться конструкторів за ось такою схемою:

declare var customElements;

import {dom1} from '../inc/dom1';
import {dom2} from '../inc/dom2';
import {WAElement} from '../inc/';

export const HelloWorld = function(dom: any){
return class HelloWorld extends WAElement {

};
}(dom1);

export const GoodByeWorld = function(dom: any){
return class GoodByeWorld extends WAElement {

};
}(dom2);


Тобто тут все простенько — всередину класів передаються різні збирачі, в обох випадках всередині класу вони видні під ім'ям dom, верстка теж транспайлится на виклик dom але при цьому кожен клас використовує свій власний складальник.

Ок, цим начебто зрозуміло. Плавно підкралися до прологу кульмінації. Метод connectedCallback — власне те, заради чого все це дійство затівалося. Вставляємо свій шматок верстки у нутрощі новоспеченого тега, вказуючи верстку безпосередньо в коді. Власне що тут пояснювати — за кодом все зрозуміло. Тонкощі оголошення і життєвого циклу веб компонент у цій статті я пояснювати не буду, поки ви з нетерпінням чекаєте від мене продовження можете не поспішаючи ознайомитися зі статтями зі списку внизу.
Ну і останній момент — метод register. Навіщо він потрібен? Щоб елемент міг зареєструватися під тим ім'ям, на яке розраховував його творець і так ЯК розраховував його творець. Справа в тому, що при реєстрації можна вказати, які саме теги розширює наш елемент (реалізація поведінки через атрибут is — цікава тема, гідна окремої статті, внизу є посилання). Якщо ви з якихось причин дивіться на світ інакше — завжди можна зробити імпорт класу а елемент реєструвати самостійно під тим ім'ям, яке вам подобається більше.

Що ж, з цим теж, я сподіваюся, серйозних питань не виникне. Повернімося тепер, як і обіцяв, до збирача dom і розглянемо процес транспайлинга верстки jsx в js-код, що збирає DOM.
Повторю наведений вище приклад:
було:
<div id="test">
some text
<some_tag>
another text
</some_tag>
</div>

стало:
dom.createElement("div", { id: 'test'},
"some text", 
dom.createElement("some_tag", null, "another text")
)


Тобто нам треба збагнути функцію (або клас), збирає DOM. Я до цього пару раз згадував якісь правила складання — тримайте це в голові.
Подивимося, що ж у нас лежить в dom.ts.

src/client/elements/inc/dom.ts
const isStringLike = function(target: any){
return ['string', 'number', 'boolean'].includes(typeof target) || target instanceof String;
};

const setAttributes = function(target: HTMLElement, attrs: {[key:string] : any}){
if(!attrs){
return;
}
if('object' === typeof attrs){
for(i let in attrs){
switch(true){
case !attrs.hasOwnProperty(i):
continue;
case isStringLike(attrs[i]):
target.setAttribute(i, attrs[i]);
break;
default:
// do something strange here
}
}
}
};

export const dom = {
createElement: function (tagName: string, attrs: {[key:string] : any}, ...dom: any[]){

const res = document.createElement(tagName);
setAttributes(res, attrs);

for(i let of dom){
switch(true){
case isStringLike(i):
res.appendChild(document.createTextNode(i));
break;
case (i instanceof HTMLElement):
res.appendChild(i);
break;
default:
// do something strange here
}
}
return res;
}

};


Як бачимо, тут теж не rocket science, все простенько і зі смаком. Класом я оформляти не став, просто експортуємо об'єкт, що надає функцію createElement. Її роботу і розглянемо.
На вході ми отримуємо ім'я тега, його атрибути і нащадків (вже зібрані ноди). Оскільки змінне число аргументів — використовуємо в сигнатурі rest параметрнепогана стаття про деструкцію). Створюємо потрібний нам елемент через document.createElement і потім виставляємо йому атрибути.

Тут зверніть увагу на два моменти.

По перше ми можемо отримати запит на створення кастомного елемента (це відбудеться в ситуації коли ми у своєму jsx використовуємо інші кастомні елементи). Ніяк ми цю ситуацію не відстежуємо, незалежно від того — оголошений такий елемент чи ні — за стандартом кастомный елемент можна використовувати у верстці до його оголошення і реєстрації.

На другий момент багато падаваны вже напевно відреагували — це установка атрибутів. document.createElement дозволяє визначати атрибути при створенні елемента, так навіщо нам своя функція для цієї тривіальної операції? Хо-хо, панове. Це не тривіальна операція, це вишенька. Але якщо ми подивимося на реалізацію функції setAttributes, то побачимо що жодної вишеньки там немає, банально все що можна привести до рядку (скаляри та об'єкти String) — встановлюється в атрибут, все інше игнорится. Абсолютно вірно. Ми йдемо від простого до складного і в коді є тільки те що потрібно для реєстрації та виведення нашого нехитрого тега. Але це не означає, що ми проігноруємо потенціал, зовсім ні, ми його трошки оглянемо.

У поточній реалізації ми бачимо що працює тільки ось цей шматочок коду:

case isStringLike(attrs[i]):
target.setAttribute(i, attrs[i]);


Але уявіть собі на секунду що ми додали ось такий шматочок:

case isStringLike(attrs[i]):
target.setAttribute(i, attrs[i]);
break;
case i.substring(0, 2) === 'on' && 'function' === typeof attrs[i]:
let eventName = i.substring(2).toLowerCase();
if(eventName.length){
target.addEventListener. (eventName, attrs[i]);
}
break;

Здавалося б. Але зате тепер ми можемо зробити так:

@autobind
onClick(evt: Event) {
.
.
evt.preventDefault();
}

connectedCallback() {
this.appendChild(
<a href='/' onClick={this.onClick}>Click me!</a>
);
}


Ну як би сиханы реакта зараз звичайно скривилися в усмішці, давайте це виправимо. Додамо щось на зразок цього:

case isStringLike(attrs[i]):
target.setAttribute(i, attrs[i]);
break;
case attrs[i] instanceof Store:
attrs[i].bindAttribute(i, this);
break;


І після цього ми можемо робити легкий биндинг:

private url: new Store('http://some_default_url');

connectedCallback() {
this.appendChild(
<a href={this.url}>Click me!</a>
);
}


Тобто ми можемо робити односторонній, двосторонній биндинг, можемо особливим чином обробляти примірники окремих класів, так хоч засунути в атрибути DOM-об'єкт і потрібне нам чином за ним пильнувати. Те ж саме ми можемо зробити не тільки з атрибутами але і з вмістом тегу. При цьому ми не переймаємося за перемальовування — це робить за нас браузер. Не страждаємо з двома системами евентов і двома деревами атрибутів. Всі нативно, модно, молодіжно.

Пам'ятайте я говорив про правила побудови DOM? Це ось воно і є. Ви можете виробити зручні правила і реалізувати їх. Можете змінювати їх від проекту до проекту, або навіть від тега до тегу — якщо вам це полегшує життя і не вносить плутанину. Тобто дивлячись на реактив розумієш що це всього лише окремий випадок складальника DOM, і ви можете наштампувати таких собі по саме не треба. Але не зараз — це якраз тема наступної статті. А поки я її пишу а ви її чекаєте з нетерпінням — можна обговорити це нижче або пофантазувати на тему в якості домашнього завдання.

Ну і останній момент — зведемо це все в одне. Вище ми пропустили процес реєстрації тега, ось він:
src/client/index.tsx
import { HelloWorld } from './elements/helloworld/';
HelloWorld.register();


Тобто по мірі штампування тегів ви просто додаєте їх імпорти в цьому файлі і реєструєте. Типу ось цього:

import { Header } from './elements/header/';
import { Workplace } from './elements/workplace/';
import { HeaderMessage } from './elements/header_message/';
import { E } from './elements/email/';
import { ChangeButton } from './elements/change/';
import { CopyButton } from './elements/copy/';
import { MessageAboutMail } from './elements/message_about_mail/';
import { ReadMail } from './elements/read_mail/';
import { AdBanner } from './elements/ad_banner/';

[Header, HeaderMessage, Workplace, Email, ChangeButton, CopyButton, MessageAboutMail, ReadMail, AdBanner]
.forEach((element) => element.register());


Ось ніби і все за кодом.

Що ж, давайте ті ж вже зберемо проект, запустимо і подивимося.
git clone https://github.com/gonzazoid/jsx-light.git
cd jsx-light
npm install
npm run build

Має зібратися приблизно так:



Далі набиваємо:

webpack-dev-server --content-base dist/public/

Якщо webpack-dev-server не варто — піднімаємо npm i -g webpack-dev-server. Ну або скористайтеся тим інструментом, який найбільше відповідає вашим скріпленням.
Відкриваємо браузер, відкриваємо нашу збірку. Повинно бути щось на кшталт:



Як бачимо — наш тег має вміст, тобто все назбиралося і працює, як і очікувалося.

Для тих, хто все ще в сум'ятті коротенько пояснюю що тут сталося — ми заюзали jsx при описі webcomponent-и без реакта і бабеля. Власне все.

Так, а тепер повернемося до налаштування компілятора — noImplicitAny. Ми виставили її в false, дозволивши тим самим за замовчуванням приводити невизначені типи до any. Ну і нафіга тоді потрібен typescript запитає розчаровано читач і буде правий. Звичайно, там де вказані типи — перевірки здійснюються, але використовувати неявне приведення до any — злісне зло. Але в даному випадку ми (тимчасово) змушені це робити з наступної причини. Справа в тому що jsx поки що ніде не розглядається окремо як самостійна технологія, і де б він не з'явився — тінь реакта висить над ним. А процес транспайлинга верстки в js трохи більш складний, ніж я описав раніше. І етапів там теж трохи більше. В загальних рисах — теги, які реалізовані нативно (з точки зору react, тобто не є react компонентами) повинні бути вказані в JSX.IntrinsicElements — спеціальний глобал. І це не проблема насправді, достатньо вказати в коді щось на кшталт:

declare global {
namespace JSX {
interface IntrinsicElements extends ElementTagNameMap{ 
'and-some-custom-tag': WAElement; 
}
}
}

І ми маємо інтерфейс, отнаследованный від списку в описі DOM typescript-а, з правильним зазначенням типів. І ось тут то ми отримуємо удар ножем в спину! Ні бабель, ні typescript не припускають типи створених елементів можуть бути різними — вони хочуть що б ми вказали ОДИН тип у JSX.ElementClass. Для реакта це, очевидно React.Component — у цьому нескладно переконатися, вглянув у тайпинг реакта:
npm install @types/react

Так, у другому тайпскрипте тайпинги поширюються як npm модулі і це прикольна фішка (там ще є й про оголошення тайпингов у своєму модулі, друга версія непогано синтегрирована з npm). Загляньте в node_modules проекту після виконання команди і вивчіть вміст react/index.d.ts — конкретно інтерфейс JSX. Це дасть поверхневе розуміння процесу (не забудьте потім видалити цей тайпинг, інакше він буде заважати збірці).
Так от, навчити тайпскрипт збирати кастомні елементи з використанням jsx — пів-справи. А от навчити його збирати їх так, що б в подальшому працювала типізація — тов! Це тема окремої статті. Я зараз добиваю патч компілятора, сподіваюся довести цю справу до кінця і оформити статтею і можливо комітом. Бо як workflow обробки jsx трохи змінюється, а застосувати це можна далеко не тільки в збірці кастомних елементів.
Так от, повертаючись до noImplicitAny — якщо виставимо її в true — нарвемося на помилки компілятора, усунути які ми можемо оголошенням інтерфейсу JSX, де натикаємося на необхідність оголосити всі рідні і кастомні елементи одним класом — а це не айс. Тому поки залишаємо так.

Ну що ж, стаття вийшла коротенькій і навіть трохи поверхневої, тому ще трохи пройдуся по моментах, які залишилися за бортом:
* За бортом залишилося безліч моментів роботи кастомних елементів — аттрибут is, різниця між створенням елемента з верстки або в js-виклик, робота з вмістом кастомного елемента, видимість DOM і купа купа всього. Якісь моменти ми в майбутньому торкнемося, якісь ні, насправді статті в мережі є, поки трохи і досить поверхневі, але думаю у міру розвитку технології статті будуть з'являтися. Якщо вам треба ближче до нутрощів — на гітхабі є непогані обговорення.
* Практично не розкритий потенціал збирачів DOM — це тема наступної статті.
* Так само в наступній статті будуть схеми побудови кастомних елементів — event-based, state-based, дистрибуція створених універсальних компонент, інтернаціоналізація та інші незручності для програміста.
* Не розкритий і НЕ БУДЕ розкрито питання сумісності з браузерами — є полифилл, є caniuse, цієї теми я торкатися не буду. Весь цикл статей про те, як використовувати це в продакшине а про те ЯК і ДЕ воно МОЖЕ бути використано. Використовувати чи ні — власне для обговорення цієї статті і пишуться.

Загалом, як би все, вибачте що коротенько, такі статті будуть з більш глибоким зануренням, це була все таки вступна.

Гарного настрою і чистого коду!



Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.