Главная » Хабрахабр » Как настроить установку переменных окружения Nuxt.js в рантайме, или Как сделать всё не как все и не пожалеть

Как настроить установку переменных окружения Nuxt.js в рантайме, или Как сделать всё не как все и не пожалеть


(Иллюстрация)

В предыдущем раунде битвы с этим фреймворком они показали, как запустить проект на Nuxt так, чтобы все были счастливы. Senior web developer’ы Антон и Алексей продолжают рассказ о непростой борьбе с Nuxt. В новой статье поговорим о реальном применении фреймворка.

Месячная аудитория составляла 6-7 млн. Мы начали переписывать проект с огромным техническим долгом. Поэтому было решено отправить ее на пенсию. уникальных посетителей, но существующая платформа доставляла слишком много проблем. Само собой, производительность была нашим наибольшим опасением, но также не хотелось просесть по SEO.

Как итог мы начали строить решение на базе Nuxt.js.
После пары раундов обсуждения решили не полагаться на традиционный подход с только серверным рендерингом — но и не загонять себя в ловушку клиентского рендеринга.

Старый добрый Nuxt.js

Берем уже известный нам по прошлой статье «фреймворк для фреймворка» на базе Vue.js для построения универсальных клиент-серверных приложений. В нашем случае приложение работает в связке с довольно-таки сложным API (хитросплетение микросервисов, но об этом как-нибудь в другой раз) и несколькими слоями кеширования, рендерит изменяемый редакторами контент и возвращает уже статический контент для молниеносной производительности. Здорово, правда?

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

Некогда объяснять, build once, deploy many!

Как-то к нам подошел техлид и озадачил: всякий раз, когда мы пушим изменения в репозиторий, нам нужно делать билд для каждого из окружений (dev-, stage- и prod-среды) отдельно. Это было медленно. Но в чем отличие этих билдов? Да только в переменных окружения! И то, что он попросил сделать, звучало логично и обоснованно. Но наша первая реакция была: О_о

Но в мире Javascript… У нас целая батарея компиляторов, транспиляторов, пре- и пост-процессоров, а еще тестов и линтеров. Стратегия «Build once, deploy many» имеет смысл в мире разработки ПО. Кроме того, существует множество потенциальных проблем утечки конфиденциальных данных (секреты, ключи API и прочее, что может храниться в конфигурациях). Все это требует времени, чтобы настроить их для каждого из окружений.

И мы начали

Конечно, начали с поиска в Google. Потом пообщались с мейнтейнерами Nuxt.js, но без особого успеха. Что поделать — пришлось придумывать решение самостоятельно, а не копировать со StackOverflow (это ведь основа нашей деятельности, не так ли?).

Разберемся, как Nuxt.js это делает

У Nuxt.js есть конфигурационный файл с ожидаемым названием nuxt.config.js. Он используется для программной передачи конфигураций в приложение:

const config = require('nuxt.config.js')
const nuxt = new Nuxt(config)

Есть возможность установить окружение через env-переменные. В общем-то, довольно распространенная практика — подключить конфигурационный файл динамически. Дальше все это передается в вебпак definePlugin и может использоваться на клиенте и сервере, примерно так:

process.env.propertyName
//или
context.env.propertyName.

Да, это означает компиляцию, и это не то, чего мы хотим. Эти переменные запекаются при сборке, больше информации здесь: Nuxt.js env page.
Обратили внимание на вебпак?

Попробуем иначе

Понимание того, как Nuxt.js работает, означает для нас:

  • мы больше не можем использовать env внутри nuxt.config.js;
  • любые другие динамические переменные (например, внутри head.meta) должны быть переданы в nuxt.config.js-объект в рантайме.

Код в server/index.js:


const config = require('../nuxt.config.js')

Меняем на:


// Импорт расширенных конфигураций Nuxt.js
const config = require('./utils/extendedNuxtConfig.js').default

Где utils/extendedNuxtConfig.js:


import config from 'config'
import get from 'lodash/get' // Импорт конфигураций Nuxt.js
const defaultConfig = require('../../nuxt.config.js') // Расширенные настройки
const extendedConfig = // Смержим конфигурации Nuxt.js const nuxtConfig = { ...defaultConfig, ...extendedConfig
} // Финальные манипуляции для мест
// где нам не нужны расширенные настройки
if (get(nuxtConfig, 'head.meta')) { nuxtConfig.head.meta.push({ hid: 'og:url', property: 'og:url', content: config.get('app.canonical_domain') })
} export default nuxtConfig

