Компонентизация веб-приложения

Это история разработки моего проекта. Большого. Он представляет собой смесь PHP и Node.js. С одной стороны это приложение из одной страницы, с другой - это SEO-оптимизированный сайт.

Было написано огромное количество JavaScript, CSS и HTML. Другими словами, настоящий ночной кошмар для любого разработчика. Были взлеты и падения. Ошибки и их исправление.

Борьба с помощью новейших технологий, которая в результате завершилась созданием удивительно простой библиотеки. Эта библиотека и является темой данной статьи.

Начало

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

В начале у нас было несколько страниц, которые управлялись через CMS. Сначала в них содержалось не так много JavaScript кода, потому что CMS поставляла большая часть контента.

Ниже приводится схематичная структура проекта:

схематичная структура

Мы добавляем код на стороне клиента в разные папки. Серверная часть кода содержала на тот момент только код PHP, поэтому мы поместили ее папку php.

Мы разместили в ней около 30 файлов, и все было в порядке.

Приключение начинается

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

Проблема № 1 - слишком много плохо структурированных файлов

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

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

Мы начали добавлять все больше и больше файлов во все вышеперечисленные папки. Структура начинала становиться грязной, поэтому мы решили создать подкаталоги для разных страниц и сохранять необходимый код в них:

подкаталоги

Например, CSS-стиль для страницы «О нас» размещался в файле css/about/styles.css, JavaScript - в файле js/about/scripts.js и так далее.

Мы использовали PHP-скрипт, который связывал файлы. Были, конечно, разделы сайта, которые размещались на нескольких страницах, и мы поместили их в папки common.

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

Чтобы найти то, что нам нужно, необходимо было искать это в трех различных папках. Тогда еще сайт был в основном написан на PHP.

Проблема № 2 - Великий поворотный момент или как мы смешали все карты

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

Мы должны были привести сайт к одностраничному приложению. И не только.

Сайт должен был иметь кучу функций реального времени. Конечно, не весь контент сайта должен был загружаться динамически. По-прежнему важной частью видения клиента оставалась SEO. Для реализации этих изменений мы выбрали стек MEAN.

Проблема заключалась в ранее созданных страницах. Их содержание должно было обслуживаться PHP, но логика страниц уже изменилась. Она была полностью реализована через JavaScript.

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

Проблема № 3 - Жесткий рабочий процесс

Какое-то время мы использовали GruntJS, но позже перешли на Gulp. Это здорово нам помогло, потому что разработка ускорилась. Тем не менее, добавлять или редактировать существующие компоненты по-прежнему было слишком неудобно.

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

Тогда мы вместе напрягли мозги и придумали следующий формат:

формат

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

Мы тщательно разрабатывали интерфейсы классов. Они были легко тестируемы, и к ним был обеспечен простой доступ. Мы поняли, что такая структура лучше подходит нам, потому что у нас было огромное количество независимых модулей.

Да, мы перемешивали файлы JavaScript со стилями CSS и шаблонами HTML, но это было проще, чем копаться в нескольких каталогах.

Проблема № 4 - Angular или собственный код

Те страницы, которые были созданы раньше, и которые обслуживались через PHP, в тоже время должны были содержать логику JavaScript.

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

Хорошо, что бюджет проекта был расширен, и мы решили использовать собственный фреймворк. В то время я работал над созданием своего собственного препроцессора CSS. С этого момента проект стал развиваться очень, очень быстро.

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

Зачем было создавать новый фреймворк?

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

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

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

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

Использование Angular, Ember, Knockout or Backbone имеет свои преимущества, но правда в том, что универсального решения не существует.

Мне нравятся слова Джереми Кейта, в своей беседе Сила простоты он сказал, что самое главное при выборе инструмента для работы - это философия, которую исповедует человек, создавший этот инструмент, и важно, чтобы эта философия совпадала с вашей.

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

Мы также пробовали Ember, но это не сработало, потому что он в значительной степени основывается на механизмах маршрутизации.

Backbone хорошо показал себя, он был ближе всего к нашему видению. Однако когда я представил команде AbsurdJS, мы решили использовать его.

Что дал нам AbsurdJS

