mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(bridge): add support for legacy composition api helpers (#584)
This commit is contained in:
parent
7fdcee3252
commit
ad9d2d1906
@ -16,6 +16,11 @@ export function setupAppBridge (_options: any) {
|
||||
nuxt.options.alias.vue = nuxt.options.alias.vue ||
|
||||
resolveModule('vue/dist/vue.runtime.esm.js', { paths: nuxt.options.modulesDir })
|
||||
|
||||
// Deprecate various Nuxt options
|
||||
if (nuxt.options.globalName !== 'nuxt') {
|
||||
throw new Error('Custom global name is not supported by @nuxt/bridge.')
|
||||
}
|
||||
|
||||
// Fix wp4 esm
|
||||
nuxt.hook('webpack:config', (configs) => {
|
||||
for (const config of configs.filter(c => c.module)) {
|
||||
|
102
packages/bridge/src/capi-legacy-key-plugin.ts
Normal file
102
packages/bridge/src/capi-legacy-key-plugin.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import crypto from 'crypto'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
import { parse } from 'acorn'
|
||||
import MagicString from 'magic-string'
|
||||
import { walk } from 'estree-walker'
|
||||
import { parseQuery, parseURL } from 'ufo'
|
||||
|
||||
function createKey (
|
||||
source: string,
|
||||
method: crypto.BinaryToTextEncoding = 'base64'
|
||||
) {
|
||||
const hash = crypto.createHash('md5')
|
||||
hash.update(source)
|
||||
return hash.digest(method).toString()
|
||||
}
|
||||
|
||||
const keyedFunctions =
|
||||
/(useStatic|shallowSsrRef|ssrPromise|ssrRef|reqSsrRef|useAsync)/
|
||||
|
||||
export const KeyPlugin = createUnplugin(() => {
|
||||
return {
|
||||
name: 'nuxt-legacy-capi-key-transform',
|
||||
enforce: 'pre',
|
||||
transformInclude (id) {
|
||||
const { pathname, search } = parseURL(id)
|
||||
const query = parseQuery(search)
|
||||
|
||||
if (id.includes('node_modules')) {
|
||||
return false
|
||||
}
|
||||
|
||||
// vue files
|
||||
if (pathname.endsWith('.vue') && (query.type === 'script' || !search)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// js files
|
||||
if (pathname.match(/\.((c|m)?j|t)sx?/g)) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
transform (code, id) {
|
||||
if (!keyedFunctions.test(code)) { return null }
|
||||
|
||||
try {
|
||||
const { 0: script = code, index: codeIndex = 0 } =
|
||||
code.match(/(?<=<script[^>]*>)[\S\s.]*?(?=<\/script>)/) || []
|
||||
const ast = parse(script, { ecmaVersion: 2020, sourceType: 'module' })
|
||||
const s = new MagicString(code)
|
||||
|
||||
walk(ast, {
|
||||
enter (node) {
|
||||
const { end } = node as unknown as {
|
||||
end: number
|
||||
}
|
||||
const { callee, arguments: args = [] } = node as {
|
||||
callee?: {
|
||||
type?: string
|
||||
name?: string
|
||||
property?: { type: string; name: string }
|
||||
}
|
||||
arguments?: any[]
|
||||
}
|
||||
if (
|
||||
callee?.type === 'Identifier' ||
|
||||
callee?.property?.type === 'Identifier'
|
||||
) {
|
||||
let method: crypto.BinaryToTextEncoding = 'base64'
|
||||
|
||||
switch (callee.name || callee.property?.name) {
|
||||
case 'useStatic':
|
||||
if (args.length > 2) { return }
|
||||
if (args.length === 2) {
|
||||
s.prependLeft(codeIndex + end - 1, ', undefined')
|
||||
}
|
||||
method = 'hex'
|
||||
break
|
||||
|
||||
case 'shallowSsrRef':
|
||||
case 'ssrPromise':
|
||||
case 'ssrRef':
|
||||
case 'reqSsrRef':
|
||||
case 'useAsync':
|
||||
if (args.length > 1) { return }
|
||||
break
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
s.appendLeft(
|
||||
codeIndex + end - 1,
|
||||
", '" + createKey(`${id}-${end}`, method) + "'"
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return s.toString()
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
})
|
@ -1,6 +1,8 @@
|
||||
import { useNuxt, addPluginTemplate, addPlugin } from '@nuxt/kit'
|
||||
import { useNuxt, addPlugin, addPluginTemplate, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
|
||||
import { resolve } from 'pathe'
|
||||
|
||||
import { distDir } from './dirs'
|
||||
import { KeyPlugin } from './capi-legacy-key-plugin'
|
||||
|
||||
export function setupCAPIBridge (_options: any) {
|
||||
const nuxt = useNuxt()
|
||||
@ -16,7 +18,10 @@ export function setupCAPIBridge (_options: any) {
|
||||
addPluginTemplate({ filename: 'capi.plugin.mjs', src: capiPluginPath })
|
||||
|
||||
// Add support for useNuxtApp
|
||||
addPlugin(resolve(distDir, 'runtime/app.plugin.mjs'))
|
||||
const appPlugin = addPluginTemplate(resolve(distDir, 'runtime/app.plugin.mjs'))
|
||||
nuxt.hook('modules:done', () => {
|
||||
nuxt.options.plugins.unshift(appPlugin)
|
||||
})
|
||||
|
||||
// Register Composition API before loading the rest of app
|
||||
nuxt.hook('webpack:config', (configs) => {
|
||||
@ -24,5 +29,14 @@ export function setupCAPIBridge (_options: any) {
|
||||
configs.forEach(config => config.entry.app.unshift(capiPluginPath))
|
||||
})
|
||||
|
||||
// Handle legacy `@nuxtjs/composition-api`
|
||||
nuxt.options.alias['@nuxtjs/composition-api'] = resolve(distDir, 'runtime/capi.legacy.mjs')
|
||||
nuxt.options.build.transpile.push('@nuxtjs/composition-api', '@vue/composition-api')
|
||||
addPlugin(resolve(distDir, 'runtime/capi.legacy.plugin.mjs'))
|
||||
|
||||
// Enable automatic ssrRef key generation
|
||||
addVitePlugin(KeyPlugin.vite())
|
||||
addWebpackPlugin(KeyPlugin.webpack())
|
||||
|
||||
// TODO: Add @nuxtjs/composition-api shims
|
||||
}
|
||||
|
606
packages/bridge/src/runtime/capi.legacy.mjs
Normal file
606
packages/bridge/src/runtime/capi.legacy.mjs
Normal file
@ -0,0 +1,606 @@
|
||||
import defu from 'defu'
|
||||
import { computed, customRef, getCurrentInstance as getVM, isReactive, isRef, onBeforeMount, onServerPrefetch, reactive, ref, set, shallowRef, toRaw, toRefs, watch } from '@vue/composition-api'
|
||||
import { useNuxtApp } from './app'
|
||||
|
||||
// Vue composition API export
|
||||
export {
|
||||
computed,
|
||||
createApp,
|
||||
createRef,
|
||||
customRef,
|
||||
defineAsyncComponent,
|
||||
del,
|
||||
effectScope,
|
||||
getCurrentInstance,
|
||||
getCurrentScope,
|
||||
h,
|
||||
inject,
|
||||
isRaw,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isRef,
|
||||
markRaw,
|
||||
nextTick,
|
||||
onActivated,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
onDeactivated,
|
||||
onErrorCaptured,
|
||||
onMounted,
|
||||
onScopeDispose,
|
||||
onServerPrefetch,
|
||||
onUnmounted,
|
||||
onUpdated,
|
||||
provide,
|
||||
proxyRefs,
|
||||
reactive,
|
||||
readonly,
|
||||
set,
|
||||
shallowReactive,
|
||||
shallowReadonly,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
toRef,
|
||||
toRefs,
|
||||
triggerRef,
|
||||
unref,
|
||||
useAttrs,
|
||||
useCssModule,
|
||||
useCSSModule,
|
||||
useSlots,
|
||||
version,
|
||||
warn,
|
||||
watch,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
watchSyncEffect
|
||||
} from '@vue/composition-api'
|
||||
|
||||
export { ref }
|
||||
|
||||
// Common deprecation utils
|
||||
// TODO: Add migration guide docs to @nuxtjs/composition-api
|
||||
const checkDocsMsg = 'Please see https://v3.nuxtjs.org for more information.'
|
||||
const msgPrefix = '[bridge] [legacy capi]'
|
||||
const unsupported = message => () => { throw new Error(`${msgPrefix} ${message} ${checkDocsMsg}`) }
|
||||
const _warned = {}
|
||||
const warnOnce = (id, message) => {
|
||||
if (!_warned[id]) {
|
||||
console.warn(msgPrefix, message)
|
||||
_warned[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Warn in case of having any imports from `@nuxtjs/composition-api`
|
||||
warnOnce('import', `\`@nuxtjs/composition-api\` is deprecated. ${checkDocsMsg}`)
|
||||
|
||||
// Stub functions that provided type support
|
||||
export const defineNuxtMiddleware = unsupported('You are using `defineNuxtMiddleware`, which can be replaced with `defineNuxtMiddleware` from `@nuxt/bridge`.')
|
||||
export const defineNuxtPlugin = unsupported('You are using `defineNuxtPlugin`, which can be replaced with `defineNuxtPlugin` from `@nuxt/bridge`.')
|
||||
|
||||
// Internal exports
|
||||
export const setMetaPlugin = unsupported('`setMetaPlugin` is an internal function that is no longer used.')
|
||||
export const setSSRContext = unsupported('`setSSRContext` is an internal function that is no longer used.')
|
||||
export const globalPlugin = unsupported('`globalPlugin` is an internal function that is no longer used.')
|
||||
|
||||
// Deprecated functions
|
||||
export const withContext = unsupported('`withContext` is a deprecated method that is no longer provided.')
|
||||
export const useStatic = unsupported('`useStatic` is a deprecated method that is no longer provided.')
|
||||
export const reqRef = unsupported('`reqRef` is a deprecated method that is no longer provided.')
|
||||
export const reqSsrRef = unsupported('`reqSsrRef` is no longer provided (`ssrRef` can be used instead).')
|
||||
|
||||
// ssrRef helpers
|
||||
const isProxyable = val => !!val && typeof val === 'object'
|
||||
const sanitise = val => (val && JSON.parse(JSON.stringify(val))) || val
|
||||
const getValue = val => val instanceof Function ? val() : val
|
||||
|
||||
export const ssrRef = (value, key) => {
|
||||
const vm = getVM()
|
||||
if (!vm) { throw new Error('ssrRef no longer supports global/ambient context and must be called within a setup() function') }
|
||||
|
||||
const ssrRefs = useSSRRefs()
|
||||
|
||||
let resolvedValue = isHMR() ? getValue(value) : ssrRefs[key] ?? getValue(value)
|
||||
|
||||
const _ref = ref(resolvedValue)
|
||||
if (process.client) { return _ref }
|
||||
|
||||
const setData = (key, val) => {
|
||||
ssrRefs[key] = sanitise(val)
|
||||
}
|
||||
|
||||
if (value instanceof Function) { setData(key, resolvedValue) }
|
||||
|
||||
const getProxy = (track, trigger, observable) =>
|
||||
new Proxy(observable, {
|
||||
get (target, prop) {
|
||||
track()
|
||||
if (isProxyable(target[prop])) { return getProxy(track, trigger, target[prop]) }
|
||||
return Reflect.get(target, prop)
|
||||
},
|
||||
set (obj, prop, newVal) {
|
||||
const result = Reflect.set(obj, prop, newVal)
|
||||
setData(key, resolvedValue)
|
||||
trigger()
|
||||
return result
|
||||
}
|
||||
})
|
||||
|
||||
const proxy = customRef((track, trigger) => ({
|
||||
get: () => {
|
||||
track()
|
||||
if (isProxyable(resolvedValue)) { return getProxy(track, trigger, resolvedValue) }
|
||||
return resolvedValue
|
||||
},
|
||||
set: (v) => {
|
||||
setData(key, v)
|
||||
resolvedValue = v
|
||||
trigger()
|
||||
}
|
||||
}))
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
export const shallowSsrRef = (value, key) => {
|
||||
const ssrRefs = useSSRRefs()
|
||||
|
||||
let resolvedValue = isHMR() ? getValue(value) : ssrRefs[key] ?? getValue(value)
|
||||
|
||||
const _ref = shallowRef(resolvedValue)
|
||||
if (process.client) { return _ref }
|
||||
|
||||
const setData = (key, val) => {
|
||||
ssrRefs[key] = sanitise(val)
|
||||
}
|
||||
|
||||
if (value instanceof Function) {
|
||||
setData(key, resolvedValue)
|
||||
}
|
||||
|
||||
return computed({
|
||||
get () {
|
||||
return resolvedValue
|
||||
},
|
||||
set (newValue) {
|
||||
setData(key, newValue)
|
||||
resolvedValue = newValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const ssrPromise = (value, key) => {
|
||||
warnOnce('ssrPromise', 'ssrPromise is deprecated. Please use an alternative implementation.')
|
||||
|
||||
const ssrRefs = useSSRRefs()
|
||||
const promise = Promise.resolve(isHMR() ? getValue(value) : ssrRefs[key] ?? getValue(value))
|
||||
|
||||
onServerPrefetch(async () => { ssrRefs[key] = sanitise(await promise) })
|
||||
return promise
|
||||
}
|
||||
|
||||
// Composition API functions
|
||||
export const onGlobalSetup = (fn) => {
|
||||
warnOnce('onGlobalSetup', '`onGlobalSetup` is deprecated and can be replaced with `defineNuxtPlugin` and `nuxt.provide`.')
|
||||
const app = useNuxtApp()
|
||||
app._setupFns.push(fn)
|
||||
}
|
||||
|
||||
export const useAsync = (cb, key) => {
|
||||
warnOnce('useAsync', 'You are using `useAsync`, which can be replaced with `useAsyncData` from `@nuxt/bridge`.')
|
||||
|
||||
const _ref = isRef(key) ? key : ssrRef(null, key)
|
||||
|
||||
if (!_ref.value || isHMR()) {
|
||||
const p = Promise.resolve(cb()).then(res => (_ref.value = res))
|
||||
onServerPrefetch(() => p)
|
||||
}
|
||||
|
||||
return _ref
|
||||
}
|
||||
|
||||
export const useContext = () => {
|
||||
warnOnce('useContext', 'You are using `useContext`, which can be replaced with `useNuxtApp` from `@nuxt/bridge`.')
|
||||
|
||||
const route = useRoute()
|
||||
const nuxt = useNuxtApp()
|
||||
|
||||
return {
|
||||
...nuxt.legacyNuxt.context,
|
||||
route: computed(() => route),
|
||||
query: computed(() => route.value.query),
|
||||
from: computed(() => nuxt.legacyNuxt.context.from),
|
||||
params: computed(() => route.value.params)
|
||||
}
|
||||
}
|
||||
|
||||
function createEmptyMeta () {
|
||||
return {
|
||||
titleTemplate: null,
|
||||
|
||||
__dangerouslyDisableSanitizers: [],
|
||||
__dangerouslyDisableSanitizersByTagID: {},
|
||||
|
||||
title: undefined,
|
||||
htmlAttrs: {},
|
||||
headAttrs: {},
|
||||
bodyAttrs: {},
|
||||
|
||||
base: undefined,
|
||||
|
||||
meta: [],
|
||||
link: [],
|
||||
style: [],
|
||||
script: [],
|
||||
noscript: [],
|
||||
|
||||
changed: undefined,
|
||||
afterNavigation: undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const getHeadOptions = (options) => {
|
||||
const head = function () {
|
||||
const optionHead =
|
||||
options.head instanceof Function ? options.head.call(this) : options.head
|
||||
|
||||
if (!this._computedHead) { return optionHead }
|
||||
|
||||
const computedHead = this._computedHead.map((h) => {
|
||||
if (isReactive(h)) { return toRaw(h) }
|
||||
if (isRef(h)) { return h.value }
|
||||
return h
|
||||
})
|
||||
return defu({}, ...computedHead.reverse(), optionHead)
|
||||
}
|
||||
|
||||
return { head }
|
||||
}
|
||||
|
||||
export const defineComponent = (options) => {
|
||||
if (!('head' in options)) { return options }
|
||||
|
||||
return {
|
||||
...options,
|
||||
...getHeadOptions(options)
|
||||
}
|
||||
}
|
||||
|
||||
export const useMeta = (init) => {
|
||||
const vm = getCurrentInstance()
|
||||
const refreshMeta = () => vm.$meta().refresh()
|
||||
|
||||
if (!vm._computedHead) {
|
||||
const metaRefs = reactive(createEmptyMeta())
|
||||
vm._computedHead = [metaRefs]
|
||||
vm._metaRefs = toRefs(metaRefs)
|
||||
|
||||
if (process.client) {
|
||||
watch(Object.values(vm._metaRefs), refreshMeta, { immediate: true })
|
||||
}
|
||||
}
|
||||
|
||||
if (init) {
|
||||
const initRef = init instanceof Function ? computed(init) : ref(init)
|
||||
vm._computedHead.push(initRef)
|
||||
|
||||
if (process.client) {
|
||||
watch(initRef, refreshMeta, { immediate: true })
|
||||
}
|
||||
}
|
||||
|
||||
return vm._metaRefs
|
||||
}
|
||||
|
||||
// Wrapped properties
|
||||
export const wrapProperty = (property, makeComputed = true) => () => {
|
||||
const vm = getCurrentInstance()
|
||||
return makeComputed ? computed(() => vm[property]) : vm[property]
|
||||
}
|
||||
|
||||
export const useRouter = () => {
|
||||
return getCurrentInstance().$router
|
||||
}
|
||||
|
||||
export const useRoute = () => {
|
||||
const vm = getCurrentInstance()
|
||||
return computed(() => vm.$route)
|
||||
}
|
||||
|
||||
export const useStore = () => getCurrentInstance().$store
|
||||
|
||||
// useFetch and helper functions
|
||||
|
||||
const fetches = new WeakMap()
|
||||
const fetchPromises = new Map()
|
||||
|
||||
const mergeDataOnMount = (data) => {
|
||||
const vm = getCurrentInstance()
|
||||
if (!vm) { throw new Error('This must be called within a setup function.') }
|
||||
|
||||
onBeforeMount(() => {
|
||||
// Merge data
|
||||
for (const key in data) {
|
||||
try {
|
||||
// Assign missing properties
|
||||
if (key in vm) {
|
||||
// Skip functions (not stringifiable)
|
||||
if (typeof vm[key] === 'function') { continue }
|
||||
// Preserve reactive objects
|
||||
if (isReactive(vm[key])) {
|
||||
// Unset keys that do not exist in incoming data
|
||||
for (const k in vm[key]) {
|
||||
if (!(k in data[key])) {
|
||||
delete vm[key][k]
|
||||
}
|
||||
}
|
||||
Object.assign(vm[key], data[key])
|
||||
continue
|
||||
}
|
||||
}
|
||||
set(vm, key, data[key])
|
||||
} catch (e) {
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
// eslint-disable-next-line
|
||||
console.warn(`Could not hydrate ${key}.`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createGetCounter (counterObject, defaultKey = '') {
|
||||
return function getCounter (id = defaultKey) {
|
||||
if (counterObject[id] === undefined) {
|
||||
counterObject[id] = 0
|
||||
}
|
||||
return counterObject[id]++
|
||||
}
|
||||
}
|
||||
|
||||
const setFetchState = (vm) => {
|
||||
vm.$fetchState =
|
||||
vm.$fetchState ||
|
||||
reactive({
|
||||
error: null,
|
||||
pending: false,
|
||||
timestamp: 0
|
||||
})
|
||||
}
|
||||
|
||||
function getKey (vm) {
|
||||
const nuxt = useNuxtApp()
|
||||
const nuxtState = nuxt.payload
|
||||
if (process.server && 'push' in vm.$ssrContext.nuxt.fetch) {
|
||||
return undefined
|
||||
} else if (process.client && '_payloadFetchIndex' in nuxtState) {
|
||||
nuxtState._payloadFetchIndex = nuxtState._payloadFetchIndex || 0
|
||||
return nuxtState._payloadFetchIndex++
|
||||
}
|
||||
const defaultKey = vm.$options._scopeId || vm.$options.name || ''
|
||||
const getCounter = createGetCounter(
|
||||
process.server
|
||||
? vm.$ssrContext.fetchCounters
|
||||
: nuxt.legacyApp._fetchCounters,
|
||||
defaultKey
|
||||
)
|
||||
|
||||
if (typeof vm.$options.fetchKey === 'function') {
|
||||
return vm.$options.fetchKey.call(vm, getCounter)
|
||||
} else {
|
||||
const key = typeof vm.$options.fetchKey === 'string' ? vm.$options.fetchKey : defaultKey
|
||||
return key ? key + ':' + getCounter(key) : String(getCounter(key))
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeError (err) {
|
||||
let message
|
||||
if (!(err.message || typeof err === 'string')) {
|
||||
try {
|
||||
message = JSON.stringify(err, null, 2)
|
||||
} catch (e) {
|
||||
message = `[${err.constructor.name}]`
|
||||
}
|
||||
} else {
|
||||
message = err.message || err
|
||||
}
|
||||
return {
|
||||
...err,
|
||||
message,
|
||||
statusCode:
|
||||
err.statusCode ||
|
||||
err.status ||
|
||||
(err.response && err.response.status) ||
|
||||
500
|
||||
}
|
||||
}
|
||||
|
||||
const loadFullStatic = (vm) => {
|
||||
vm._fetchKey = getKey(vm)
|
||||
// Check if component has been fetched on server
|
||||
const { fetchOnServer } = vm.$options
|
||||
const fetchedOnServer =
|
||||
typeof fetchOnServer === 'function'
|
||||
? fetchOnServer.call(vm) !== false
|
||||
: fetchOnServer !== false
|
||||
|
||||
if (!fetchedOnServer || vm.$nuxt?.isPreview || !vm.$nuxt?._pagePayload) {
|
||||
return
|
||||
}
|
||||
vm._hydrated = true
|
||||
const data = vm.$nuxt._pagePayload.fetch[vm._fetchKey]
|
||||
|
||||
// If fetch error
|
||||
if (data && data._error) {
|
||||
vm.$fetchState.error = data._error
|
||||
return
|
||||
}
|
||||
|
||||
mergeDataOnMount(data)
|
||||
}
|
||||
|
||||
async function serverPrefetch (vm) {
|
||||
if (!vm._fetchOnServer) { return }
|
||||
|
||||
// Call and await on $fetch
|
||||
setFetchState(vm)
|
||||
|
||||
try {
|
||||
await callFetches.call(vm)
|
||||
} catch (err) {
|
||||
if (process.dev) {
|
||||
console.error('Error in fetch():', err)
|
||||
}
|
||||
vm.$fetchState.error = normalizeError(err)
|
||||
}
|
||||
vm.$fetchState.pending = false
|
||||
|
||||
// Define an ssrKey for hydration
|
||||
vm._fetchKey =
|
||||
// Nuxt 2.15+ uses a different format - an object rather than an array
|
||||
'push' in vm.$ssrContext.nuxt.fetch
|
||||
? vm.$ssrContext.nuxt.fetch.length
|
||||
: vm._fetchKey || vm.$ssrContext.fetchCounters['']++
|
||||
|
||||
// Add data-fetch-key on parent element of Component
|
||||
if (!vm.$vnode.data) { vm.$vnode.data = {} }
|
||||
const attrs = (vm.$vnode.data.attrs = vm.$vnode.data.attrs || {})
|
||||
attrs['data-fetch-key'] = vm._fetchKey
|
||||
|
||||
const data = { ...vm._data }
|
||||
Object.entries(vm.__composition_api_state__.rawBindings).forEach(
|
||||
([key, val]) => {
|
||||
if (val instanceof Function || val instanceof Promise) { return }
|
||||
|
||||
data[key] = isRef(val) ? val.value : val
|
||||
}
|
||||
)
|
||||
|
||||
// Add to ssrContext for window.__NUXT__.fetch
|
||||
const content = vm.$fetchState.error
|
||||
? { _error: vm.$fetchState.error }
|
||||
: JSON.parse(JSON.stringify(data))
|
||||
if ('push' in vm.$ssrContext.nuxt.fetch) {
|
||||
vm.$ssrContext.nuxt.fetch.push(content)
|
||||
} else {
|
||||
vm.$ssrContext.nuxt.fetch[vm._fetchKey] = content
|
||||
}
|
||||
}
|
||||
|
||||
async function callFetches () {
|
||||
const fetchesToCall = fetches.get(this)
|
||||
if (!fetchesToCall) { return }
|
||||
this.$nuxt.nbFetching++
|
||||
|
||||
this.$fetchState.pending = true
|
||||
this.$fetchState.error = null
|
||||
this._hydrated = false
|
||||
|
||||
let error = null
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
fetchesToCall.map((fetch) => {
|
||||
if (fetchPromises.has(fetch)) { return fetchPromises.get(fetch) }
|
||||
const promise = Promise.resolve(fetch(this)).finally(() =>
|
||||
fetchPromises.delete(fetch)
|
||||
)
|
||||
fetchPromises.set(fetch, promise)
|
||||
return promise
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
if (process.dev) {
|
||||
console.error('Error in fetch():', err)
|
||||
}
|
||||
error = normalizeError(err)
|
||||
}
|
||||
|
||||
const delayLeft = (this._fetchDelay || 0) - (Date.now() - startTime)
|
||||
if (delayLeft > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, delayLeft))
|
||||
}
|
||||
|
||||
this.$fetchState.error = error
|
||||
this.$fetchState.pending = false
|
||||
this.$fetchState.timestamp = Date.now()
|
||||
|
||||
this.$nextTick(() => (this.$nuxt).nbFetching--)
|
||||
}
|
||||
|
||||
const isSsrHydration = vm => vm.$vnode?.elm?.dataset?.fetchKey
|
||||
|
||||
export const useFetch = (callback) => {
|
||||
const vm = getCurrentInstance()
|
||||
const nuxt = useNuxtApp()
|
||||
|
||||
const nuxtState = nuxt.payload
|
||||
|
||||
const callbacks = fetches.get(vm) || []
|
||||
fetches.set(vm, [...callbacks, callback])
|
||||
|
||||
if (typeof vm.$options.fetchOnServer === 'function') {
|
||||
vm._fetchOnServer = vm.$options.fetchOnServer.call(vm) !== false
|
||||
} else {
|
||||
vm._fetchOnServer = vm.$options.fetchOnServer !== false
|
||||
}
|
||||
|
||||
if (process.server) {
|
||||
vm._fetchKey = getKey(vm)
|
||||
}
|
||||
|
||||
setFetchState(vm)
|
||||
|
||||
onServerPrefetch(() => serverPrefetch(vm))
|
||||
|
||||
function result () {
|
||||
return {
|
||||
fetch: vm.$fetch,
|
||||
fetchState: vm.$fetchState
|
||||
}
|
||||
}
|
||||
|
||||
vm._fetchDelay =
|
||||
typeof vm.$options.fetchDelay === 'number' ? vm.$options.fetchDelay : 0
|
||||
|
||||
vm.$fetch = callFetches.bind(vm)
|
||||
|
||||
onBeforeMount(() => !vm._hydrated && callFetches.call(vm))
|
||||
|
||||
if (process.server || !isSsrHydration(vm)) {
|
||||
if (process.client && !process.dev && process.static) { loadFullStatic(vm) }
|
||||
return result()
|
||||
}
|
||||
|
||||
// Hydrate component
|
||||
vm._hydrated = true
|
||||
vm._fetchKey = vm.$vnode.elm?.dataset.fetchKey || getKey(vm)
|
||||
const data = nuxtState.fetch[vm._fetchKey]
|
||||
|
||||
// If fetch error
|
||||
if (data && data._error) {
|
||||
vm.$fetchState.error = data._error
|
||||
return result()
|
||||
}
|
||||
|
||||
mergeDataOnMount(data)
|
||||
|
||||
return result()
|
||||
}
|
||||
|
||||
// -- Private shared utils (across composables) --
|
||||
|
||||
function getCurrentInstance () {
|
||||
const vm = getVM()
|
||||
if (!vm) { throw new Error('This must be called within a setup function.') }
|
||||
return vm.proxy
|
||||
}
|
||||
|
||||
const useSSRRefs = () => {
|
||||
const { payload } = useNuxtApp()
|
||||
payload.ssrRefs = payload.ssrRefs || {}
|
||||
return payload.ssrRefs
|
||||
}
|
||||
|
||||
const isHMR = () => process.env.NODE_ENV === 'development' && process.client && window.$nuxt?.context.isHMR
|
15
packages/bridge/src/runtime/capi.legacy.plugin.mjs
Normal file
15
packages/bridge/src/runtime/capi.legacy.plugin.mjs
Normal file
@ -0,0 +1,15 @@
|
||||
import { defineNuxtPlugin } from '#app'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
nuxt._setupFns = []
|
||||
|
||||
const _originalSetup = nuxt.legacyNuxt.setup
|
||||
|
||||
nuxt.legacyNuxt.setup = function (...args) {
|
||||
const result = _originalSetup instanceof Function ? _originalSetup(...args) : {}
|
||||
for (const fn of nuxt._setupFns) {
|
||||
Object.assign(result, fn.call(this, ...args))
|
||||
}
|
||||
return result
|
||||
}
|
||||
})
|
20
test/fixtures/bridge/components/FetchTest.vue
vendored
Normal file
20
test/fixtures/bridge/components/FetchTest.vue
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<tr><td><b>useFetch</b></td><td> {{ fetched }}</td></tr>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, useFetch } from '@nuxtjs/composition-api'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
const fetched = ref('🚧')
|
||||
useFetch(() => {
|
||||
fetched.value = '✅'
|
||||
})
|
||||
|
||||
return {
|
||||
fetched
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
2
test/fixtures/bridge/nuxt.config.ts
vendored
2
test/fixtures/bridge/nuxt.config.ts
vendored
@ -4,6 +4,7 @@ import { defineNuxtConfig } from '@nuxt/kit'
|
||||
global.__NUXT_PREPATHS__ = (global.__NUXT_PREPATHS__ || []).concat(__dirname)
|
||||
|
||||
export default defineNuxtConfig({
|
||||
components: true,
|
||||
buildModules: [
|
||||
'@nuxt/bridge'
|
||||
],
|
||||
@ -16,6 +17,7 @@ export default defineNuxtConfig({
|
||||
}
|
||||
],
|
||||
buildDir: process.env.NITRO_BUILD_DIR,
|
||||
plugins: ['~/plugins/setup.js'],
|
||||
nitro: {
|
||||
output: { dir: process.env.NITRO_OUTPUT_DIR }
|
||||
}
|
||||
|
88
test/fixtures/bridge/pages/legacy-capi.vue
vendored
Normal file
88
test/fixtures/bridge/pages/legacy-capi.vue
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<table>
|
||||
<tbody>
|
||||
<!-- Basic setup function -->
|
||||
<tr><td><b>setup</b></td><td> {{ setup }}</td></tr>
|
||||
<!-- Ref -->
|
||||
<tr>
|
||||
<td>
|
||||
<b>ref</b>
|
||||
</td>
|
||||
<td>
|
||||
{{ ref }}
|
||||
</td>
|
||||
<td>
|
||||
<button @click="ref = '❇️'">
|
||||
update
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Lifecycle methods -->
|
||||
<tr><td><b>onMounted</b></td><td> {{ mounted }}</td></tr>
|
||||
<!-- Wrappers -->
|
||||
<tr><td><b>useStore</b></td><td> {{ store.state.test }}</td></tr>
|
||||
<tr><td><b>useRoute</b></td><td> {{ route.path === '/legacy' ? '✅' : '❌' }}</td></tr>
|
||||
<tr><td><b>useContext</b></td><td> {{ Object.keys(context).length ? '✅' : '❌' }}</td></tr>
|
||||
<!-- Helpers -->
|
||||
<tr><td><b>useAsync</b></td><td> {{ async }}</td></tr>
|
||||
<tr><td><b>ssrRef</b></td><td> {{ ssrRef }}</td></tr>
|
||||
<tr><td><b>shallowSsrRef</b></td><td> {{ shallow }}</td></tr>
|
||||
<tr><td><b>ssrPromise</b></td><td> {{ promise }}</td></tr>
|
||||
<tr>
|
||||
<td><b>useMeta</b></td><td> {{ title }}</td>
|
||||
<td>
|
||||
<button @click="title = '❇️'">
|
||||
update
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td><b>onGlobalSetup</b></td><td> {{ globalsetup }}</td></tr>
|
||||
<FetchTest />
|
||||
<tr><td><b>reqSsrRef</b></td><td> {{ '⛔️' }}</td></tr>
|
||||
<tr><td><b>reqRef</b></td><td> {{ '⛔️' }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, useRoute, useContext, useStore, useAsync, ssrRef, shallowSsrRef, ssrPromise, useMeta } from '@nuxtjs/composition-api'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
const mounted = ref()
|
||||
const shallow = shallowSsrRef('❌')
|
||||
const { isHMR, $globalsetup } = useContext()
|
||||
const { title } = useMeta()
|
||||
if (process.server || isHMR) {
|
||||
shallow.value = '✅'
|
||||
title.value = '❌'
|
||||
}
|
||||
const promise = ref(null)
|
||||
ssrPromise(() => new Promise(resolve => setTimeout(() => resolve(process.server || isHMR ? '✅' : '❌'), 100))).then((r) => {
|
||||
promise.value = r
|
||||
})
|
||||
onMounted(() => {
|
||||
mounted.value = '✅'
|
||||
title.value = '✅'
|
||||
})
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
|
||||
return {
|
||||
setup: '✅',
|
||||
ref: ref('✅'),
|
||||
mounted,
|
||||
store,
|
||||
route,
|
||||
context: useContext(),
|
||||
async: useAsync(() => new Promise(resolve => setTimeout(() => resolve(process.server || isHMR ? '✅' : '❌'), 100))),
|
||||
ssrRef: ssrRef(() => process.server || isHMR ? '✅' : '❌'),
|
||||
shallow,
|
||||
promise,
|
||||
title,
|
||||
globalsetup: $globalsetup
|
||||
}
|
||||
},
|
||||
head: {}
|
||||
})
|
||||
</script>
|
11
test/fixtures/bridge/plugins/setup.js
vendored
Normal file
11
test/fixtures/bridge/plugins/setup.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { onGlobalSetup, ref } from '@nuxtjs/composition-api'
|
||||
|
||||
import { defineNuxtPlugin } from '#app'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
const globalsetup = ref('🚧')
|
||||
onGlobalSetup(() => {
|
||||
globalsetup.value = '✅'
|
||||
})
|
||||
nuxt.provide('globalsetup', globalsetup)
|
||||
})
|
9
test/fixtures/bridge/store/index.js
vendored
Normal file
9
test/fixtures/bridge/store/index.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
export const state = () => ({
|
||||
test: '❌'
|
||||
})
|
||||
|
||||
export const actions = {
|
||||
nuxtServerInit ({ state }) {
|
||||
state.test = '✅'
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user