image
Эта статья о том, как написать универсальный JavaScript-компонент, который можно будет использовать


  • как React-компонент;
  • как Preact-компонент;
  • как Angular-компонент;
  • как Web Component;
  • как jQuery функцию для рендеринга в DOMElement;
  • как нативную функцию для рендеринга в DOMElement.

Зачем и кому это нужно


Мир JavaScript-разработки очень фрагментирован. Есть десятки популярных фреймворков, большая часть из которых абсолютно несовместима друг с другом. В таких условиях разработчики JavaScript-компонентов и библиотек, выбирая один конкретный фреймворк, автоматически отказываются от очень большой аудитории, которая данный фреймворк не использует. Это серьезная проблема, и в статье предложено ее решение.


Как все будет реализовано


  1. Напишем React-компонент.
  2. Используя JavaScript-библиотеки preact и preact-compat, которые вместе работают точно так же как React и при этом весят жалкие 20 килобайт, напишем обертки для всего остального.
  3. Настроим сборку с помощью Webpack-а.

Пишем код компонента


Для примера разработаем Donut Chart такого вида:


Donut Chart


Здесь ничего удивительного мы не увидим — просто код.


import React from 'react';

export default class DonutChart extends React.Component {
    render() {
        const { radius, holeSize, text, value, total, backgroundColor, valueColor } = this.props;
        const r = radius * (1 - (1 - holeSize)/2);
        const width = radius * (1 - holeSize);
        const circumference = 2 * Math.PI * r;
        const strokeDasharray = ((value * circumference) / total) + ' ' + circumference;
        const transform = 'rotate(-90 ' + radius + ',' + radius + ')';
        const fontSize = r * holeSize * 0.6;
        return (
            <div style = {{ textAlign: 'center', fontFamily: 'sans-serif' }}>
                <svg width = {radius * 2 + 'px'} height = {radius * 2 + 'px'}>
                    <circle 
                        r = {r + 'px'} cx = {radius + 'px'} cy = {radius + 'px'}
                        transform = {transform} fill = 'none'
                        stroke = {backgroundColor} strokeWidth = {width}
                    />
                    <circle
                        r = {r + 'px'} cx = {radius + 'px'} cy = {radius + 'px'}
                        transform = {transform} fill = 'none'
                        stroke = {valueColor} strokeWidth = {width}
                        strokeDasharray = {strokeDasharray}
                    />
                    <text
                        x = {radius + 'px'} y = {radius + 'px' }dy = {fontSize/3 + 'px'}
                        textAnchor = 'middle' fill = {valueColor} fontSize = {fontSize + 'px'}
                    >
                        {~~(value * 1000 / total) / 10}%
                    </text>
                </svg>
                <div style = {{ marginTop: '10px' }}>
                    {text}
                </div>
            </div>
        );
    }
}

DonutChart.defaultProps = {
    holeSize : 0.8,
    radius : 65,
    backgroundColor : '#d1d8e7',
    valueColor : '#49649f'
};

Что должно получиться в итоге


Codepen Collection


Настраиваем сборку Webpack-ом


Базовый Webpack-конфиг
var webpack = require('webpack');

module.exports = {
    output: {
        path: './dist'
    },
    resolve: {
        extensions: ['', '.js'],
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    presets: [
                        'latest',
                        'stage-0',
                        'react'
                    ],
                    plugins: [
                        'transform-react-remove-prop-types',
                        'transform-react-constant-elements'
                    ]
                }
            }
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': "'production'"
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.AggressiveMergingPlugin(),
        new webpack.optimize.UglifyJsPlugin({
            compress: { warnings: false },
            comments: false,
            sourceMap: true,
            mangle: true,
            minimize: true
        })
    ]
};

Добавляем в package.json скрипты для сборки проекта