AbsurdJS первоначально задумывался, как CSS процессор, расширенный до HTML процессора. Он был успешно портирован нами для использования на стороне клиента.

Так, вначале мы используем его для компиляции JavaScript в HTML или CSS. Да, вы поняли меня правильно; мы начали писать стили и разметку в JavaScript (вероятно, это прозвучит странно, но, пожалуйста, прочитайте дальше).

Я усовершенствовал библиотеку и добавил в нее с дюжину новых функций.

Разделяй и властвуй

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

Гораздо эффективнее разделить систему на более мелкие задачи и решать их по одной. Мы сделали то же самое.

Мы решили, что наше приложение будет построено из более мелких компонентов, примерно вот так:

var absurd = Absurd();
var MyComp = absurd.component('MyComp', {
    constructor: function() {
        // ...
    }
});
var instance = MyComp();

absurd.component определяет класс. Вызов метода MyComp() создает новый объект.

Давайте общаться друг с другом

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

Таким образом, каждый компонент является диспетчером событий:

var MyComp = absurd.component('MyComp', {
    doSomething: function() {
        this.dispatch('something-happen');
    }
});
var instance = MyComp();
instance.on('something-happen', function() {
    console.log('Hello!');
});
instance.doSomething();

Мы также имеем возможность передавать данные вместе с сообщением. Определение компонентов и их характеристика «отслеживать-рассылать» довольно проста.

Я перенял это понятие из других популярных фреймворков, потому оно является довольно распространенным. Кроме того, так моим коллегам было намного легче начать работать с AbsurdJS.

Управление DOM

Наряду с PHP обслуживающим разметку, мы динамически создаем DOM-элементы. Это значит, что нам нужен будет доступ к существующим элементам DOM или новым, тем которые будут позже добавлены на страницу.

Например, предположим, что мы имеем следующий HTML-код:

<div class="content">
    <h1>Заголовок страницы</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
Вот компонент, который извлекает заголовок:
	absurd.component('MyComp', {
    html: '.content h1',
    constructor: function() {
        this.populate();
        console.log(this.el.innerHTML); // Page title
    }
})();

Метод populate - это единственный магический метод во всей библиотеке. Он выполняет несколько действий, наподобие составления CSS или HTML, связывает события и тому подобное.

В приведенном выше примере, он видит свойство html и инициализирует переменную el, которая указывает на элемент DOM.

Это было очень удобно для нас, потому что, как только мы получали эту ссылку, мы могли работать с основными элементами и их дочерними элементами.

Для тех компонентов, которым нужны динамически созданные элементы, свойство html принимает объект:

absurd.component('MyComp', {
    html: {
        'div.content': {
            h1: 'Page title',
            p: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
        }
    },
    constructor: function() {
        this.populate();
        document.querySelector('body').appendChild(this.el);
    }
})();

Приведенный выше JSON-код переводится в аналогичную HTML-разметку. Я выбрал JSON, потому что с точки зрения JavaScript, он является гораздо более гибким.

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

AbsurdJS также имеет свой собственный движок шаблонов:

absurd.component('MyComp', {
    html: {
        'div.content': {
            h1: '<% this.title %>',
            ul: [
                '<% for(var i=0; i',
                { li: '<% this.availableFor[i] %>' },
                '<% } %>'
            ]
        }
    },
    title: 'That\'s awesome',
    availableFor: ['all browsers', 'Node.js'],
    constructor: function() {
        this.populate();
        document.querySelector('body').appendChild(this.el);
    }
})();

В результате дает следующее:

<div class="content">
    <h1>Это невероятно</h1>
    <ul>
        <li>все браузеры</li>
        <li>Node.js</li>
    </ul>
</div>

Ключевое слово this в приведенных выше выражениях указывает на сам компонент. Код между - валидный JavaScript.

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

Например:

<div class="content">
    <h1><% this.title %></h1>
    <ul>
        <% for(var i=0; i&amp;lt;this.availableFor.length; i++) { %>
        <li><% this.availableFor[i] %></li>
        <% } %>
    </ul>
</div>

... им можно управлять с помощью следующего компонента (результат тот же самый):

