feat(bridge): add support for legacy composition api helpers (#584)

This commit is contained in:
Daniel Roe 2021-09-29 11:38:44 +01:00 committed by GitHub
parent 7fdcee3252
commit ad9d2d1906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 874 additions and 2 deletions

View File

@ -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)) {

View 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 { }
}
}
})

View File

@ -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
}

View 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

View 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
}
})

View 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>

View File

@ -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 }
}

View 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
View 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
View File

@ -0,0 +1,9 @@
export const state = () => ({
test: '❌'
})
export const actions = {
nuxtServerInit ({ state }) {
state.test = '✅'
}
}