"scripts": {
    "build:preact": "node ./scripts/build-as-preact-component.js",
    "build:react": "node ./scripts/build-as-react-component.js",
    "build:webcomponent": "node ./scripts/build-as-web-component.js",
    "build:vanila": "node ./scripts/build-as-vanila-component.js",
    "build:jquery": "node ./scripts/build-as-jquery-component",
    "build:angular": "node ./scripts/build-as-angular-component",
    "build": "npm run build:preact && npm run build:react && npm run build:webcomponent && npm run build:vanila && npm run build:jquery && npm run build:angular"
  }

Сборка Webpack-ом и обертка для Web Components


Модификация базового Webpack-конфига и сборка
var webpack = require('webpack');
var config = require('./webpack.config');
var statsConfig = require('./statsConfig');

config.resolve.alias = {
    'react': 'preact-compat',
    'react-dom': 'preact-compat'
};
config.entry = './src/DonutChartWebComponent.js';
config.output.filename = 'DonutChartWebComponent.js';

webpack(config).run(function (err, stats) {
    console.log(stats.toString(statsConfig));
});

Обертка


import React from 'react';
import ReactDOM from 'react-dom';
import DonutChart from './DonutChart';

const proto = Object.create(HTMLElement.prototype, {
    attachedCallback: {
        value: function() {
            const mountPoint = document.createElement('span');
            this.createShadowRoot().appendChild(mountPoint);
            const props = {
                radius          : +this.getAttribute('radius') || undefined,
                holeSize        : +this.getAttribute('hole-size') || undefined,
                text            : this.getAttribute('text') || undefined,
                value           : +this.getAttribute('value') || undefined,
                total           : +this.getAttribute('total') || undefined,
                backgroundColor : this.getAttribute('background-color') || undefined,
                valueColor      : this.getAttribute('value-color') || undefined
            };
            ReactDOM.render((
                <DonutChart {...props}/>
            ), mountPoint);
        }
    }
});
document.registerElement('donut-chart', {prototype: proto});

Пример использования


<donut-chart value="39.6" total="100" text="Hello Web Components"></donut-chart>

Результат



Сборка Webpack-ом и обертка для Angular


Модификация базового Webpack-конфига и сборка
var webpack = require('webpack');
var config = require('./webpack.config');
var statsConfig = require('./statsConfig');

config.resolve.alias = {
    'react': 'preact-compat',
    'react-dom': 'preact-compat'
};
config.entry = './src/DonutChartAngularComponent.js';
config.output.filename = 'DonutChartAngularComponent.js';
config.output.library = 'DonutChart';
config.output.libraryTarget = 'umd';

webpack(config).run(function (err, stats) {
    console.log(stats.toString(statsConfig));
});

Обертка


import React from 'react';
import ReactDOM from 'react-dom';
import DonutChart from './DonutChart';

const module = angular.module('future-charts-example', []);

module.directive('donutChart', function() {
    return {
        restrict: 'E',
        link: function(scope, element, attrs) {
            const props = {
                radius          : +attrs['radius'] || undefined,
                holeSize        : +attrs['hole-size'] || undefined,
                text            : attrs['text'] || undefined,
                value           : +attrs['value'] || undefined,
                total           : +attrs['total'] || undefined,
                backgroundColor : attrs['background-color'] || undefined,
                valueColor      : attrs['value-color'] || undefined
            };
            ReactDOM.render((
                <DonutChart {...props}/>
            ), element[0]);
        }
    };
});

Пример использования


<body ng-app="future-charts-example">
    <donut-chart value="89.6" total="100" text="Hello Angular"></donut-chart>
</body>

Результат



Сборка Webpack-ом и обертка для jQuery


Модификация базового Webpack-конфига и сборка
var webpack = require('webpack');
var config = require('./webpack.config');
var statsConfig = require('./statsConfig');

config.resolve.alias = {
    'react': 'preact-compat',
    'react-dom': 'preact-compat'
};
config.entry = './src/DonutChartJQueryComponent.js';
config.output.filename = 'DonutChartJQueryComponent.js';
config.output.library = 'DonutChart';
config.output.libraryTarget = 'umd';

webpack(config).run(function (err, stats) {
    console.log(stats.toString(statsConfig));
});

Обертка