Слона-то мы и не приметили

Хорошо, мы решили проблему получения динамических переменных извне env-свойства объекта конфигураций в nuxt.config.js. Но изначальная проблема по-прежнему не решена.

Было предположение, что некий абстрактный sharedEnv.js будет использоваться для:

  • клиента — создадим файл env.js, который будет загружен глобально (window.env.envKey),
  • сервера — импортируется в модули, где это необходимо,
  • изоморфного кода, что-то вроде
    context.isClient? window.env[key]: global.sharedEnv[key].

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

Vuex нам поможет

Во время исследования проблемы мы обратили внимание, что Vuex store экспортируется в window-объект. Это решение вынужденное, для поддержки изоморфности Nuxt,js. Vuex — это хранилище данных, вдохновленное Flux, специально разработанное для Vue.js-приложений.

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

Начнем с server/utils/sharedEnv.js:


import config from 'config' /** * Настройка объекта, доступного для клиента и сервера * Не усложняйте, объект должен быть плоским * Будьте внимательны, чтобы не произошло утечки конфиденциальной информации * * @type {Object} */
const sharedEnv = { // ... canonicalDomain: config.get('app.canonical_domain'),
} export default sharedEnv

Код, что выше, выполнится во время запуска сервера. Затем добавим его в хранилище Vuex:


/** * Получить объект конфигураций. * Документация подразумевает только выполнение на стороне сервера * см. больше здесь * https://nuxtjs.org/guide/vuex-store/#the-nuxtserverinit-action * * @return {Object} Shared environment variables. */
const getSharedEnv = () => process.server ? require('~/server/utils/sharedEnv').default || {} : {} // ... export const state = () => ({ // ... sharedEnv: {}
}) export const mutations = { // ... setSharedEnv (state, content) { state.sharedEnv = content }
} export const actions = { nuxtServerInit ({ commit }) { if (process.server) { commit('setSharedEnv', getSharedEnv()) } }
}

Будем опираться на факт, что nuxtServerInit запускается во время, хм, серверной инициализации. Есть некоторая сложность: обратите внимание на метод getSharedEnv, здесь добавлена проверка на повторное выполнения на сервере.

Что вышло

Теперь мы получили общие переменные, которые могут быть извлечены в компонентах примерно так:
this.$store.state.sharedEnv.canonicalDomain

Победа!

А, нет. Что насчет плагинов?

Для конфигурации некоторых плагинов нужны переменные окружения. И когда мы хотим их использовать:
Vue.use(MyPlugin, { someEnvOption: 'Здесь нет доступа к vuex store' })

Великолепно, состояние гонки, Vue.js пытается инициализировать себя, прежде чем Nuxt.js зарегистрирует sharedEnvobject в хранилище Vuex.

Решается это довольно просто — сделаем плагин async-функцией и будем ждать выполнения nuxtServerInit: Хотя функция, которая регистрирует плагины, предоставляет доступ к объекту Context, содержащему ссылку на хранилище, sharedEnv по-прежнему пуст.


import Vue from 'vue'
import MyPlugin from 'my-plugin' /** * Конфигурация плагина MyPlugin асинхронно. */
export default async (context) => { // выполнить вручную, чтобы иметь доступ к объекту sharedEnv await context.store.dispatch('nuxtServerInit', context) const env = { ...context.store.state.sharedEnv } Vue.use(MyPlugin, { option: env.someKey })
}

Вот теперь победа.


Оставить комментарий

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

*

x

Ещё Hi-Tech Интересное!

Разработка buck-преобразователя на STM32F334: принцип работы, расчеты, макетирование

В двух своих последних статьях я рассказал о силовом модуле и плате управления на базе микроконтроллера STM32F334R8T6, которые созданы специально для реализации систем управления силовыми преобразователями и электроприводом. Так же был рассмотрен пример DC/AC преобразователя, который являлся демонстрацией, а не ...

Simulation theory: взаимосвязь квантово-химических расчётов и Реальности

Введение О чём этот текст Если человек услышит о «симуляции реальности», то в наиболее вероятно ему в голову придут или разные научно-фантастические произведения (типа Матрицы, Темного города, или Теоремы Зеро), или компьютерные игры. В случае людей, чьи головы засорены инженерным ...