feat: render app shell with ssr to improve loading experience (#448)
This commit is contained in:
parent
b545efeacc
commit
9395b7031e
35 changed files with 169 additions and 127 deletions
|
@ -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 })
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
14
composables/hydration.ts
Normal 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
|
||||
})
|
|
@ -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 = [
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue