Billing Frontend Ecosystem

Что умеет SASS

чего не умеет CSS

// theme
$themeMainColor: #8e3b4b;
$themeMainTextColor: #a52f47;

// text
$textBaseColor: #404040;
$textWhiteColor: #fff;
$textDisabledColor: #b7b7b7;
$textDarkColor: #8c8c8c;
$textErrorColor: #d92b2b;
$textSuccessColor: #00b359;

$transitionBaseTime: .2s;
$transitionBaseTimingFunction: ease-in;
$transitionSmoothOut: .4s cubic-bezier(.23, 1, .32, 1);

$fontSizeSmall: 12px;
$fontSizeRegular: 14px;
$fontSizeBig: 16px;
$fontSizeGreat: 18px;
$fontSizeTextDotsActions: 30px;

$lineHeightSmall: 1.2;
$lineHeightRegular: 1.4;

Что умеет SASS

чего не умеет CSS

.element {
    &.is-active {}
    &.is-disabled {}

    .sub-element {}
}

/* css */
.element {}
.element.is-active {}
.element.is-diabled {}
.element .sub-element {}

Что умеет SASS

чего не умеет CSS

%as-link {
    background: none;

    color: $linkBaseColor;

    cursor: pointer;

    &:hover,
    &:focus {
        outline: none;
        box-shadow: none;

        color: $linkActiveColor;
    }
}

/* usage */
@extend %as-link;

Что умеет SASS

чего не умеет CSS

@import "../mixins";

@mixin windowLessOrEqual($width: 1248px) {
    @media only screen and (max-width: $width) {
        @content;
    }
}

/* usage */
@include windowLessOrEqual(320px) {
    font-size: 16px;
};

Что умеет SASS

чего не умеет CSS

