refactor: 💡 replace memoization with manual object cache

This commit is contained in:
Christian Kaisermann 2019-11-20 01:32:30 -03:00
parent 0a0e4b3bab
commit 31faccec3b
2 changed files with 65 additions and 42 deletions

View File

@ -76,9 +76,9 @@
}, },
"dependencies": { "dependencies": {
"commander": "^4.0.1", "commander": "^4.0.1",
"deepmerge": "^4.2.2",
"estree-walker": "^0.9.0", "estree-walker": "^0.9.0",
"intl-messageformat": "^7.5.2", "intl-messageformat": "^7.5.2",
"micro-memoize": "^4.0.8",
"object-resolve-path": "^1.1.1", "object-resolve-path": "^1.1.1",
"tiny-glob": "^0.2.6" "tiny-glob": "^0.2.6"
} }

View File

@ -1,6 +1,6 @@
import { writable, derived } from 'svelte/store' import { writable, derived } from 'svelte/store'
import resolvePath from 'object-resolve-path' import resolvePath from 'object-resolve-path'
import memoize from 'micro-memoize' import merge from 'deepmerge'
import { import {
capital, capital,
@ -21,24 +21,49 @@ import {
let currentLocale: string let currentLocale: string
let currentDictionary: Record<string, Record<string, any>> let currentDictionary: Record<string, Record<string, any>>
const dictQueue: Record<string, any[]> = {}
const hasLocale = (locale: string) => locale in currentDictionary const hasLocale = (locale: string) => locale in currentDictionary
async function registerLocaleLoader(locale: string, loader: any) {
if (!(locale in currentDictionary)) {
$dictionary.update(d => {
d[locale] = {}
return d
})
}
if (!(locale in dictQueue)) dictQueue[locale] = []
dictQueue[locale].push(loader)
}
function getAvailableLocale(locale: string): string | null { function getAvailableLocale(locale: string): string | null {
if (locale in currentDictionary || locale == null) return locale if (locale in currentDictionary || locale in dictQueue || locale == null) return locale
return getAvailableLocale(getGenericLocaleFrom(locale)) return getAvailableLocale(getGenericLocaleFrom(locale))
} }
const lookupMessage = memoize((path: string, locale: string): string => { const lookupCache: Record<string, Record<string, string>> = {}
const addToCache = (path: string, locale: string, message: string) => {
if (!(locale in lookupCache)) lookupCache[locale] = {}
if (!(path in lookupCache[locale])) lookupCache[locale][path] = message
return message
}
const invalidateLookupCache = (locale: string) => {
delete lookupCache[locale]
}
const lookupMessage = (path: string, locale: string): string => {
if (locale == null) return null if (locale == null) return null
if (locale in lookupCache && path in lookupCache[locale]) {
return lookupCache[locale][path]
}
if (hasLocale(locale)) { if (hasLocale(locale)) {
if (path in currentDictionary[locale]) return currentDictionary[locale][path] if (path in currentDictionary[locale]) {
return addToCache(path, locale, currentDictionary[locale][path])
}
const message = resolvePath(currentDictionary[locale], path) const message = resolvePath(currentDictionary[locale], path)
if (message) return message if (message) return addToCache(path, locale, message)
} }
return lookupMessage(path, getGenericLocaleFrom(locale)) return lookupMessage(path, getGenericLocaleFrom(locale))
}) }
const formatMessage: Formatter = (id, options = {}) => { const formatMessage: Formatter = (id, options = {}) => {
if (typeof id === 'object') { if (typeof id === 'object') {
@ -47,13 +72,20 @@ const formatMessage: Formatter = (id, options = {}) => {
} }
const { values, locale = currentLocale, default: defaultValue } = options const { values, locale = currentLocale, default: defaultValue } = options
if (locale == null) {
throw new Error(
'[svelte-i18n] Cannot format a message without first setting the initial locale.'
)
}
const message = lookupMessage(id, locale) const message = lookupMessage(id, locale)
if (!message) { if (!message) {
console.warn( console.warn(
`[svelte-i18n] The message "${id}" was not found in "${getGenericLocalesFrom(locale).join( `[svelte-i18n] The message "${id}" was not found in "${getGenericLocalesFrom(locale).join(
'", "', '", "'
)}".`, )}".`
) )
return defaultValue || id return defaultValue || id
} }
@ -73,36 +105,27 @@ formatMessage.lower = (id, options) => lower(formatMessage(id, options))
const $dictionary = writable<Record<string, Record<string, any>>>({}) const $dictionary = writable<Record<string, Record<string, any>>>({})
$dictionary.subscribe(newDictionary => (currentDictionary = newDictionary)) $dictionary.subscribe(newDictionary => (currentDictionary = newDictionary))
const loadLocale = (localeToLoad: string) => { function loadLocale(localeToLoad: string) {
return Promise.all( return Promise.all(
getGenericLocalesFrom(localeToLoad) getGenericLocalesFrom(localeToLoad).map(localeItem =>
.map(localeItem => { flushLocaleQueue(localeItem)
const loader = currentDictionary[localeItem] .then(() => [localeItem, { err: undefined }])
if (loader == null && localeItem !== localeToLoad) { .catch(e => [localeItem, { err: e }])
console.warn(
`[svelte-i18n] No dictionary or loader were found for the locale "${localeItem}". It's the fallback locale of "${localeToLoad}."`,
) )
return
}
if (typeof loader !== 'function') return
return loader().then((dict: any) => [localeItem, dict.default || dict])
})
.filter(Boolean),
) )
.then(updates => { }
if (updates.length > 0) {
// update dictionary only once async function flushLocaleQueue(locale: string = currentLocale) {
if (!(locale in dictQueue)) return
return Promise.all(dictQueue[locale].map((loader: any) => loader())).then(partials => {
dictQueue[locale] = []
partials = partials.map(partial => partial.default || partial)
invalidateLookupCache(locale)
$dictionary.update(d => { $dictionary.update(d => {
updates.forEach(([localeItem, localeDict]) => { d[locale] = merge.all<any>([d[locale] || {}].concat(partials))
d[localeItem] = localeDict
})
return d return d
}) })
}
return updates
})
.catch((e: Error) => {
throw e
}) })
} }
@ -111,10 +134,8 @@ const localeSet = $locale.set
$locale.set = (newLocale: string): void | Promise<void> => { $locale.set = (newLocale: string): void | Promise<void> => {
const locale = getAvailableLocale(newLocale) const locale = getAvailableLocale(newLocale)
if (locale) { if (locale) {
if (typeof currentDictionary[locale] === 'function') { if (locale in dictQueue && dictQueue[locale].length > 0) {
// load all locales related to the passed locale return flushLocaleQueue(locale).then(() => localeSet(newLocale))
// i.e en-GB loads en, but en doesn't load en-GB
return loadLocale(locale).then(() => localeSet(newLocale))
} }
return localeSet(newLocale) return localeSet(newLocale)
} }
@ -136,8 +157,10 @@ export {
$dictionary as dictionary, $dictionary as dictionary,
$format as _, $format as _,
$format as format, $format as format,
$locales, $locales as locales,
getClientLocale, getClientLocale,
defineMessages, defineMessages,
loadLocale as preloadLocale, loadLocale as preloadLocale,
registerLocaleLoader,
flushLocaleQueue,
} }