Как я могу связать динамический импорт с метеором? - PullRequest
1 голос
/ 04 июня 2019

Мой вариант использования

Я работаю над большим приложением, где, в зависимости от роли пользователя, я загружаю / импортирую различные наборы модулей.Это метеорное приложение с Vue, vue-router & vue-i18n на внешнем интерфейсе, но без магазина, как vuex.

Каждый модуль имеет свои собственные маршруты, файлы перевода, API и пользовательский интерфейс.Вот почему мне нужно проверить, что каждый модуль и его переводы загружены, прежде чем я отобразлю основной интерфейс и навигацию (или, например, ярлыки элементов навигации, относящиеся к выгруженному модулю, не будут переведены, или локализованные маршруты вернут 404).

Существует ли как можно более простой шаблон для обеспечения загрузки всего?

Мой код и логика

Мой пример использованияболее сложный, чем я могу достичь с помощью Promise.all afaik.

Я пытался давать вложенные обещания с комбинацией Promises.all и then().

Чтобы подвести итог, порядок:

  • загрузить базовый комплект
  • войти в клиент
  • импортировать языковой файл i18n для основногозатем связать для каждого модуля
  • для каждого модуля, после того, как его языковой файл загружен и объединен в соответствующие сообщения i18n, мне нужно загрузить сам модуль (локализованные маршруты, пользовательский интерфейс ...)

Основная часть загрузки

Accounts.onLogin(function (user) {
    let userRoles = Roles.getRolesForUser(Meteor.userId())
    let promises = []
    let lang = getDefaultLanguage()
    promises.push(loadLanguageAsync(lang))
    this.modulesReady = false
    for (let role of userRoles) {
        switch (role) {
        case "user":
            import { loadUserLanguageAsync } from "/imports/user/data/i18n"
            promises.push(loadUserLanguageAsync(lang).then(import("/imports/user/")))
            break
        case "admin":
            import { loadAdminLanguageAsync } from "/imports/admin/data/i18n"
            promises.push(loadAdminLanguageAsync(lang).then(import("/imports/admin/")))
            break
        default:
            break
        }
    }

    return Promise.all(promises).then(function (values) {
        this.modulesReady = true // my green flag, attached to the window object
    })
})

основные функции загрузки языка

const loadedLanguages = []

// Load i18n
Vue.use(VueI18n)
export const i18n = new VueI18n()


export const getDefaultLanguage = () => {
    let storedLanguage = window.localStorage.getItem(
        Meteor.settings.public.brand + "_lang"
    )
    return Meteor.user() && Meteor.user().settings && Meteor.user().settings.language
        ? Meteor.user().settings.language
        : // condition 2: if not, rely on a previously selected language
        storedLanguage
            ? storedLanguage
            : // or simply the browser default lang
            navigator.language.substring(0, 2)
}
export const loadLanguage = (lang, langFile) => {
    console.log("LOAD LANGUAGE " + lang)

    // we store agnostically the last selected language as default, if no user is logged in.
    window.localStorage.setItem(
        Meteor.settings.public.brand + "_lang",
        lang
    )
    loadedLanguages.push(lang)
    if (langFile) {
        i18n.setLocaleMessage(lang, Object.assign(langFile))
    }
    i18n.locale = lang


    return lang
}

export const loadLanguageModule = (lang, langFile) => {
    console.log("LOAD LANGUAGE MODULE" + lang)
    i18n.mergeLocaleMessage(lang, Object.assign(langFile))
    return lang
}

export function loadLanguageAsync(lang) {
    if (i18n.locale !== lang) {
        if (!loadedLanguages.includes(lang)) {
            switch (lang) {

            case "en":
                return import("./lang/en.json").then(langFile => loadLanguage("en", langFile))

            case "fr":
                return import("./lang/fr.json").then(langFile => loadLanguage("fr", langFile))

            default:
                return import("./lang/fr.json").then(langFile => loadLanguage("fr", langFile))
            }
        } else {
            console.log("Already loaded " + lang)

        }
        return Promise.resolve(!loadedLanguages.includes(lang) || loadLanguage(lang))
    }
    return Promise.resolve(lang)
}

Загрузка языка пользовательского модуля

const userLoadedLanguages = []

export default function loadUserLanguageAsync(lang) {
    if (i18n.locale !== lang || !userLoadedLanguages.includes(lang)) {
        switch (lang) {
        case "en":
            return import("./lang/en.json").then(langFile => loadLanguageModule("en", langFile))
        case "fr":
            return import("./lang/fr.json").then(langFile => loadLanguageModule("fr", langFile))
        default:
            return import("./lang/fr.json").then(langFile => loadLanguageModule("fr", langFile))
        }
    }
    return Promise.resolve(i18n.messages[lang].user).then(console.log("USER LANG LOADED"))
}
  • После загрузки каждого модуля я устанавливаю флажок, который позволяет моему навигационному устройству маршрутизатора переходить к требуемому маршруту (см.основная загружаемая часть).

Асинхронная функция защиты маршрутизатора и ожидания

router.beforeEach((to, from, next) => {
     isReady().then(
        console.log("NEXT"),
        next()
    )
})
async function isReady() {
    while (true) {
        if (this.modulesReady) { console.log("READY"); return }
        await null // prevents app from hanging
    }
}

Я совершенно новичок в логике асинхронности и пытаюсь определитьчто я делаю не такПриведенный здесь код приводит к сбою браузера, так как я предполагаю, что значения моих обещаний не правильные, и он идет в бесконечном цикле isReady().

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

Спасибо!

1 Ответ

2 голосов
/ 04 июня 2019

Как связать динамический импорт с метеором?

Сначала рассмотрим этот ответ в цепочках обещаний: Как получить доступ к результатам предыдущих обещаний в цепочке .then ()?

Если вы предпочитаете использовать асинхронный / ожидающий стиль, который вы здесь используете: динамический импорт может быть вызван с помощью await внутри async function. Это дает вам возможность обернуть ваш стиль синхронизации кода и разрешить все в конечном обещании:

Рассмотрим простой файл JSON в относительном пути проекта /imports/lang.json:

{
  "test": "value"
}

и некоторый пример экспортированной константы по пути /imports/testObj.js:

export const testObj = {
  test: 'other value'
}

Вы можете динамически импортировать их, используя асинхронную функцию, например так (пример в client/main.js):

async function imports () {
  const json = await import('../imports/lang.json')
  console.log('json loaded')
  const { testObj } = await import('../imports/test')
  console.log('testObj loaded')
  return { json: json.default, testObj }
}

Meteor.startup(() => {
  imports().then(({ json, testObj }) => {
    console.log('all loaded: ', json, testObj)
  })
})

Это напечатает в последовательности

json loaded
testObj loaded
all loaded: Object { test: "value" } Object { test: "other value" }

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

...