Ну что, мальчиши и мальчишки, а не пора ли вам познать джесай до? Не, ну вам то рано еще, а я вот пожалуй начну. Но вы можете присоединиться.

В череде последних статей про 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(let i 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(let i 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 { Email } 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-вызове, работа с содержимым кастомного элемента, видимоcть DOM и куча куча всего. Какие то моменты мы в будущем затронем, какие то нет, на самом деле статьи в сети есть, пока немного и довольно поверхностные, но думаю по мере развития технологии статьи будут появляться. Если вам надо поближе к внутренностям — на гитхабе есть неплохие обсуждения.
* Практически не раскрыт потенциал сборщиков DOM — это тема следующей статьи.
* Так же в следующей статье будут схемы построения кастомных элементов — event-based, state-based, дистрибуция созданных универсальных компонент, интернационализация и прочие неудобства для программиста.
* Не раскрыт и НЕ БУДЕТ раскрыт вопрос совместимости с браузерами — есть полифилл, есть caniuse, этой темы я касаться не буду. Весь цикл статей не о том как использовать это в продакшине а о том КАК и ГДE оно МОЖЕТ быть использовано. Использовать или нет — собственно для обсуждения этого статьи и пишутся.

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

Хорошего настроения и чистого кода!



Поделиться с друзьями
-->

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


  1. serginho
    30.12.2016 00:17
    +3

    Простите за глупый вопрос, но зачем внутри TS писать HTML? Это же по сути скрещение логики и представления. Почему не выносить все в отдельный HTML шаблон и загружать его как зависимость?


    1. Veikedo
      30.12.2016 11:01
      +2

      реакт это только view. По-хорошему, никакой логики, не касающейся представления вашего компонента, там быть не должно.


      1. gonzazoid
        30.12.2016 11:15
        +1

        Да, надо все таки, как выше отметили, разделять бизнес логику и логику представления. Логику представления вполне можно смешивать с самим представлением, в чем проблема? MVC это не нарушает, и это не учитывая того факта что MVC не единственный и не всегда самый правильный паттерн. Программить надо так, что бы было удобно — писать, читать, сопровождать. В следующей статье будут интерактивные плюшки (если мне к тому времени не сольют карму) — может тогда преимущества этого подхода станут более очевидны. А вообще на хабре недавно были статьи на тему jsx — там это подробно обсуждалось.

        Упс, ответ предназначался serginho


  1. abby
    30.12.2016 01:36
    +1

    Спасибо за статью, кажется жизнь в мире JS/HTML налаживается.

    Каждый раз когда приходится иметь дело с проектом на JS испытываю много всяких эмоций. К примеру, всегда возникает вопрос: «сколько людей смогут запустить и внести изменения в современный проект через 3-4 года в его текущем состоянии?» В конце концов, был бы код — разобраться можно, но никто же не гарантирует доступность пакетов текущих версий.

    Тут и хабр как бы намекает в колонке справа


    1. gonzazoid
      30.12.2016 13:01

      >Спасибо за статью, кажется жизнь в мире JS/HTML налаживается.

      Всегда пожалуйста! Да, js сильно возмужал, а используя typescript и метапрограммирование (например mustache шаблоны) — можно делать интересные вещи. В следующих статьях об этом будет. Не скоро конечно, на статью уходит пара-тройка недель (работать тоже иногда надо), то тем не менее…


  1. eshimischi
    30.12.2016 11:03

    Уже пора использовать webpack2, чтобы потом меньше заниматься адаптацией под него, сразу сделать и забыть, все рецепты тут http://javascriptplayground.com/blog/2016/10/moving-to-webpack-2/


    1. gonzazoid
      30.12.2016 13:18

      Когда нибудь (это совсем не скоро) будет статья о том как мы выбрасываем webpack. Если коротенько, то суть в чем.
      Вот это:

      <!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>
      

      что по Вашему? Рискну предположить что Вы решите предположить что это html верстка холдера некого SPA. А теперь немного изменим:

      <html v="5">
        <head>
          <title>jsx light</title>
          <meta charset="utf-8"/>
        </head>
        <body>
          <script src="elements/index.ts"></script>
          <x-hello-world></x-hello-world>
        </body>
      </html>
      

      Казалось бы. Но теперь я Вам заявляю что это jsx. Да! И мы можем прогнать его по той же самой схеме, которая описана в статье и получить на выходе один js файл. Но подставив особый билдер DOM (который обычные теги пропускает а на тег script — собирает указанный typescript файл), получим код, который DOM вообще не строит. Он строит нам приложение. То есть на выходе получаем код для ноды, запустив который мы получаем собранное приложение.

      И тогда мы можем делать например так:

      <app>
          <api spec="swagger.json" back-engine="express" back="server.js" client="api.js" db-engine="pgsql" db="sql.js" />
         <typescript src="path" output="path.js options="правила сборки" />
         <css бла бла />
      </app>
      


      или что то вроде этого — и писать приложение сразу с его сборщиком (используя уже написанные компоненты, конечно же) Да! Старый добрый XML возрождается из пепла! Тут главное понять что JSX в этом случае работает не на DOM а на ноду. И тогда горизонты развернутся так что могут по пути порвать пару шаблонов ) В общем webpack не нужен, но потом.


      1. eshimischi
        31.12.2016 17:37

        Пока этот инструмент никто никуда не выбрасывал, а ваши заключения могут быть лишь гипотетикой на дворе 2017 года.


        1. gonzazoid
          31.12.2016 19:42

          Да, конечно, я же не навязываю.


  1. Fen1kz
    30.12.2016 17:06
    +1

    Лучше бы вместо пространных рассуждений про мальчиков и сопли автор больше времени посвятил вебпаку.


    Далее видим минимально необходимый для удобства набор плагинов, на самом деле они сейчас по большому счету и не нужны, но пусть будут.

    Это туториал, так они нужны или нет?


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


    1. gonzazoid
      30.12.2016 17:32
      +1

      так то статья не про вебпэк. Совсем.


      1. Fen1kz
        30.12.2016 17:35

        Сегодня мы будем строить webpack

        Ах, вот оно что, действительно..


        1. gonzazoid
          30.12.2016 17:42
          +2

          Так построили же, изучать с азов я не обещал:

          >Статей по его настройке в сети — море, я пройдусь только по значимым.

          Но если Вам прямо жмет — вот тут неплохой скринкаст, честно говоря просто не вижу смысла в очередной статье, копирующей документацию. А эта, да и последующие статьи — они про то что в документации не написано. И скорее для тех, кто документацию освоил.


  1. raidhon
    30.12.2016 18:44
    +3

    <<мальчиши и мальчишки>>
    <<Многие из вас сегодня повзрослеют, а некоторые возможно даже станут мужчинами>>

    Простите за откровенность, но чуть не сблевал на эту оду самолюбования с минимумом полезной информации.
    Надеюсь продолжения не будет!


    1. gonzazoid
      30.12.2016 19:09
      -1

      >Простите за откровенность,

      Право, не стоит извинений. Эта ода может смутить только юных нарциссов, ну да ладно. Вам то полегчало?

      >Надеюсь продолжения не будет!

      Да просто не читайте, в чем проблема?


      1. raidhon
        30.12.2016 19:46
        +1

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

        >Да просто не читайте, в чем проблема?
        Так и поступлю.


        1. gonzazoid
          30.12.2016 20:07
          +1

          >Прошу извинить!

          Без проблем, принято!


  1. gonzazoid
    30.12.2016 19:09

    -


  1. BoryaMogila
    03.01.2017 16:12

    -