optimization and refactoring, rules are first flattened and then

processed, letting us to set individual rules as "lazy"
This commit is contained in:
Henry Jameson 2024-02-26 21:37:40 +02:00
parent ac85cdac68
commit dc22386599
3 changed files with 243 additions and 250 deletions

View File

@ -327,8 +327,6 @@ const setConfig = async ({ store }) => {
} }
const checkOAuthToken = async ({ store }) => { const checkOAuthToken = async ({ store }) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (store.getters.getUserToken()) { if (store.getters.getUserToken()) {
try { try {
await store.dispatch('loginUser', store.getters.getUserToken()) await store.dispatch('loginUser', store.getters.getUserToken())
@ -336,8 +334,7 @@ const checkOAuthToken = async ({ store }) => {
console.error(e) console.error(e)
} }
} }
resolve() return Promise.resolve()
})
} }
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ store, i18n }) => {

View File

@ -5,23 +5,16 @@ import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js'
import { getCssRules } from '../theme_data/css_utils.js' import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from '../../modules/config.js' import { defaultState } from '../../modules/config.js'
export const applyTheme = (input) => { export const applyTheme = async (input) => {
let extraRules let extraRules
if (input.themeType !== 1) { if (input.themeType !== 1) {
const t0 = performance.now()
const { theme } = generatePreset(input) const { theme } = generatePreset(input)
const t1 = performance.now()
console.debug('Themes 2 initialization took ' + (t1 - t0) + 'ms')
extraRules = convertTheme2To3(theme) extraRules = convertTheme2To3(theme)
} else { } else {
console.debug(input)
extraRules = convertTheme2To3(input) extraRules = convertTheme2To3(input)
} }
const t1 = performance.now()
const themes3 = init(extraRules, '#FFFFFF') const themes3 = init(extraRules, '#FFFFFF')
const t2 = performance.now()
console.debug('Themes 3 (eager) initialization took ' + (t2 - t1) + 'ms')
const head = document.head const head = document.head
const body = document.body const body = document.body
body.classList.add('hidden') body.classList.add('hidden')
@ -47,14 +40,21 @@ export const applyTheme = (input) => {
styleSheet.insertRule(rule, 'index-max') styleSheet.insertRule(rule, 'index-max')
} }
}) })
body.classList.remove('hidden') body.classList.remove('hidden')
themes3.lazy.then(lazyRules => {
setTimeout(() => {
themes3.lazy().then(lazyRules => {
const t2 = performance.now()
getCssRules(lazyRules, themes3.staticVars).forEach(rule => { getCssRules(lazyRules, themes3.staticVars).forEach(rule => {
styleSheet.insertRule(rule, 'index-max') styleSheet.insertRule(rule, 'index-max')
}) })
const t3 = performance.now() const t3 = performance.now()
console.debug('Themes 3 finalization (lazy) took ' + (t3 - t2) + 'ms') console.debug('Themes 3 finalization (lazy) took ' + (t3 - t2) + 'ms')
}) })
})
return Promise.resolve()
} }
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) => const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>

View File

