MicroSPA, или как изобрести квадратное колесо

    Всем привет, меня зовут Андрей Яковенко, и я веб-разработчик компании Digital Design.

    В нашей компании есть множество проектов, реализованных с помощью системы управления веб-контентом sitefinity, или по-простому CMS. Причины, по которым мы ее используем, были описаны ранее в этой статье. CMS – это, как правило, Multi Page Application, и сегодня я расскажу о том, что может дать внедрение frontend-фреймворков в решения на sitefinity и как это сделать.



    Лирическое отступление


    Хоть sitefinity и имеет на борту Angular.js, возможность интеграции других фреймворков отсутствует в силу закрытости исходных кодов. Однако это не мешает нам использовать возможности других фреймворков.

    Примеры в данной статье не являются панацеей и несут в основном информационный характер.

    Поиск нового


    Так как Angular, хоть и первой версии, уже присутствует в sitefinity, нам захотелось подружить нашу CMS с React или Vue.js.

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

    После беглого просмотра возможных решений был найден интересный проект, который компилировал страницы, написанные на React, в статический html. Такое решение нам не подходило, так как нам необходимо не терять всех прелестей SSR (Server Side Render) наряду с использованием исполняемого кода на стороне клиента.

    От слов к делу


    Sitefinity, как и большинство CMS, позволяет компоновать страницу различными элементами для отображения контента (виджетами). Мы на примере рассмотрим варианты внедрения Vue.js в уже готовые виджеты.

    Первый способ (простой)


    Данный способ заключается в подключении Vue.js как библиотеки в основной шаблон страницы.

    После этого мы в любом месте можем инициализировать наше приложение в любом виджете.

    Пример простого виджета:

    new Vue({
        el: '#widget-id',
        data: {
            msg: 'Hello world',
        },
    })
    

    Однако это будет означать, что весь блок виджета будет использован как шаблон, и Vue.js будет пытаться его интерполировать и, чтобы вывести сообщение в нужном месте, необходимо будет написать специальную конструкцию, которая будет отображаться, если что-то вызовет приостановку выполнения js кода перед рендером, или если у пользователя будет отключен JavaScript.

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

    Пример:

    new Vue({
        el: '#widget-id',
        data: {
            msg: 'Hello world',
        },
        template: '<div>{{msg}}</div>',
    })
    

    Теперь весь html код нашего виджета будет заменен на шаблон, описанный в поле template, а для того чтобы передать данные из модели виджета во Vue, достаточно конвертировать модель в JSON любым удобным способом и передать его в поле data.

    Второй способ (сложный)


    Для данного способа нам потребуется webpack, в минимальной конфигурации которого будет vue-loader. Также у нас нет необходимости в использовании html-extract-plugin, т.к. мы можем хранить шаблоны в js коде.

    Теперь мы можем использовать однофайловые компоненты Vue.

    Пример однофайлового компонента (Sample.vue):

    <template>
        <div>{{msg}}</div>
    </template>
    
    <script>
    export default {
        name: 'Sample',
        data: function () {
            return {
                msg: 'Hello world',
            }
        },
    }
    </script>
    

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

    После написания компонента-виджета нам необходимо его инициализировать.

    Пример инициализации виджета:

    import Vue from 'vue'
    import Sample from '../Sample.vue'
    
    Vue({
        render: function (h) {
            return h(Sample)
        }
    }).$mount('#widget-id')
    

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

    Расширим наш пример:

    import Vue from 'vue'
    import Sample from '../Sample.vue'
    
    function initWidget (selector) {
        Vue({
            render: function (h) {
                return h(Sample)
            }
        }).$mount(selector)
    }
    
    initWidget('#widget-id')
    

    Осталось передать данные модели виджета во Vue.js компонент. Это легко сделать, записав их в JSON перед передачей в объект data экземпляра Vue, после чего их можно передать компоненту в качестве пропсов.

    Пример:

    import Vue from 'vue'
    import Sample from '../Sample.vue'
    
    function initWidget (selector) {
        Vue({
            data: function () {
                return {
                    some: 'some value',
                }
            },
            render: function (h) {
                return h(Sample, { props: data })
            }
        }).$mount(selector)
    }
    
    initWidget('#widget-id')
    

    Небольшое ревью


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

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

    С другой стороны, при желании, с помощью такого подхода можно подключить любой фреймворк, библиотеку или написать собственный DOM-менеджер на ванильном JavaScript или jQuery.

    На этом все. Спасибо за внимание.
    Digital Design
    Компания

    Комментарии 15

      +1
      Прочитав статью, я так и не понял:
      1. Что может дать внедрение frontend-фреймворков в решения на sitefinity?
      2. Почему вы решили использовать именно Vue?

      Ну и вообще я считаю что данные способы требуют доработки, как минимум. Вы используете это в production?
        0
        1. Внедрение фреймворков дает доступ, непосредственно, к их возможностям, а также дает больше возможностей для js разработчиков, отдалив их(разработчиков) от Razor.
        2. Рассматривались разные варианты, но выбор пал на Vue т.к. в нем легко инициализировать изолированные приложения, при необходимости, со своими стейтами на основе Vuex, а также с независимым друг от друга роутингом.

        Данные способы все же имеют место быть. В production есть опыт интеграции Vue в разделах личного кабинета.
          0
          Ок. Тогда советую вам использовать Vue CLI если вы еще этого не делаете, у него есть возможность делать сборки отдельных компонентов, можно даже в виде веб-компонентов.

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

          Выполнится, так как этот код уже вызывает функцию, а вы ее зачем-то заворачиваете в другую функцию и вызываете уже ее.
            0
            Vue-cli не умеет из коробки собирать отдельные компоненты, для этого нужно допиливать конфигурацию webpack примерно так: github.com/Featurum/webpack-vue-components
              0

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

                –1
                Это будет отдельный компонент, который не является приложением + есть некоторые отличия сборки библиотеки от приложения. А если собирать веб компоненты, то мы теряем, как минимум IE11 и, возможно, другие браузеры. К сожалению не все заказчики перешли хотя бы на Edge.
                  0
                  Тут вы сударь не правы, ибо для поддержки более старых браузеров существует babel и различные полифилы, которые позволяют компилировать vue приложения вплоть до IE 9.
                  0
                  .vue содержит шаблон компонента, а для компиляции bundle.js требуется точка входа (экземпляр).
                    0
                    Что-то я вас не понимаю.
                    Если вы хотите сделать билд отдельного компонента, то точкой входа будет файл .vue. Такой компонент вы сможете подключить на страницу и использовать во Vue-приложении.
                    Если же вы хотите билд готового приложения, то используете как точку входа main.js. Полученный файл вы подключите к странице и он все сделает сам.
                0
                Как подметил tvelforce, vue-cli действительно из коробки не умеет собирать отдельные компоненты, да и это просто надстройка над webpack.

                По поводу выполнения кода — Вы правы, но при желании инициализировать приложения в определенном месте или после какого-то действия все таки придется оборачивать в функцию. С другой стороны webpack все равно обернет код в IIFE, в случае umd сборки.
                  0

                  Да, но вы вводите людей в заблуждение в статье. А если вы захотите "инициализировать приложения в определенном месте или после какого-то действия", то вам уже не IIFE нужна. Ну а если вебпак это сделает, то нам не надо делать это еще и самим. Прием безусловно хороший, но тут он не нужен.

                    0
                    Этот момент уже исправлен. Спасибо за поддержку.
            –1

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

              0
              Похоже на подход в SingleSPA на минималках :)
              Но подход действительно хороший, успехов вам!
                0
                Спасибо)
                На деле это самый простой способ внедрения фреймворков в виджеты/представления разного рода CMS.

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

              Самое читаемое