absurd.component('MyComp', {
    html: '.content',
    title: 'That\'s awesome',
    availableFor: ['all browsers', 'Node.js'],
    constructor: function() {
        this.populate();
    }
})();

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

Все это делается всего лишь через свойства старого доброго объекта JavaScript.

А как насчет стилей?

Мы успешно разделили всю систему на небольшие модули. Части, которые были до того контроллерами Angular, стали компонентами AbsurdJS.

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

Мы не должны создавать HTML-файлы вообще. Оглядываясь назад, я четко помню этот момент в истории разработки нашего проекта.

Многие файлы были удалены.

Тогда я подумал, что если мы сделаем то же самое с CSS. Это, конечно, можно было сделать, потому что AbsurdJS является и CSS-препроцессором, поэтому он может генерировать CSS-код.

Мы просто получаем скомпилированную строку, создаем в head текущей страницы новый тег style и вставляем его:

absurd.component('MyComp', {
    css: {
        '.content': {
            h1: {
                color: '#99FF00',
                padding: 0,
                margin: 0
            },
            p: {
                fontSize: '20px'
            }
        }
    },
    html: '.content',
    constructor: function() {
        this.populate();
    }
})();

Вот тег style, который будет сгенерирован:

<style id="MyComp-css" type="text/css">
    .content h1 {
      color: #99FF00;
      padding: 0;
      margin: 0;
    }
    .content p {
      font-size: 20px;
    }
</style>

И каждый день мы передавали CSS-стили из файлов SASS (на определенном этапе мы выбрали для себя SASS в качестве CSS-препроцессора) компонентам AbsurdJS.

Если честно, это было довольно просто, потому что все примеси и переменные, которые у нас были, определялись, как функции и переменные JavaScript. Передача стилей осуществлялась даже еще легче, потому что все было написано на JavaScript.

Это неприятный момент

... когда все работает идеально, но вы чувствуете, что что-то не так.

Мы оценили код. Он работал. AbsurdJS подхватил даже старые части. Новый контент использовал ту же библиотеку. HTML и CSS были отлично разделены и размещены непосредственно в определение компонентов.

Тем не менее, я чувствовал, что что-то не так. В какой-то момент и спросил себя: «Из чего состоит Веб?».

Новый контент

А то, что мы сделали, немного отличается от этого. Это больше похоже на схему, приведенную ниже:

схема

Я занимаюсь созданием сайтов уже более десяти лет, и прекрасно помню времена, когда мы все боролись за разделение этих трех элементов. И то, что я сделал в этом проекте, было сделано с точностью до наоборот. У нас совсем не было CSS и HTML файлов (почти). Все было на JavaScript.

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

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

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

Такие проекты, как AbsurdJS или Polymer показывают, что это возможно, и я рекомендую вам поэкспериментировать в этом направлении.

Назад к реальности

В конце концов, дела у клиента пошли хорошо. Настолько хорошо, что он решил запустить новый сервис. И что вы думаете? Он захотел, чтобы некоторые части существующего приложения были перенесены в новый проект.

Я не могу передать вам, как мы были рады тому факту, что у нас будет возможность переместить компоненты из одного проекта в другой. Нам не нужно будет устанавливать что-то, копировать HTML-разметку или CSS-файлы.

Нам просто нужно было взять JavaScript-файл компонента, поместить его в другое место и создать новый экземпляр. Он сразу мог работать, потому что не был связан ни с чем другим.

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

Да, мы нарушили некоторые правила. Правила, с которыми я лично согласен. Правила, которым я следовал в течение многих лет.

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

Мы не хотим в какой-то момент взглянуть на код и воскликнуть: "О, черт возьми ... это я написал!?". Когда я смотрю на код, я прекрасно понимаю, почему он именно таков, и что он делает. Так, как будто он был создан именно для этого проекта.

Заключение

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

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

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

Это программное обеспечение с полностью открытым исходным кодом, оно доступно на GitHub.

Перевод статьи «Componentizing the Web» был подготовлен дружной командой проекта Сайтостроение от А до Я.

30 апреля 2014 в 13:22
Материалы по теме
{"url":"http://www.fastvps.ru/", "src":"/images/advbanners/fastvps.png", "alt":"Хостинг Fastvps.ru. Наш выбор!"}
Заработок