@ -151,6 +151,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
const eagerRules = [] const eagerRules = []
const lazyRules = [] const lazyRules = []
const lazyPromises = []
const rulesetUnsorted = [ const rulesetUnsorted = [
...Object.values(components) ...Object.values(components)
@ -187,68 +188,16 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name)) const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
let counter = 0 const processCombination = (combination, rules) => {
const promises = []
const processInnerComponent = (component, rules, parent) => {
const addRule = (rule) => { const addRule = (rule) => {
rules.push(rule) rules.push(rule)
} }
const parentSelector = ruleToSelector(parent, true) const selector = ruleToSelector(combination, true)
// const parentList = parent ? unroll(parent).reverse().map(c => c.component) : [] const cssSelector = ruleToSelector(combination)
// if (!component.virtual) {
// const path = [...parentList, component.name].join(' > ')
// console.log('Component ' + path + ' process starting')
// }
// const t0 = performance.now()
const {
validInnerComponents = [],
states: originalStates = {},
variants: originalVariants = {},
name
} = component
// Normalizing states and variants to always include "normal" const parentSelector = selector.split(/ /g).slice(0, -1).join(' ')
const states = { normal: '', ...originalStates } const soloSelector = selector.split(/ /g).slice(-1)[0]
const variants = { normal: '', ...originalVariants }
const innerComponents = (validInnerComponents).map(name => {
const result = components[name]
if (result === undefined) console.error(`Component ${component.name} references a component ${name} which does not exist!`)
return result
})
// Optimization: we only really need combinations without "normal" because all states implicitly have it
const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal')
const stateCombinations = [
['normal'],
...getAllPossibleCombinations(permutationStateKeys)
.map(combination => ['normal', ...combination])
.filter(combo => {
// Optimization: filter out some hard-coded combinations that don't make sense
if (combo.indexOf('disabled') >= 0) {
return !(
combo.indexOf('hover') >= 0 ||
combo.indexOf('focused') >= 0 ||
combo.indexOf('pressed') >= 0
)
}
return true
})
]
const stateVariantCombination = Object.keys(variants).map(variant => {
return stateCombinations.map(state => ({ variant, state }))
}).reduce((acc, x) => [...acc, ...x], [])
stateVariantCombination.forEach(combination => {
counter++
// const tt0 = performance.now()
combination.component = component.name
const soloSelector = ruleToSelector(combination, true)
const soloCssSelector = ruleToSelector(combination)
const selector = [parentSelector, soloSelector].filter(x => x).join(' ')
const cssSelector = [parentSelector, soloCssSelector].filter(x => x).join(' ')
const lowerLevelSelector = parentSelector const lowerLevelSelector = parentSelector
const lowerLevelBackground = computed[lowerLevelSelector]?.background const lowerLevelBackground = computed[lowerLevelSelector]?.background
@ -262,12 +211,10 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
} }
// Inheriting all of the applicable rules // Inheriting all of the applicable rules
const existingRules = ruleset.filter(findRules({ component: component.name, ...combination, parent })) const existingRules = ruleset.filter(findRules(combination))
const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {}) const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {})
const computedRule = { const computedRule = {
component: component.name,
...combination, ...combination,
parent,
directives: computedDirectives directives: computedDirectives
} }
@ -275,10 +222,10 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
computed[selector].computedRule = computedRule computed[selector].computedRule = computedRule
computed[selector].dynamicVars = dynamicVars computed[selector].dynamicVars = dynamicVars
if (virtualComponents.has(component.name)) { if (virtualComponents.has(combination.component)) {
const virtualName = [ const virtualName = [
'--', '--',
component.name.toLowerCase(), combination.component.toLowerCase(),
combination.variant === 'normal' combination.variant === 'normal'
? '' ? ''
: combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(), : combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
@ -323,7 +270,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
) )
// Updating previously added rule // Updating previously added rule
const earlyLowerLevelRules = rules.filter(findRules(parent, true)) const earlyLowerLevelRules = rules.filter(findRules(combination.parent, true))
const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0] const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0]
const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {} const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {}
@ -344,17 +291,26 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
if (computedDirectives.background) { if (computedDirectives.background) {
let inheritRule = null let inheritRule = null
const variantRules = ruleset.filter(findRules({ component: component.name, variant: combination.variant, parent })) const variantRules = ruleset.filter(
findRules({
component: combination.component,
variant: combination.variant,
parent: combination.parent
})
)
const lastVariantRule = variantRules[variantRules.length - 1] const lastVariantRule = variantRules[variantRules.length - 1]
if (lastVariantRule) { if (lastVariantRule) {
inheritRule = lastVariantRule inheritRule = lastVariantRule
} else { } else {
const normalRules = ruleset.filter(findRules({ component: component.name, parent })) const normalRules = ruleset.filter(findRules({
component: combination.component,
parent: combination.parent
}))
const lastNormalRule = normalRules[normalRules.length - 1] const lastNormalRule = normalRules[normalRules.length - 1]
inheritRule = lastNormalRule inheritRule = lastNormalRule
} }
const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true) const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true)
const inheritedBackground = computed[inheritSelector].background const inheritedBackground = computed[inheritSelector].background
dynamicVars.inheritedBackground = inheritedBackground dynamicVars.inheritedBackground = inheritedBackground
@ -398,7 +354,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
case 'color': { case 'color': {
const color = findColor(value[0], { dynamicVars, staticVars }) const color = findColor(value[0], { dynamicVars, staticVars })
dynamicVars[k] = color dynamicVars[k] = color
if (component.name === 'Root') { if (combination.component === 'Root') {
staticVars[k.substring(2)] = color staticVars[k.substring(2)] = color
} }
break break
@ -406,14 +362,14 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
case 'shadow': { case 'shadow': {
const shadow = value const shadow = value
dynamicVars[k] = shadow dynamicVars[k] = shadow
if (component.name === 'Root') { if (combination.component === 'Root') {
staticVars[k.substring(2)] = shadow staticVars[k.substring(2)] = shadow
} }
break break
} }
case 'generic': { case 'generic': {
dynamicVars[k] = value dynamicVars[k] = value
if (component.name === 'Root') { if (combination.component === 'Root') {
staticVars[k.substring(2)] = value staticVars[k.substring(2)] = value
} }
break break
@ -424,50 +380,90 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
addRule({ addRule({
dynamicVars, dynamicVars,
selector: cssSelector, selector: cssSelector,
component: component.name,
...combination, ...combination,
parent,
directives: computedDirectives directives: computedDirectives
}) })
} }
}
const processInnerComponent = (component, parent) => {
const combinations = []
const {
validInnerComponents = [],
states: originalStates = {},
variants: originalVariants = {}
} = component
// Normalizing states and variants to always include "normal"
const states = { normal: '', ...originalStates }
const variants = { normal: '', ...originalVariants }
const innerComponents = (validInnerComponents).map(name => {
const result = components[name]
if (result === undefined) console.error(`Component ${component.name} references a component ${name} which does not exist!`)
return result
})
// Optimization: we only really need combinations without "normal" because all states implicitly have it
const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal')
const stateCombinations = [
['normal'],
...getAllPossibleCombinations(permutationStateKeys)
.map(combination => ['normal', ...combination])
.filter(combo => {
// Optimization: filter out some hard-coded combinations that don't make sense
if (combo.indexOf('disabled') >= 0) {
return !(
combo.indexOf('hover') >= 0 ||
combo.indexOf('focused') >= 0 ||
combo.indexOf('pressed') >= 0
)
}
return true
})
]
const stateVariantCombination = Object.keys(variants).map(variant => {
return stateCombinations.map(state => ({ variant, state }))
}).reduce((acc, x) => [...acc, ...x], [])
stateVariantCombination.forEach(combination => {
combination.component = component.name
combination.lazy = component.lazy || parent?.lazy
combination.parent = parent
if (combination.state.indexOf('hover') >= 0) {
combination.lazy = true
}
combinations.push(combination)
innerComponents.forEach(innerComponent => { innerComponents.forEach(innerComponent => {
if (innerComponent.lazy) { combinations.push(...processInnerComponent(innerComponent, combination))
promises.push(new Promise((resolve, reject) => { })
setTimeout(() => { })
try {
processInnerComponent(innerComponent, lazyRules, { parent, component: name, ...combination }) return combinations
resolve()
} catch (e) {
reject(e)
} }
}, 0)
})) const t0 = performance.now()
const combinations = processInnerComponent(components.Root)
const t1 = performance.now()
console.debug('Tree tranveral took ' + (t1 - t0) + ' ms')
combinations.forEach((combination) => {
if (combination.lazy) {
lazyPromises.push(async () => processCombination(combination, lazyRules))
} else { } else {
processInnerComponent(innerComponent, rules, { parent, component: name, ...combination }) processCombination(combination, eagerRules)
} }
}) })
// const tt1 = performance.now() const t2 = performance.now()
// if (!component.virtual) { console.debug('Eager processing took ' + (t2 - t1) + ' ms')
// console.log('State-variant ' + combination.variant + ' : ' + combination.state.join('+') + ' procession time: ' + (tt1 - tt0) + 'ms')
// }
})
// const t1 = performance.now()
// if (!component.virtual) {
// const path = [...parentList, component.name].join(' > ')
// console.log('Component ' + path + ' procession time: ' + (t1 - t0) + 'ms')
// }
}
processInnerComponent(components.Root, eagerRules)
console.debug('Eager combinations processed:' + counter)
const lazyExec = Promise.all(promises).then(() => {
console.debug('Total combinations processed: ' + counter)
}).then(() => lazyRules)
return { return {
lazy: lazyExec, lazy: async () => {
await Promise.all(lazyPromises.map(x => x()))
return lazyRules
},
eager: eagerRules, eager: eagerRules,
staticVars staticVars
} }