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>, и они применяются по умолчанию. Для того чтобы реализовать стили под конкретный портал, необходимо прописать их в ещё одном теге

ссылка на оригинал статьи https://habr.com/ru/company/digdes/blog/492212/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *