Frontend разработки порталов на СПО: делимся опытом

    В первой части статьи о том, как мы создаем портальные решения для крупнейших работодателей России, была описана архитектура со стороны backend-а. В данной статье мы перейдём к frontend-у.



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

    Переиспользуемость

    Фронт написан в большей части на Vue.js, и так как весь портал разбит на портлеты, каждый из них – это отдельный инстанс Vue со своим стором (Vuex), роутом (Vue Router) и компонентами. Каждый такой инстанс вынесен в свой репозиторий.

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

    Один хранит общий функционал: это общие компоненты, сервисы, константы и т.д. У нас этот модуль называется Vue-common.

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

    Для удобства работы с API методы REST-а также были разделены на общие и локальные. Во Vue-common были вынесены методы для получения списка юзеров портала, методы администрирования, доступа к файловой системе портала и прочие. Все API endpoint-ы были вынесены в отдельные сервисы, которые регистрировались в точке входа и подключались к инстансу Vue. Затем они могли использоваться в любой точке приложения.

    Каждый отдельный сервис регистрируется внутри плагина. Для подключения плагинов во Vue есть встроенная функция use. Подробнее о плагинах во Vue можно почитать тут.

    Сам плагин инициализируется вот так:

    class Api {
        constructor () {
            // Инстанс http клиента, который формирует запросы
            this.instance = instance
    
            // Так происходит регистрация общих сервисов
           // В каждый сервис прокидывается this, чтобы был доступ к инстансу http клиента
            Object.keys(commonServices).forEach(name => commonServices[name](this))
            
            // Так происходит регистрация локальных сервисов
    	requireService.keys().forEach(filename => requireService(filename).default(this))
        }
    
        install () {
    	Vue.prototype.$api= this
        }
    }
    
    export default new Api()
    

    Кроме инициализации:

    1. Создается инстанс http клиента. В котором задается baseURL нашего backend-а и заголовки

      const instance = axios.create({
      	baseURL: '/example/api',
      	responseType: 'json',
      	headers: {
      		'Content-Type': 'application/json',
      		'Cache-Control': 'no-cache',
      		'Pragma': 'no-cache',
      	}
      })
                    Так как backend у нас рестовый, мы используем axios.

    2. Создаются сервисы, хранящие сами запросы

      // api - это наш http клиент
      export default api => {
      	api.exampleService= {
      		exampleGetRequest(params) {
      			return api.instance.request({
      				method: 'get',
      				url: `example/get`,
      				params
      			})
      		},
      		examplePostRequest(data) {
      			return api.instance.request({
      				method: 'post',
      				url: `example/post`,
      				data
      			})
      		},
      	}
      }
      

      Во vue-common-е достаточно создать только такой сервис, а регистрируется он уже для каждого портлета в классе Api
    3. Регистрируются общие и локальные сервисы

           const requireService = require.context('./service', false, /.service.js$/)
      

    В компонентах они используются очень просто. Например:

    export default {
    	methods: {
    		someMethod() {
        			this.$api.exampleService.exampleGetRequest()
    }
    }
    }
    

    Если нужно делать запросы за пределами приложения, то можно сделать так:

    // Импортировать апи класс (@ - это алиас прописанный в конфиге)
    import api from ‘@/api’
    
    // И потом просто из него дергать нужный метод
    api.exampleService.exampleGetRequest()
    

    Маштабирование

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

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

    Регистрация компонентов выглядит так:

    import _ from “lodash”
    
    const requireComponent = require.context('@/components', true, /^[^_].+\.vue$/i)
    
    requireComponent.keys().forEach(filename => {
        const componentConfig = requireComponent(filename)
    
        // Get PascalCase name of component
        const componentName = _.upperFirst(
            _.camelCase(/\/\w+\.vue/.exec(filename)[0].replace(/^\.\//, '').replace(/\.\w+$/, ''))
        )
    
        Vue.component(componentName, componentConfig.default || componentConfig)
    })
    

    Иногда возникает необходимость для разрабатываемого нами портала добавить уникальную функциональность и для этого приходится писать компоненты, свойственные только ему, или просто по-другому реализовать тот или иной компонент. Достаточно создать компонент в отдельной папке, например /components-portal/*название портала*/*.vue, и зарегистрировать его в нужном entry point-е, добавив require.context не для одной папки, а для нескольких.

    const contexts = [
        require.context('@/components', true, /^[^_].+\.vue$/i),
       require.context('@/components-portal/example', true, /^[^_].+\.vue$/i)
    ]
    
    contexts.forEach(requireComponent => {
        requireComponent.keys().forEach(filename => {
            const componentConfig = requireComponent(filename)
    
            // Get PascalCase name of component
            const componentName = _.upperFirst(
                _.camelCase(/\/\w+\.vue/.exec(filename)[0].replace(/^\.\//, '').replace(/\.\w+$/, ''))
            )
    
            Vue.component(componentName, componentConfig.default || componentConfig)
        })
    })
    

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

    Также глобально регистрируются ассеты, например svg иконки. Мы используем svg-sprite-loader, чтобы создать спрайт из svg иконок и потом использовать их через <use :xlink:href="#*название иконки*"/>

    Регистрируются они так:

    const requireAll = (r) => r.keys().forEach(r)
    
    const requireContext = require.context('@/assets/icons/', true, /\.svg$/)
    
    requireAll(requireContext)
    

    Чтобы масштабировать не только функционал, но и стили компонентов, у нас реализован механизм смены стилей для определенного портала. В однофайловых компонентах указываются стили в теге <style>, и они применяются по умолчанию. Для того чтобы реализовать стили под конкретный портал, необходимо прописать их в ещё одном теге
    Digital Design
    Компания

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

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

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