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

BDFINFO2.0
Оффлайн
Регистрация
14.05.16
Сообщения
11.398
Реакции
501
Репутация
0
В статьи о том, как мы создаем портальные решения для крупнейших работодателей России, была описана архитектура со стороны backend-а. В данной статье мы перейдём к frontend-у.

wzj5rj7alebchimas2cqght5dcq.png


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

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

Фронт написан в большей части на 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 иконок и потом использовать их через

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


const requireAll = (r) => r.keys().forEach(r)

const requireContext = require.context('@/assets/icons/', true, /\.svg$/)

requireAll(requireContext)


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