color: darken(#800, 20%); // => #200

Что умеет SASS

чего не умеет CSS

@for $i from 1 through $equalizerBarsCount {
    .equalizer__bar:nth-child(#{$i}) {
        animation-delay: $i * 10ms + random(200);
    }
}

P.S.: уникальность

Какой бывает JS

$(document).ready(function() {
    $('#b-form').filter_send();
    $('a.js-gallerys').fancybox();
    $('a.item_gallery_slider').fancybox();
    $('a.js-gallery').fancybox({
        'titlePosition': 'over'
    });

    // Метод проверки поля на пустоту, TRUE - если поле пустое
    $.validator.addMethod('empty', function(value) {
        return (value == '');
    });

    // Scroll to top
    $(window).scroll(function() {
        var t = $(document).scrollTop();
        var x = $('.l-wrapper').offset().top;
        if (t >= x) {
            $('#move-to-top').fadeIn();
        } else {
            $('#move-to-top').fadeOut();
        }
    });

    // Colorbox
    $('.b-colorbox').colorbox({
        title: function() {
            return $(this).parent().find('.b-album-images__desc').html();
        },
        maxHeight: "98%"
    });

    $('.b-colorbox--html').colorbox({
        inline: true,
        onOpen: function() {
            $("#colorbox").addClass("colorbox-inline-block");
        }
    });

    $('.main-colorbox').colorbox({
        rel: 'gallery',
        title: function() {
            return $(this).parent().find('.b-album-images__desc').html();
        },
        maxHeight: "98%"
    });

    $('.js-mainpage-filter__link').on('click', function(e) {
        e.preventDefault();
        $('.js-mainpage-filter').slideToggle('fast');
//        $('.js-mainpage-catalog').hide('fast');
    });

    $('.js-mainpage-catalog__link').on('click', function(e) {
        e.preventDefault();
        $('.js-mainpage-catalog').slideToggle('fast');
//        $('.js-mainpage-filter').hide('fast');
    });

    $('.js-items-on-page').on('change', function() {
        $(this).closest('.js-itemspage').submit();
    });

    wrap_content_images();
});

Base.JS

var ActionButtonControl = ButtonControlBase.extend({
        constructor: function(id, view) {
            this.base(id, view || new ActionButtonControl.View(id));
            this._href = null;

            this.onClick = Event.create();
            this.__attachEventListeners();
        },
        __attachEventListeners: function() {
            var _this = this;

            this._view.onClick(function(evt) {
                if (_this._isEnabled)
                    _this.onClick(evt);
                else
                    evt.preventDefault();
            });
        },
        getHref: function () {
            return this._href || (this._href = this._view.getHref());
        }
    }, {
        View: ButtonControlBase.View.extend({
            constructor: function (id, settings) {
                this.base(id, settings);

                this.onClick = Event.create();
                this.__attachEventListeners();
            },
            __attachEventListeners: function() {
                var _this = this;

                this._node.on("click.actionClick", function(evt) {
                    _this.onClick(evt);
                });
            },
            getHref: function () {
                return this._node.find("a").attr("href");
            }
        })
    });

EcmaScript 6 (aka 2015)

Тысячи изменений

  • let, const
  • function (a = 10) {/* ... */}
  • class
  • import, export modules
  • for ... of
  • () => {}
  • ...obj
  • function({a, b}) {/* ... */}
  • Promise
  • function*
  • async/await
  • Map, WeakMap, Set, WeakSet
  • Iterator protocol
  • и другие...

...

const user = {
   name: "Maybel",
   post: "Sister",
   clothes: "Sweater"
};

const { name, post } = user;

// same as
const name = user.name;
const post = user.post;

...

const user = {
   name: "Dipper",
   post: "Brother",
   clothes: "T-shirt"
};

const logger = ({ name, post }) => {
    console.log(post, name);
};

logger(user);

Pyramid of doom

someAsyncOp(function() {
    anotherAsyncOp(function(a) {
        andAnotherAsyncOp(function(b) {
            okayEnough(function(c) {
                complete(d);
            });
        });
    });
});

Promise

someAsyncOp()
    .then(a => { anotherAsyncOp(a) })
    .then(b => { andAnotherAsyncOp(b) })
    .then(c => { okayEnough(c) })
    .then(d => { complete(d) })
    .catch(e => { handleError(e) });

Generator

function* handle() {
    const a = yield someAsyncOp();
    const b = yield anotherAsyncOp(a);
    const c = yield andAnotherAsyncOp(b);
    const d = yield okayEnough(c);
    const e = yield complete(d);
}

async/await

async function handle() {
    const a = await someAsyncOp();
    const b = await anotherAsyncOp(a);
    const c = await andAnotherAsyncOp(b);
    const d = await okayEnough(c);
    const e = await complete(d);
}

Общая архитектура?

React

React

Компонент

React.DOM.h1(null, 'Hello');
<h1>Hello</h1>

React

Составляющие

// Element
<h1>Hello!</h1>

// Component
<LightboxHeader>Hello!</LightboxHeader>

React

props

Link.propTypes = {
    href: PropTypes.string,
    children: PropTypes.node,
    className: PropTypes.string,
    disabledClassName: PropTypes.string,
    disabled: PropTypes.bool
};

React

Жизненный цикл

// Mounting
componentWillMount()
componentDidMount()

shouldComponentUpdate()

// Updating
componentWillReceiveProps()
componentWillUpdate()
componentDidUpdate()

// Unmounting
componentWillUnmount()

React

state компонента

this.setState({
    isActive: true
});

React

Пример компонента

class TextArea extends Component {
    state = {
        height: null
    };

    constructor(props) {
        super(props);
        const { minHeight } = props;

        this.state = {
            height: minHeight
        };

        /*...*/
    }

    /*...*/

    componentDidMount() {
        this._changeTextAreaNodeHeight();
    }

    componentDidUpdate() {
        this._changeTextAreaNodeHeight();
    }

    /*...*/

    render() {
        const { height } = this.state;
        const textInputProps = { ...this.props };
        delete textInputProps.minHeight;
        delete textInputProps.inputClassName;

        return (
            <TextInput isTextArea={true}
                       inputClassName={cx(styles.textArea, this.props.inputClassName)}
                       height={height}
                       {...textInputProps}
                       onChange={this.handleChange}
                       ref={ (el) => { this._textArea = el }} />
        );
    }
}

TextArea.propTypes = {
    minHeight: PropTypes.number,
    onChange: PropTypes.func,
    /*...*/
    readonly: PropTypes.bool,
    disabled: PropTypes.bool,
    value: PropTypes.string,
    /*...*/
};

TextArea.defaultProps = {
    minHeight: 30
};

Flux

Реализации

  • flux
  • reflux
  • redux
  • alt
  • flummox
  • fluxible
  • fluxxor
  • marty.js
  • fynx
  • McFly
  • DeLorean.js
  • fluxify
  • MobX

Flux

Redux

Redux

Единый store

Redux

Actions, Action Creators

Redux

Reducers

Redux

Redux

Связь с React

  • <Provider>
  • connect()

Routing

Routing

Routing

<Route path="ExternRegistrationErrors" component={ExternRegistrationErrorsPage}>
     <IndexRoute component={AdminErrorsList} />
     <Route path="users" component={UsersErrorsList} />
     <Route path="permissions" component={PermissionsErrorsList} />

     <Redirect from="*" to="ExternRegistrationErrors" />
</Route>

Saga

Sagas

function* validateStepWatcher() {
    let task = null;
    const actionsToWatch = [
        actionTypes.PAYMENT_DOCUMENTS_TOGGLE,,
        actionTypes.PHYSICAL_CLIENT_CHANGE,
        actionTypes.PHYSICAL_CLIENT_ENDING_CHANGE
    ];

    yield* takeLatest(actionsToWatch, function*() {
        /* ... */

        task = yield fork(fetchData,
            validateStepFormUrl,
            { ...printForm, printStep: currentStep },
            actions.validateStepBegin,
            actions.validateStepSuccess,
            actions.validateStepError
        );
    });
}

Общая архитектура?

Как это все собирается

  1. "Старый код"
  2. "Новый код"

Как это все собирается

Старый код

Как это все собирается

Старый код

  • Gulp: SASS -> CSS
  • и причесывание стилей
  • MVC bundler: объединение и сжатие

Как это все собирается

Новый код

Как это все собирается

Новый код

  • webpack: сборка js в бандлы
  • стили туда же
  • babel: транспайлинг

Build

  • Установка пакетов
  • Сборка JS (webpack)
  • Сборка стилей (gulp)

Обеспечение качества

Обеспечение качества

Stylelint

ESLint

Mocha

Обеспечение качества

  • Редюсеры
  • Компоненты
  • Хелперы
  • Селекторы
  • Саги

Тесты

Обеспечение качества

Мечты: деплой

  • На pre-commit прогонять lint, comb
  • На pre-push прогонять тесты

Занимательное

  • http://bit.ly/billy_front

Code quality

Billing Frontend Ecosystem

By shimmy

Billing Frontend Ecosystem

  • 1,471