Skip to content

Commit

Permalink
feat: ssr handlers (vitest-dev#1060)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Dec 23, 2021
1 parent 6832422 commit 3aa4997
Show file tree
Hide file tree
Showing 17 changed files with 1,369 additions and 1,264 deletions.
3 changes: 3 additions & 0 deletions meta/packages.ts
Expand Up @@ -34,6 +34,9 @@ export const packages: PackageManifest[] = [
'@vueuse/core',
'@vueuse/shared',
'local-pkg',
'fs',
'path',
'url',
],
},
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -7,6 +7,7 @@
"author": "Anthony Fu<https://github.com/antfu>",
"scripts": {
"build": "nr update && esno scripts/build.ts",
"watch": "esno scripts/build.ts --watch",
"build:redirects": "esno scripts/redirects.ts",
"build:rollup": "cross-env NODE_OPTIONS=\"--max-old-space-size=6144\" rollup -c",
"build:types": "tsc --emitDeclarationOnly && nr types:fix",
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Expand Up @@ -106,3 +106,4 @@ export * from './useWindowScroll'
export * from './useWindowSize'
export * from './types'
export * from '@vueuse/shared'
export * from './ssr-handlers'
1 change: 1 addition & 0 deletions packages/core/nuxt.mjs
Expand Up @@ -26,6 +26,7 @@ const disabled = [
export default function() {
const { nuxt } = this

// eslint-disable-next-line no-console
console.log('[@vueuse/core] Installing Nuxt module with `@vueuse/core/nuxt` is deprecated. Please use `@vueuse/nuxt` instead.')

// opt-out Vite deps optimization for VueUse
Expand Down
38 changes: 38 additions & 0 deletions packages/core/ssr-handlers.ts
@@ -0,0 +1,38 @@
import type { Awaitable } from '@vueuse/shared'

export interface StorageLikeAsync {
getItem(key: string): Awaitable<string | null>
setItem(key: string, value: string): Awaitable<void>
removeItem(key: string): Awaitable<void>
}

export interface StorageLike {
getItem(key: string): string | null
setItem(key: string, value: string): void
removeItem(key: string): void
}

/**
* @expiremental The API is not finalized yet. It might not follow semver.
*/
export interface SSRHandlersMap {
getDefaultStorage: () => StorageLike | undefined
getDefaultStorageAsync: () => StorageLikeAsync | undefined
updateHTMLAttrs: (selector: string, attribute: string, value: string) => void
}

const globalKey = '__vueuse_ssr_handlers__'
// @ts-expect-error
globalThis[globalKey] = globalThis[globalKey] || {}
// @ts-expect-error
const handlers: Partial<SSRHandlersMap> = globalThis[globalKey]

export function getSSRHandler<T extends keyof SSRHandlersMap>(key: T, fallback: SSRHandlersMap[T]): SSRHandlersMap[T]
export function getSSRHandler<T extends keyof SSRHandlersMap>(key: T, fallback: SSRHandlersMap[T] | undefined): SSRHandlersMap[T] | undefined
export function getSSRHandler<T extends keyof SSRHandlersMap>(key: T, fallback?: SSRHandlersMap[T]): SSRHandlersMap[T] | undefined {
return handlers[key] as SSRHandlersMap[T] || fallback
}

export function setSSRHandler<T extends keyof SSRHandlersMap>(key: T, fn: SSRHandlersMap[T]) {
handlers[key] = fn
}
54 changes: 31 additions & 23 deletions packages/core/useColorMode/index.ts
@@ -1,7 +1,9 @@
import type { Ref } from 'vue-demi'
import { computed, ref, watch } from 'vue-demi'
import { tryOnMounted } from '@vueuse/shared'
import type { StorageLike, StorageOptions } from '../useStorage'
import type { StorageLike } from '../ssr-handlers'
import { getSSRHandler } from '../ssr-handlers'
import type { StorageOptions } from '../useStorage'
import { useStorage } from '../useStorage'
import { defaultWindow } from '../_configurable'
import { usePreferredDark } from '../usePreferredDark'
Expand Down Expand Up @@ -71,7 +73,7 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
selector = 'html',
attribute = 'class',
window = defaultWindow,
storage = defaultWindow?.localStorage,
storage = getSSRHandler('getDefaultStorage', () => defaultWindow?.localStorage)(),
storageKey = 'vueuse-color-scheme',
listenToStorageChanges = true,
storageRef,
Expand Down Expand Up @@ -102,26 +104,32 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
},
})