import React from 'react';
import ReactDOM from 'react-dom';
import DonutChart from './DonutChart';

jQuery.fn.extend({
    DonutChart: function(props) {
        this.each(
            function () {
                ReactDOM.render((
                    <DonutChart {...props}/>
                ), this);
            }
        );
    }
});

Пример использования


$('#app').DonutChart({
    value : 42.1,
    total : 100,
    text : 'Hello jQuery'
});

Результат



Сборка Webpack-ом и обертка для VanilaJS (использование из нативной функции)


Модификация базового Webpack-конфига и сборка
var webpack = require('webpack');
var config = require('./webpack.config');
var statsConfig = require('./statsConfig');

config.resolve.alias = {
    'react': 'preact-compat',
    'react-dom': 'preact-compat'
};
config.entry = './src/DonutChartVanilaComponent.js';
config.output.filename = 'DonutChartVanilaComponent.js';
config.output.library = 'DonutChart';
config.output.libraryTarget = 'umd';

webpack(config).run(function (err, stats) {
    console.log(stats.toString(statsConfig));
});

Обертка


import React from 'react';
import ReactDOM from 'react-dom';
import DonutChart from './DonutChart';

module.exports = function DonutChartVanilaComponent(mountPoint, props) {
    ReactDOM.render((
        <DonutChart {...props}/>
    ), mountPoint);
};

Пример использования


DonutChart(document.getElementById('app'), {
    value : 57.4,
    total : 100,
    text : 'Hello Vanila'
});

Результат



Сборка Webpack-ом для React


Модификация базового Webpack-конфига и сборка
var webpack = require('webpack');
var config = require('./webpack.config');
var statsConfig = require('./statsConfig');

var react = {
    root: 'React',
    commonjs2: 'react',
    commonjs: 'react'
};

var reactDom = {
    root: 'ReactDOM',
    commonjs2: 'react-dom',
    commonjs: 'react-dom'
};

config.externals = {
    'react': react,
    'react-dom': reactDom
};
config.entry = './src/DonutChartUMD.js';
config.output.filename = 'DonutChartReact.js';
config.output.library = 'DonutChart';
config.output.libraryTarget = 'umd';

webpack(config).run(function (err, stats) {
    console.log(stats.toString(statsConfig));
});

Результат



Сборка Webpack-ом для Preact


Модификация базового Webpack-конфига и сборка
var webpack = require('webpack');
var config = require('./webpack.config');
var statsConfig = require('./statsConfig');

var preactCompat = {
    root: 'preactCompat',
    commonjs2: 'preact-compat',
    commonjs: 'preact-compat'
};

config.externals = {
    'react': preactCompat,
    'react-dom': preactCompat
};
config.entry = './src/DonutChartUMD.js';
config.output.filename = 'DonutChartPreact.js';
config.output.library = 'DonutChart';
config.output.libraryTarget = 'umd';

webpack(config).run(function (err, stats) {
    console.log(stats.toString(statsConfig));
});

Результат



Заключение


Сколько в итоге будет весить каждый из вариантов:


React Preact VanilaJS jQuery Angular Web Components
Код компонента (3кб) Код компонента (3кб) Код компонента (3кб) Код компонента (3кб) Код компонента (3кб) Код компонента (3кб)
Обертка (1кб) Обертка(1кб) Обертка(1кб) Обертка (1кб)
preact.min.js (3кб) preact.min.js (3кб) preact.min.js (3кб) preact.min.js (3кб)
preact-compat.min.js (18кб) preact-compat.min.js (18кб) preact-compat.min.js (18кб) preact-compat.min.js (18кб)
3кб 3кб 25кб 25кб 25кб 25кб

Оверхед в 20 килобайт за возможность использовать React-компоненты в любых других фреймворках или в качестве Web Components — это прекрасный результат. Если вы разрабатываете какие-то React-компоненты, знайте — вы можете сделать их доступными всем и каждому — это очень просто. Надеюсь, что этот туториал поможет сделать мир хотя бы чуточку лучше и сократит страшную фрагментацию вселенной JavaScript-разработки.


Исходники: Github, Codepen, NPM

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

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


  1. webmasterx
    05.12.2016 08:41

    А что насчет биндинга в ангуляре?


  1. jbubsk
    05.12.2016 08:53
    +5

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


    1. MrCheater
      05.12.2016 09:26
      +6

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


  1. nazarpc
    05.12.2016 09:49
    +1

    Мне всегда казалось что использование Web Components единственное решение, которое на 100% совместимо с чем угодно другим. Зачем ещё кучу всего сверху?


    1. MrCheater
      05.12.2016 10:10
      +8

      На данный момент нативно Web Components очень мало где поддерживается
      custom-elements


      C полифилами ситуация лучше


      With the polyfills
      Но полифилы отжирают ~120 кб.


      И опять же — в Web Components — внутри все DOM-операции и отслеживание изменений состояния компонента нужно делать ручками, что не так удобно, как в React. Уровень абстракции другой


      1. nazarpc
        05.12.2016 10:18

        React нативно вообще нигде не поддерживается и не будет.


        Уровень абстракции не хуже. При использовании Polymer есть и биндинги, и возможности рендерить кучу элементов из массива по шаблону, включая реакцию на изменение состояния (в новом проекте я использую Redux вместе с веб-компонентами), и декларативные подписки на события, и многое другое.


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


        В противовес попробуйте интегрировать один React компонент в не-React проект — у вас и оверхед React будет похуже оверхеда полифиллов + Polymer, да и без трехэтажной системы сборки не обойтись. А веб-компоненты просто работают. В совершенно любом проекте, так же как в любом проекте работает <div>.


        1. MrCheater
          05.12.2016 10:23
          +2

          В противовес попробуйте интегрировать один React компонент в не-React проект — у вас и оверхед React будет похуже оверхеда полифиллов + Polymer

          В статье я показал, как добиться оверхеда в 20кб.


          1. nazarpc
            05.12.2016 10:27
            +3

            x-tags, к примеру, тоже 20 КБ, а в браузерах что поддерживают нативно будет вообще 0 (как не крути, таких подавляющее большинство).
            При этом будет быстрее и проще, ибо используются нативные интерфейсы.


          1. RealFLYNN
            05.12.2016 16:29

            Присоединяюсь к предыдущему комментатору. Polymer 2.0 больше не является фреймворком — теперь это библиотека с сахаром для нативных компонентов + привязка данных. Полимер, таким образом, можно вывести в зависимость через html imports — сколько бы не было на странице компонентов, ссылающихся на него, свои N килобайт он загрузит только один раз.

            В описанном способе (насколько я понял) технологический оверхед зашит в каждый компонент, так что 8 компонентов на одной странице по 25 килобайт каждый — это уже солидный жирок. Поправьте, если я ошибаюсь


            1. MrCheater
              05.12.2016 16:43
              +3

              так что 8 компонентов на одной странице по 25 килобайт каждый

              Я предполагаю, что разработчик компонентов даст вам эти 8 компонентов, собранных в одну либу вместе с одним preact-ом.


              @salex772 предложил вариант получше https://habrahabr.ru/company/devexpress/blog/316358/?reply_to=9946908#comment_9946898
              Я думаю, это вполне реально реализовать


              1. RealFLYNN
                05.12.2016 16:51
                +2

                Я предполагаю, что разработчик компонентов даст вам эти 8 компонентов, собранных в одну либу вместе с одним preact-ом.

                Это, разумеется, если будет возможность собрать все нужные компоненты в одном месте для такой сборки. В данный момент работаю над проектом, для которого жизненно необходим импорт сторонних компонентов по кросс-домейну вместе с компонентами-зависимостями.

                Я думаю, это вполне реально реализовать

                Вот тут, да, хотя все равно выглядит сложнее нативного компонента + html imports.


        1. justboris
          05.12.2016 14:00
          +2

          React нативно вообще нигде не поддерживается и не будет.
          При использовании Polymer

          Изначально речь шла о нативных веб-компонентах. Polymer — это тоже фреймворк и тоже поставляется отдельно от браузера, а значит, сколько-то весит.


          Дальше — выбор, на каком фреймворке строить внутренности компонента, это уже дело личных предпочтений каждого, и React (Preact) тут тоже хороший вариант


        1. salex772
          05.12.2016 16:17

          В чем конкретно будет оверхед? В условной загрузке 20кб? Это только первый раз, после этого все это будет в кэше броузера.


      1. SPAHI4
        05.12.2016 12:02

        Только полифилы можно загружать по надобности.


  1. kxl
    05.12.2016 10:09
    +1

    Оверхед в 20 килобайт......- это прекрасный результат.

    нда, на моем первом компьютере 48К было, из них 8 — на видео память и сиcтемные переменные уходило…
    эх…


  1. shanhaichik
    05.12.2016 10:21
    +5

    Как говорится смешивать, но не взбалтывать.
    Из своего опыта знаю, что лучше наоборот писать компоненты или какие-то не большие библиотеки на VanilaJS. А для фреймворков писать уже адапторы, если это необходимо.

    Хотя ваша мысль мне то де нравится, но как и с любыми решениями такого рода, наверняка есть свои подводные камни.


  1. DarthVictor
    05.12.2016 11:33
    +3

    Хороший способ, но хотелось бы подробностей про дополнительный вес оберток и его масштабирование. Во сколько килобайт превратится оверхед в 22КБ на один компонент скажем для 10, 30 или 100 компонент? Какая часть этих 22КБ может быть общая? Просто если там общих 21КБ из 22КБ, то это одно, а если 12КБ, то другое.


    1. MrCheater
      05.12.2016 11:44

      Я думаю, что создание оберток можно автоматизировать. Например генерить их налету из propTypes


      Пример PropTypes

      DonutChart.propTypes = {
      radius: React.PropTypes.number.isRequired,
      holeSize: React.PropTypes.number.isRequired, //0...1
      text: React.PropTypes.string.isRequired,
      value: React.PropTypes.number.isRequired,
      total: React.PropTypes.number.isRequired,
      backgroundColor: React.PropTypes.string.isRequired,
      valueColor: React.PropTypes.string.isRequired
      };


  1. salex772
    05.12.2016 12:20

    Я так думаю, что неплохо было вынести preact в externals и подключать динамически через scriptJs в зависимости от того, есть ли внешние библиотеки в window. Не обязательно для каждого компонента тянуть свой реакт.


    1. MrCheater
      05.12.2016 12:21

      Про дублирование Preact-а на каждый компонент речи не идёт. Если 100 компонентов в сборке будет — то получится 100 компонентов + один Preact


      1. salex772
        05.12.2016 14:26

        А если один, то webpack включит его в сборку, при этом на странице он уже может быть в составе другого компонента вне вашей сборки… Я уверен, preact или react надо делать внешним компонентом и тянуть его опционально, если его там нет.


        1. MrCheater
          05.12.2016 14:30

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


          1. salex772
            05.12.2016 16:21
            +1

            Ну я бы сделал через сторонний загрузчик — это первое что должно исполнятся внутри UMD модуля. Например scriptjs, Он проверит зависимости, скачает что надо пл CDN, вернет промис, на resolve() которого нужно повесить инициализацию реакта. К моменту инициализации компонента все ссылки уже должны быть удовлетворены.


          1. duodvk
            05.12.2016 18:35
            +3

            А чем плох webpack конфиг externals. Можно задать его как-то так (на примере react):


            externals: {
                'react': {
                    root: 'React',
                    commonjs2: 'react',
                    commonjs: 'react',
                    amd: 'react'
                }
            }

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


            1. MrCheater
              05.12.2016 18:38

              Лично мне бы не хотелось заставлять программиста, который использует мою библиотеку, подключать какие-то react/preact-ы. Ибо это детали реализации уже.
              Но предложенный вами вариант тоже имеет место быть. Многим он подойдет


              1. duodvk
                05.12.2016 18:41

                Я понимаю, но тогда возникнет проблема с размером при использовании множества таких компонентов.


            1. salex772
              10.12.2016 02:15

              Externals просто говорит о том, что это все «в другом месте». А речь про то, как это все подключить помимо сборки, я и предложил использовать загрузчики типа LABjs или ScriptJS


  1. nsinreal
    05.12.2016 13:03

    А что с управлением состоянием? Просто все очень хорошо с реюзом пока у компонентов не появится внутреннего состояния, а вам не понадобится history management.


    1. MrCheater
      05.12.2016 13:35

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


      1. salex772
        05.12.2016 14:29

        на каком-нибудь легаси сайте на каждой странице компонент будет уничтожаться и создаваться каждый раз, ИМХО при обновлении state нужна сериализация каждый раз.


        1. MrCheater
          05.12.2016 14:37

          Компонент можно написать так, чтобы он поддерживал помимо uncontrolled режима, в котором state внутри создается, еще и controlled режим, при котором state не нужен. В controlled режиме компонент будет дергать колбеки, они будут менять state и отдавать его обратно в props (в аргументы). Так что никакой сериализации. Просто данные можно хранить вне компонента, если нужно


          1. salex772
            05.12.2016 16:23
            +1

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


  1. novoxudonoser
    05.12.2016 15:20

    Я правильно понимаю что при таком коде

    <svg width = {radius * 2 + 'px'} height = {radius * 2 + 'px'}>
    у вас вся шаблонизация будет проходить на клиенте?


    1. Punk_UnDeaD
      06.12.2016 23:13

      При желании вы можете делать всю или часть шаблонизации на клиенте.
      Что именно вас интересует?


  1. vintage
    07.12.2016 09:09
    +1

    Для таких кроссплатформенных компонент лучше использовать какой-нибудь микромодульный фреймворк, который бы в комплексте с компонентом поставлял лишь минимально необходимый набор микромодулей, а не всю библиотеку целиком.


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


    1. MrCheater
      07.12.2016 11:34

      В Web Components огромные заморочки с настоящим CSS, поэтому я его не использовал. Но всегда можно взять что-то из аттрибутов(props) и на основе этого выставить style у нужных элементов. Внутри Shadow DOM там как-то не так CSS работает, я мало с этим работал — особо не подскажу.


      Насчет микромодулей/микрофреймворка — для меня важна совместимость с React, так что это не мой путь. И я готов заплатить за него 20 лишними килобайтами


      1. vintage
        07.12.2016 14:54

        Почему вам так важен React?


        1. MrCheater
          07.12.2016 15:02

          React меня устраивает больше, чем любой другой фреймворк для UI из тех, что я пробовал. Но это просто дело вкуса.


          1. vintage
            07.12.2016 15:55

            А какие фреймворки вы пробовали?


            1. MrCheater
              07.12.2016 16:14

              То, что пробовал для написания UI — jquery, ember, angular (чутка), react и еще пару велосипедных. Не так уж и много — но с react-ом я ощущаю наименьшее количество боли при разработке


              1. vintage
                07.12.2016 17:01

                Но всё же некоторая боль есть? В чём она заключается?


    1. duodvk
      08.12.2016 13:40

      А можно какой-нибудь пример такого фреймворка? Просто 20 килобайт — это и так вроде не много, но если можно ещё меньше, то почему нет.


      1. MrCheater
        08.12.2016 13:58

        Пример — preact весит всего 3кб. Умеет почти всё тоже самое, что и React, но нет Context-а. Если нужен Context, то подойдёт preact + preact-compat


        1. vintage
          08.12.2016 15:00

          Это же просто хитрый шаблонизатор, а не фреймворк.


      1. vintage
        08.12.2016 14:03

        Можно, только осторожно.


        Я попробовал собрать donut, но там получается всё вместе в минифициованном виде 20 кб, что не сильно лучше варианта с преактом. Но тут и компонент очень простой без особых зависимостей.