perf: lazy-load computations (#1538)

* perf: lazy-load computations (experimental)

* fix lint

* add marks

* fixup

* lazy-load mixins too

* add missing files

* fix tests
This commit is contained in:
Nolan Lawson 2019-09-26 05:23:36 -07:00 committed by GitHub
parent 8fbf38e974
commit 038dc27163
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 97 additions and 57 deletions

View file

@ -144,7 +144,7 @@
(name === 'notifications' && $hasNotifications) || (name === 'community' && $hasFollowRequests) (name === 'notifications' && $hasNotifications) || (name === 'community' && $hasFollowRequests)
), ),
badgeNumber: ({ name, $numberOfNotifications, $numberOfFollowRequests }) => ( badgeNumber: ({ name, $numberOfNotifications, $numberOfFollowRequests }) => (
(name === 'notifications' && $numberOfNotifications) || (name === 'community' && $numberOfFollowRequests) (name === 'notifications' && $numberOfNotifications) || (name === 'community' && $numberOfFollowRequests) || 0
) )
}, },
methods: { methods: {

View file

@ -2,11 +2,14 @@
<svelte:component this={composeBox} {realm} {hidden} /> <svelte:component this={composeBox} {realm} {hidden} />
{/if} {/if}
<script> <script>
import { importComposeBox } from '../../_utils/asyncModules' import { importComposeBox, importLoggedInStoreExtensions } from '../../_utils/asyncModules'
export default { export default {
async oncreate () { async oncreate () {
const composeBox = await importComposeBox() const [composeBox] = await Promise.all([
importComposeBox(),
importLoggedInStoreExtensions()
])
this.set({ composeBox }) this.set({ composeBox })
}, },
data: () => ({ data: () => ({

View file

@ -10,18 +10,19 @@
</style> </style>
<script> <script>
import { store } from '../../_store/store' import { store } from '../../_store/store'
import { importTimeline } from '../../_utils/asyncModules' import { importLoggedInStoreExtensions, importTimeline } from '../../_utils/asyncModules'
export default { export default {
async oncreate () { async oncreate () {
console.log('LazyTimeline oncreate') console.log('LazyTimeline oncreate')
const { currentInstance } = this.store.get() const { currentInstance } = this.store.get()
const { timeline } = this.get() const { timeline } = this.get()
const [timelineComponent] = await Promise.all([
importTimeline(),
importLoggedInStoreExtensions()
])
this.store.set({ currentTimeline: timeline }) this.store.set({ currentTimeline: timeline })
this.store.setForTimeline(currentInstance, timeline, { runningUpdate: false }) this.store.setForTimeline(currentInstance, timeline, { runningUpdate: false })
console.log('importing timeline')
const timelineComponent = await importTimeline()
console.log('imported timeline')
this.set({ timelineComponent }) this.set({ timelineComponent })
}, },
store: () => store, store: () => store,

View file

@ -1,4 +1,5 @@
import { get } from '../../_utils/lodash-lite' import { get } from '../../_utils/lodash-lite'
import { mark, stop } from '../../_utils/marks'
const MIN_PREFIX_LENGTH = 2 const MIN_PREFIX_LENGTH = 2
// Technically mastodon accounts allow dots, but it would be weird to do an autosuggest search if it ends with a dot. // Technically mastodon accounts allow dots, but it would be weird to do an autosuggest search if it ends with a dot.
@ -17,6 +18,7 @@ function computeForAutosuggest (store, key, defaultValue) {
} }
export function autosuggestComputations (store) { export function autosuggestComputations (store) {
mark('autosuggestComputations')
computeForAutosuggest(store, 'composeFocused', false) computeForAutosuggest(store, 'composeFocused', false)
computeForAutosuggest(store, 'composeSelectionStart', 0) computeForAutosuggest(store, 'composeSelectionStart', 0)
computeForAutosuggest(store, 'autosuggestSelected', 0) computeForAutosuggest(store, 'autosuggestSelected', 0)
@ -59,4 +61,5 @@ export function autosuggestComputations (store) {
!!(composeFocused && autosuggestSearchText && autosuggestNumSearchResults) !!(composeFocused && autosuggestSearchText && autosuggestNumSearchResults)
) )
) )
stop('autosuggestComputations')
} }

View file

@ -1,11 +1,7 @@
import { instanceComputations } from './instanceComputations' import { instanceComputations } from './instanceComputations'
import { timelineComputations } from './timelineComputations'
import { navComputations } from './navComputations' import { navComputations } from './navComputations'
import { autosuggestComputations } from './autosuggestComputations'
export function computations (store) { export function computations (store) {
instanceComputations(store) instanceComputations(store)
timelineComputations(store)
navComputations(store) navComputations(store)
autosuggestComputations(store)
} }

View file

@ -1,4 +1,5 @@
import { DEFAULT_THEME } from '../../_utils/themeEngine' import { DEFAULT_THEME } from '../../_utils/themeEngine'
import { mark, stop } from '../../_utils/marks'
function computeForInstance (store, computedKey, key, defaultValue) { function computeForInstance (store, computedKey, key, defaultValue) {
store.compute(computedKey, store.compute(computedKey,
@ -7,6 +8,7 @@ function computeForInstance (store, computedKey, key, defaultValue) {
} }
export function instanceComputations (store) { export function instanceComputations (store) {
mark('instanceComputations')
computeForInstance(store, 'currentTheme', 'instanceThemes', DEFAULT_THEME) computeForInstance(store, 'currentTheme', 'instanceThemes', DEFAULT_THEME)
computeForInstance(store, 'currentVerifyCredentials', 'verifyCredentials', null) computeForInstance(store, 'currentVerifyCredentials', 'verifyCredentials', null)
computeForInstance(store, 'currentInstanceInfo', 'instanceInfos', null) computeForInstance(store, 'currentInstanceInfo', 'instanceInfos', null)
@ -59,4 +61,6 @@ export function instanceComputations (store) {
(currentInstanceInfo && currentInstanceInfo.max_toot_chars) || 500 (currentInstanceInfo && currentInstanceInfo.max_toot_chars) || 500
) )
) )
stop('instanceComputations')
} }

View file

@ -0,0 +1,10 @@
// like loggedInObservers.js, these can be lazy-loaded once the user is actually logged in
import { timelineComputations } from './timelineComputations'
import { autosuggestComputations } from './autosuggestComputations'
import { store } from '../store'
export function loggedInComputations () {
timelineComputations(store)
autosuggestComputations(store)
}

View file

@ -1,4 +1,8 @@
import { mark, stop } from '../../_utils/marks'
export function navComputations (store) { export function navComputations (store) {
mark('navComputations')
store.compute( store.compute(
'pinnedListTitle', 'pinnedListTitle',
['lists', 'pinnedPage'], ['lists', 'pinnedPage'],
@ -89,4 +93,6 @@ export function navComputations (store) {
] ]
} }
) )
stop('navComputations')
} }

View file

@ -9,6 +9,7 @@ import {
NOTIFICATION_POLLS, NOTIFICATION_POLLS,
NOTIFICATION_MENTIONS NOTIFICATION_MENTIONS
} from '../../_static/instanceSettings' } from '../../_static/instanceSettings'
import { mark, stop } from '../../_utils/marks'
function computeForTimeline (store, key, defaultValue) { function computeForTimeline (store, key, defaultValue) {
store.compute(key, store.compute(key,
@ -45,6 +46,7 @@ function computeNotificationFilter (store, computationName, key) {
} }
export function timelineComputations (store) { export function timelineComputations (store) {
mark('timelineComputations')
computeForTimeline(store, 'timelineItemSummaries', null) computeForTimeline(store, 'timelineItemSummaries', null)
computeForTimeline(store, 'timelineItemSummariesToAdd', null) computeForTimeline(store, 'timelineItemSummariesToAdd', null)
computeForTimeline(store, 'runningUpdate', false) computeForTimeline(store, 'runningUpdate', false)
@ -191,4 +193,5 @@ export function timelineComputations (store) {
['numberOfFollowRequests'], ['numberOfFollowRequests'],
(numberOfFollowRequests) => !!numberOfFollowRequests (numberOfFollowRequests) => !!numberOfFollowRequests
) )
stop('timelineComputations')
} }

View file

@ -0,0 +1,8 @@
import { loggedInComputations } from './computations/loggedInComputations'
import { loggedInObservers } from './observers/loggedInObservers'
import { loggedInMixins } from './mixins/loggedInMixins'
console.log('imported logged in observers and computations')
loggedInMixins()
loggedInComputations()
loggedInObservers()

View file

@ -0,0 +1,27 @@
import { get } from '../../_utils/lodash-lite'
export function composeMixins (Store) {
Store.prototype.setComposeData = function (realm, obj) {
const { composeData, currentInstance } = this.get()
const instanceNameData = composeData[currentInstance] = composeData[currentInstance] || {}
instanceNameData[realm] = Object.assign(
instanceNameData[realm] || {},
{ ts: Date.now() },
obj
)
this.set({ composeData })
}
Store.prototype.getComposeData = function (realm, key) {
const { composeData, currentInstance } = this.get()
return get(composeData, [currentInstance, realm, key])
}
Store.prototype.clearComposeData = function (realm) {
const { composeData, currentInstance } = this.get()
if (composeData && composeData[currentInstance]) {
delete composeData[currentInstance][realm]
}
this.set({ composeData })
}
}

View file

@ -1,32 +1,6 @@
import { get } from '../../_utils/lodash-lite' import { get } from '../../_utils/lodash-lite'
export function instanceMixins (Store) { export function instanceMixins (Store) {
Store.prototype.setComposeData = function (realm, obj) {
const { composeData, currentInstance } = this.get()
const instanceNameData = composeData[currentInstance] = composeData[currentInstance] || {}
instanceNameData[realm] = Object.assign(
instanceNameData[realm] || {},
{ ts: Date.now() },
obj
)
this.set({ composeData })
}
Store.prototype.getComposeData = function (realm, key) {
const { composeData, currentInstance } = this.get()
return composeData[currentInstance] &&
composeData[currentInstance][realm] &&
composeData[currentInstance][realm][key]
}
Store.prototype.clearComposeData = function (realm) {
const { composeData, currentInstance } = this.get()
if (composeData && composeData[currentInstance]) {
delete composeData[currentInstance][realm]
}
this.set({ composeData })
}
Store.prototype.getInstanceSetting = function (instanceName, settingName, defaultValue) { Store.prototype.getInstanceSetting = function (instanceName, settingName, defaultValue) {
const { instanceSettings } = this.get() const { instanceSettings } = this.get()
return get(instanceSettings, [instanceName, settingName], defaultValue) return get(instanceSettings, [instanceName, settingName], defaultValue)

View file

@ -0,0 +1,12 @@
import { timelineMixins } from './timelineMixins'
import { statusMixins } from './statusMixins'
import { autosuggestMixins } from './autosuggestMixins'
import { composeMixins } from './composeMixins'
import { PinaforeStore as Store } from '../store'
export function loggedInMixins () {
composeMixins(Store)
timelineMixins(Store)
statusMixins(Store)
autosuggestMixins(Store)
}

View file

@ -1,11 +1,5 @@
import { timelineMixins } from './timelineMixins'
import { instanceMixins } from './instanceMixins' import { instanceMixins } from './instanceMixins'
import { statusMixins } from './statusMixins'
import { autosuggestMixins } from './autosuggestMixins'
export function mixins (Store) { export function mixins (Store) {
instanceMixins(Store) instanceMixins(Store)
timelineMixins(Store)
statusMixins(Store)
autosuggestMixins(Store)
} }

View file

@ -8,7 +8,7 @@ import { cleanup } from './cleanup'
// These observers can be lazy-loaded when the user is actually logged in. // These observers can be lazy-loaded when the user is actually logged in.
// Prevents circular dependencies and reduces the size of main.js // Prevents circular dependencies and reduces the size of main.js
export default function loggedInObservers () { export function loggedInObservers () {
instanceObservers() instanceObservers()
timelineObservers() timelineObservers()
notificationObservers() notificationObservers()

View file

@ -1,15 +1,12 @@
import { importLoggedInObservers } from '../../_utils/asyncModules' import { importLoggedInStoreExtensions } from '../../_utils/asyncModules'
let observedOnce = false
// An observer that calls an observer... this is a bit weird, but it eliminates // An observer that calls an observer... this is a bit weird, but it eliminates
// circular dependencies and also allows us to lazy load observers that are // circular dependencies and also allows us to lazy load observers/computations
// only needed when you're logged in. // that are only needed when you're logged in.
export function setupLoggedInObservers (store) { export function setupLoggedInObservers (store) {
store.observe('isUserLoggedIn', isUserLoggedIn => { store.observe('isUserLoggedIn', isUserLoggedIn => {
if (isUserLoggedIn && !observedOnce) { if (isUserLoggedIn) {
importLoggedInObservers().then(loggedInObservers => loggedInObservers()) importLoggedInStoreExtensions()
observedOnce = true
} }
}) })
} }

View file

@ -67,7 +67,7 @@ const nonPersistedState = {
const state = Object.assign({}, persistedState, nonPersistedState) const state = Object.assign({}, persistedState, nonPersistedState)
const keysToStoreInLocalStorage = new Set(Object.keys(persistedState)) const keysToStoreInLocalStorage = new Set(Object.keys(persistedState))
class PinaforeStore extends LocalStorageStore { export class PinaforeStore extends LocalStorageStore {
constructor (state) { constructor (state) {
super(state, keysToStoreInLocalStorage) super(state, keysToStoreInLocalStorage)
} }

View file

@ -24,9 +24,9 @@ export const importDatabase = () => import(
/* webpackChunkName: 'database.js' */ '../_database/databaseApis.js' /* webpackChunkName: 'database.js' */ '../_database/databaseApis.js'
) )
export const importLoggedInObservers = () => import( export const importLoggedInStoreExtensions = () => import(
/* webpackChunkName: 'loggedInObservers.js' */ '../_store/observers/loggedInObservers.js' /* webpackChunkName: 'loggedInStoreExtensions.js' */ '../_store/loggedInStoreExtensions.js'
).then(getDefault) )
export const importNavShortcuts = () => import( export const importNavShortcuts = () => import(
/* webpackChunkName: 'NavShortcuts' */ '../_components/NavShortcuts.html' /* webpackChunkName: 'NavShortcuts' */ '../_components/NavShortcuts.html'

View file

@ -3,12 +3,13 @@
import { store } from './_store/store' import { store } from './_store/store'
import { goto } from '../../__sapper__/client' import { goto } from '../../__sapper__/client'
import { decodeURIComponentWithPluses } from './_utils/decodeURIComponentWithPluses' import { decodeURIComponentWithPluses } from './_utils/decodeURIComponentWithPluses'
import { importLoggedInStoreExtensions } from './_utils/asyncModules'
const SHARE_KEYS = ['title', 'text', 'url'] const SHARE_KEYS = ['title', 'text', 'url']
export default { export default {
store: () => store, store: () => store,
oncreate () { async oncreate () {
const params = new URLSearchParams(location.search) const params = new URLSearchParams(location.search)
const text = SHARE_KEYS const text = SHARE_KEYS
@ -16,6 +17,7 @@
.filter(Boolean) .filter(Boolean)
.join(' ') .join(' ')
await importLoggedInStoreExtensions()
this.store.set({ openShareDialog: true }) this.store.set({ openShareDialog: true })
this.store.clearComposeData('dialog') this.store.clearComposeData('dialog')
this.store.setComposeData('dialog', { text }) this.store.setComposeData('dialog', { text })