function defaultOnChanged(value: T | BasicColorSchema) {
const el = window?.document.querySelector(selector)
if (!el)
return

if (attribute === 'class') {
const current = (modes[value] || '').split(/\s/g)
Object.values(modes)
.flatMap(i => (i || '').split(/\s/g))
.filter(Boolean)
.forEach((v) => {
if (current.includes(v))
el.classList.add(v)
else
el.classList.remove(v)
})
}
else {
el.setAttribute(attribute, value)
}
const updateHTMLAttrs = getSSRHandler(
'updateHTMLAttrs',
(selector, attribute, value) => {
const el = window?.document.querySelector(selector)
if (!el)
return

if (attribute === 'class') {
const current = value.split(/\s/g)
Object.values(modes)
.flatMap(i => (i || '').split(/\s/g))
.filter(Boolean)
.forEach((v) => {
if (current.includes(v))
el.classList.add(v)
else
el.classList.remove(v)
})
}
else {
el.setAttribute(attribute, value)
}
})

function defaultOnChanged(mode: T | BasicColorSchema) {
updateHTMLAttrs(selector, attribute, modes[mode] ?? mode)
}

function onChanged(mode: T | BasicColorSchema) {
Expand All @@ -131,7 +139,7 @@ export function useColorMode<T extends string = BasicColorSchema>(options: UseCo
defaultOnChanged(mode)
}

watch(state, onChanged, { flush: 'post' })
watch(state, onChanged, { flush: 'post', immediate: true })

tryOnMounted(() => onChanged(state.value))

Expand Down
19 changes: 6 additions & 13 deletions packages/core/useStorage/index.ts
Expand Up @@ -2,6 +2,8 @@ import type { Awaitable, ConfigurableEventFilter, ConfigurableFlush, MaybeRef, R
import { watchWithFilter } from '@vueuse/shared'
import type { Ref } from 'vue-demi'
import { ref, shallowRef, unref } from 'vue-demi'
import type { StorageLike } from '../ssr-handlers'
import { getSSRHandler } from '../ssr-handlers'
import { useEventListener } from '../useEventListener'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
Expand All @@ -17,18 +19,6 @@ export type SerializerAsync<T> = {
write(value: T): Awaitable<string>
}

export interface StorageLikeAsync {
getItem(key: string): Awaitable<string | null>
setItem(key: string, value: string): Awaitable<void>
removeItem(key: string): Awaitable<void>
}

export interface StorageLike {
getItem(key: string): string | null
setItem(key: string, value: string): void
removeItem(key: string): void
}

export const StorageSerializers: Record<'boolean' | 'object' | 'number' | 'any' | 'string' | 'map' | 'set', Serializer<any>> = {
boolean: {
read: (v: any) => v === 'true',
Expand Down Expand Up @@ -120,7 +110,7 @@ export function useStorage<T = unknown> (key: string, initialValue: MaybeRef<nul
export function useStorage<T extends(string|number|boolean|object|null)> (
key: string,
initialValue: MaybeRef<T>,
storage: StorageLike | undefined = defaultWindow?.localStorage,
storage: StorageLike | undefined = getSSRHandler('getDefaultStorage', () => defaultWindow?.localStorage)(),
options: StorageOptions<T> = {},
): RemovableRef<T> {
const {
Expand Down Expand Up @@ -153,6 +143,9 @@ export function useStorage<T extends(string|number|boolean|object|null)> (
if (writeDefaults && rawInit !== null)
storage.setItem(key, serializer.write(rawInit))
}
else if (typeof rawValue !== 'string') {
data.value = rawValue
}
else {
data.value = serializer.read(rawValue)
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/useStorageAsync/index.ts
Expand Up @@ -2,7 +2,9 @@ import type { MaybeRef, RemovableRef } from '@vueuse/shared'
import { watchWithFilter } from '@vueuse/shared'
import type { Ref } from 'vue-demi'
import { ref, shallowRef, unref } from 'vue-demi'
import type { SerializerAsync, StorageLikeAsync, StorageOptions } from '../useStorage'
import type { StorageLikeAsync } from '../ssr-handlers'
import { getSSRHandler } from '../ssr-handlers'
import type { SerializerAsync, StorageOptions } from '../useStorage'
import { StorageSerializers } from '../useStorage'
import { useEventListener } from '../useEventListener'
import { guessSerializerType } from '../useStorage/guess'
Expand Down Expand Up @@ -33,7 +35,7 @@ export function useStorageAsync<T = unknown> (key: string, initialValue: MaybeRe
export function useStorageAsync<T extends(string|number|boolean|object|null)> (
key: string,
initialValue: MaybeRef<T>,
storage: StorageLikeAsync | undefined = defaultWindow?.localStorage,
storage: StorageLikeAsync | undefined = getSSRHandler('getDefaultStorageAsync', () => defaultWindow?.localStorage)(),
options: StorageAsyncOptions<T> = {},
): RemovableRef<T> {
const {
Expand Down
111 changes: 66 additions & 45 deletions packages/nuxt/index.ts
Expand Up @@ -4,9 +4,7 @@ import { fileURLToPath } from 'url'
import { isPackageExists } from 'local-pkg'
import type { PackageIndexes } from '../../meta/types'

const _dirname = typeof __dirname === 'undefined'
? dirname(fileURLToPath(import.meta.url))
: __dirname
const _dirname = dirname(fileURLToPath(import.meta.url))

const disabledFunctions = [
'useFetch',
Expand All @@ -29,6 +27,19 @@ const packages = [

const fullPackages = packages.map(p => `@vueuse/${p}`)

export interface VueUseNuxtOptions {
/**
* @default true
*/
autoImports?: boolean

/**
* @expiremental
* @default false
*/
ssrHandlers?: boolean
}

/**
* Auto import for VueUse in Nuxt
* Usage:
Expand All @@ -45,6 +56,8 @@ const fullPackages = packages.map(p => `@vueuse/${p}`)
function VueUseModule(this: any) {
const { nuxt } = this

const options: VueUseNuxtOptions = nuxt.options.vueuse || {}

// opt-out Vite deps optimization for VueUse
nuxt.hook('vite:extend', ({ config }: any) => {
config.optimizeDeps = config.optimizeDeps || {}
Expand All @@ -59,50 +72,58 @@ function VueUseModule(this: any) {

let indexes: PackageIndexes | undefined

// auto Import
nuxt.hook('autoImports:sources', (sources: any[]) => {
if (sources.find(i => fullPackages.includes(i.from)))
return

if (!indexes) {
try {
indexes = JSON.parse(fs.readFileSync(resolve(_dirname, './indexes.json'), 'utf-8'))
indexes?.functions.forEach((i) => {
if (i.package === 'shared')
i.package = 'core'
})
if (options.ssrHandlers) {
const pluginPath = resolve(_dirname, './ssr-plugin.mjs')
nuxt.options.plugins = nuxt.options.plugins || []
nuxt.options.plugins.push(pluginPath)
}

if (options.autoImports !== false) {
// auto Import
nuxt.hook('autoImports:sources', (sources: any[]) => {
if (sources.find(i => fullPackages.includes(i.from)))
return

if (!indexes) {
try {
indexes = JSON.parse(fs.readFileSync(resolve(_dirname, './indexes.json'), 'utf-8'))
indexes?.functions.forEach((i) => {
if (i.package === 'shared')
i.package = 'core'
})
}
catch (e) {
throw new Error('[@vueuse/nuxt] Failed to load indexes.json')
}
}
catch (e) {
throw new Error('[@vueuse/nuxt] Failed to load indexes.json')
}
}

if (!indexes)
return

for (const pkg of packages) {
if (pkg === 'shared')
continue

if (!isPackageExists(`@vueuse/${pkg}`))
continue

const functions = indexes
.functions
.filter(i => (i.package === 'core' || i.package === 'shared') && !i.internal)

if (functions.length) {
sources.push({
from: `@vueuse/${pkg}`,
names: indexes
.functions
.filter(i => i.package === pkg && !i.internal)
.map(i => i.name)
.filter(i => i.length >= 4 && !disabledFunctions.includes(i)),
})

if (!indexes)
return

for (const pkg of packages) {
if (pkg === 'shared')
continue

if (!isPackageExists(`@vueuse/${pkg}`))
continue

const functions = indexes
.functions
.filter(i => (i.package === 'core' || i.package === 'shared') && !i.internal)

if (functions.length) {
sources.push({
from: `@vueuse/${pkg}`,
names: indexes
.functions
.filter(i => i.package === pkg && !i.internal)
.map(i => i.name)
.filter(i => i.length >= 4 && !disabledFunctions.includes(i)),
})
}
}
}
})
})
}
}

export default VueUseModule
12 changes: 6 additions & 6 deletions packages/nuxt/package.json
Expand Up @@ -9,6 +9,10 @@
"nuxt3",
"nuxt-module"
],
"homepage": "https://github.com/vueuse/vueuse/tree/main/packages/nuxt#readme",
"bugs": {
"url": "https://github.com/vueuse/vueuse/issues"
},
"license": "MIT",
"repository": {
"type": "git",
Expand All @@ -17,6 +21,7 @@
},
"funding": "https://github.com/sponsors/antfu",
"author": "Anthony Fu <https://github.com/antfu>",
"sideEffects": false,
"exports": {
".": {
"import": "./index.mjs",
Expand All @@ -26,13 +31,8 @@
"./*": "./*"
},
"main": "./index.cjs",
"types": "./index.d.ts",
"module": "./index.mjs",
"sideEffects": false,
"bugs": {
"url": "https://github.com/vueuse/vueuse/issues"
},
"homepage": "https://github.com/vueuse/vueuse/tree/main/packages/nuxt#readme",
"types": "./index.d.ts",
"dependencies": {
"@vueuse/core": "workspace:*",
"local-pkg": "^0.4.0",
Expand Down

0 comments on commit 3aa4997

Please sign in to comment.