feat: render app shell with ssr to improve loading experience (#448)

This commit is contained in:
Daniel Roe 2022-12-17 16:55:29 +00:00 committed by GitHub
parent b545efeacc
commit 9395b7031e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 169 additions and 127 deletions

View file

@ -5,7 +5,7 @@ const cache = new LRU<string, any>({
max: 1000,
})
if (process.dev)
if (process.dev && process.client)
// eslint-disable-next-line no-console
console.log({ cache })

View file

@ -2,7 +2,7 @@ import type { Emoji } from 'masto'
import type { Node } from 'ultrahtml'
import { TEXT_NODE, parse, render, walkSync } from 'ultrahtml'
const decoder = document.createElement('textarea')
const decoder = process.client ? document.createElement('textarea') : null as any as HTMLTextAreaElement
function decode(text: string) {
decoder.innerHTML = text
return decoder.value

View file

@ -13,7 +13,7 @@ export function getDefaultFeatureFlags(): FeatureFlags {
}
}
export const currentUserFeatureFlags = useUserLocalStorage(STORAGE_KEY_FEATURE_FLAGS, getDefaultFeatureFlags)
export const currentUserFeatureFlags = process.server ? computed(getDefaultFeatureFlags) : useUserLocalStorage(STORAGE_KEY_FEATURE_FLAGS, getDefaultFeatureFlags)
export function useFeatureFlags() {
const featureFlags = currentUserFeatureFlags.value

14
composables/hydration.ts Normal file
View file

@ -0,0 +1,14 @@
export const isHydrated = computed(() => {
if (process.server)
return false
const nuxtApp = useNuxtApp()
if (!nuxtApp.isHydrating)
return false
const hydrated = ref(false)
nuxtApp.hooks.hookOnce('app:suspense:resolve', () => {
hydrated.value = true
})
return hydrated
})

View file

@ -1,12 +1,11 @@
import type { Ref } from 'vue'
import type { Account, MastoClient, Relationship, Status } from 'masto'
import type { Account, Relationship, Status } from 'masto'
import { withoutProtocol } from 'ufo'
import type { ElkMasto } from '~/types'
export const useMasto = () => useNuxtApp().$masto.api as MastoClient
export const useMasto = () => useNuxtApp().$masto as ElkMasto
export const setMasto = (masto: MastoClient) => {
useNuxtApp().$masto?.replace(masto)
}
export const isMastoInitialised = computed(() => process.client && useMasto().loggedIn.value)
// @unocss-include
export const STATUS_VISIBILITIES = [

View file

@ -56,23 +56,25 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>, stream?: WsEvent
bound.update()
}
useIntervalFn(() => {
bound.update()
}, 1000)
if (process.client) {
useIntervalFn(() => {
bound.update()
}, 1000)
watch(
() => [isInScreen, state],
() => {
if (
isInScreen
watch(
() => [isInScreen, state],
() => {
if (
isInScreen
&& state.value === 'idle'
// No new content is loaded when the keepAlive page enters the background
&& deactivated.value === false
)
loadNext()
},
{ immediate: true },
)
)
loadNext()
},
{ immediate: true },
)
}
return {
items,

View file

@ -19,22 +19,25 @@ export function setupPageHeader() {
export async function setupI18n() {
const { locale, setLocale, locales } = useI18n()
const isFirstVisit = !window.localStorage.getItem(STORAGE_KEY_LANG)
const localeStorage = useLocalStorage(STORAGE_KEY_LANG, locale.value)
const nuxtApp = useNuxtApp()
nuxtApp.hook('app:suspense:resolve', async () => {
const isFirstVisit = process.server ? false : !window.localStorage.getItem(STORAGE_KEY_LANG)
const localeStorage = process.server ? ref('en-US') : useLocalStorage(STORAGE_KEY_LANG, locale.value)
if (isFirstVisit) {
const userLang = (navigator.language || 'en-US').toLowerCase()
// cause vue-i18n not explicit export LocaleObject type
const supportLocales = unref(locales) as { code: string }[]
const lang = supportLocales.find(locale => userLang.startsWith(locale.code.toLowerCase()))?.code
if (isFirstVisit) {
const userLang = (navigator.language || 'en-US').toLowerCase()
// cause vue-i18n not explicit export LocaleObject type
const supportLocales = unref(locales) as { code: string }[]
const lang = supportLocales.find(locale => userLang.startsWith(locale.code.toLowerCase()))?.code
|| supportLocales.find(locale => userLang.startsWith(locale.code.split('-')[0]))?.code
localeStorage.value = lang || 'en-US'
}
localeStorage.value = lang || 'en-US'
}
if (localeStorage.value !== locale.value)
await setLocale(localeStorage.value)
if (localeStorage.value !== locale.value)
await setLocale(localeStorage.value)
watchEffect(() => {
localeStorage.value = locale.value
watchEffect(() => {
localeStorage.value = locale.value
})
})
}

View file

@ -2,7 +2,7 @@ import type { Account, Status } from 'masto'
import { STORAGE_KEY_DRAFTS } from '~/constants'
import type { Draft, DraftMap } from '~/types'
export const currentUserDrafts = useUserLocalStorage<DraftMap>(STORAGE_KEY_DRAFTS, () => ({}))
export const currentUserDrafts = process.server ? computed<DraftMap>(() => ({})) : useUserLocalStorage<DraftMap>(STORAGE_KEY_DRAFTS, () => ({}))
export function getDefaultDraft(options: Partial<Draft['params'] & Omit<Draft, 'params'>> = {}): Draft {
const {

View file

@ -8,10 +8,9 @@ export interface TranslationResponse {
}
}
const config = useRuntimeConfig()
export const languageCode = process.server ? 'en' : navigator.language.replace(/-.*$/, '')
export async function translateText(text: string, from?: string | null, to?: string) {
const config = useRuntimeConfig()
const { translatedText } = await $fetch<TranslationResponse>(config.public.translateApi, {
method: 'POST',
body: {
@ -41,7 +40,7 @@ export function useTranslation(status: Status) {
}
return {
enabled: !!config.public.translateApi,
enabled: !!useRuntimeConfig().public.translateApi,
toggle,
translation,
}

View file

@ -73,8 +73,6 @@ export async function loginTo(user?: Omit<UserLogin, 'account'> & { account?: Ac
}
}
setMasto(masto)
if ('server' in route.params && user?.token) {
await router.push({
...route,
@ -117,6 +115,7 @@ const notifications = reactive<Record<string, undefined | [Promise<WsEvents>, nu
export const useNotifications = () => {
const id = currentUser.value?.account.id
const masto = useMasto()
const clearNotifications = () => {
if (!id || !notifications[id])
@ -125,10 +124,9 @@ export const useNotifications = () => {
}
async function connect(): Promise<void> {
if (!id || notifications[id] || !currentUser.value?.token)
if (!isMastoInitialised.value || !id || notifications[id] || !currentUser.value?.token)
return
const masto = useMasto()
const stream = masto.stream.streamUser()
notifications[id] = [stream, 0]
;(await stream).on('notification', () => {