From ad9d2d190659f9f560d198a9dfecdb9142c48e83 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 29 Sep 2021 11:38:44 +0100 Subject: [PATCH] feat(bridge): add support for legacy composition api helpers (#584) --- packages/bridge/src/app.ts | 5 + packages/bridge/src/capi-legacy-key-plugin.ts | 102 +++ packages/bridge/src/capi.ts | 18 +- packages/bridge/src/runtime/capi.legacy.mjs | 606 ++++++++++++++++++ .../bridge/src/runtime/capi.legacy.plugin.mjs | 15 + test/fixtures/bridge/components/FetchTest.vue | 20 + test/fixtures/bridge/nuxt.config.ts | 2 + test/fixtures/bridge/pages/legacy-capi.vue | 88 +++ test/fixtures/bridge/plugins/setup.js | 11 + test/fixtures/bridge/store/index.js | 9 + 10 files changed, 874 insertions(+), 2 deletions(-) create mode 100644 packages/bridge/src/capi-legacy-key-plugin.ts create mode 100644 packages/bridge/src/runtime/capi.legacy.mjs create mode 100644 packages/bridge/src/runtime/capi.legacy.plugin.mjs create mode 100644 test/fixtures/bridge/components/FetchTest.vue create mode 100644 test/fixtures/bridge/pages/legacy-capi.vue create mode 100644 test/fixtures/bridge/plugins/setup.js create mode 100644 test/fixtures/bridge/store/index.js diff --git a/packages/bridge/src/app.ts b/packages/bridge/src/app.ts index 4c13947497..09c380df96 100644 --- a/packages/bridge/src/app.ts +++ b/packages/bridge/src/app.ts @@ -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)) { diff --git a/packages/bridge/src/capi-legacy-key-plugin.ts b/packages/bridge/src/capi-legacy-key-plugin.ts new file mode 100644 index 0000000000..3334b18d3c --- /dev/null +++ b/packages/bridge/src/capi-legacy-key-plugin.ts @@ -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(/(?<=]*>)[\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 { } + } + } +}) diff --git a/packages/bridge/src/capi.ts b/packages/bridge/src/capi.ts index a51ca9e8c3..c3af2aad00 100644 --- a/packages/bridge/src/capi.ts +++ b/packages/bridge/src/capi.ts @@ -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 } diff --git a/packages/bridge/src/runtime/capi.legacy.mjs b/packages/bridge/src/runtime/capi.legacy.mjs new file mode 100644 index 0000000000..5027d5f3d7 --- /dev/null +++ b/packages/bridge/src/runtime/capi.legacy.mjs @@ -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 diff --git a/packages/bridge/src/runtime/capi.legacy.plugin.mjs b/packages/bridge/src/runtime/capi.legacy.plugin.mjs new file mode 100644 index 0000000000..ac244dc8dd --- /dev/null +++ b/packages/bridge/src/runtime/capi.legacy.plugin.mjs @@ -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 + } +}) diff --git a/test/fixtures/bridge/components/FetchTest.vue b/test/fixtures/bridge/components/FetchTest.vue new file mode 100644 index 0000000000..db1c344dea --- /dev/null +++ b/test/fixtures/bridge/components/FetchTest.vue @@ -0,0 +1,20 @@ + + + diff --git a/test/fixtures/bridge/nuxt.config.ts b/test/fixtures/bridge/nuxt.config.ts index 5275dd77a0..e661e111d9 100644 --- a/test/fixtures/bridge/nuxt.config.ts +++ b/test/fixtures/bridge/nuxt.config.ts @@ -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 } } diff --git a/test/fixtures/bridge/pages/legacy-capi.vue b/test/fixtures/bridge/pages/legacy-capi.vue new file mode 100644 index 0000000000..e31d70bbcd --- /dev/null +++ b/test/fixtures/bridge/pages/legacy-capi.vue @@ -0,0 +1,88 @@ + + + diff --git a/test/fixtures/bridge/plugins/setup.js b/test/fixtures/bridge/plugins/setup.js new file mode 100644 index 0000000000..25e13f2326 --- /dev/null +++ b/test/fixtures/bridge/plugins/setup.js @@ -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) +}) diff --git a/test/fixtures/bridge/store/index.js b/test/fixtures/bridge/store/index.js new file mode 100644 index 0000000000..96412b0d7d --- /dev/null +++ b/test/fixtures/bridge/store/index.js @@ -0,0 +1,9 @@ +export const state = () => ({ + test: '❌' +}) + +export const actions = { + nuxtServerInit ({ state }) { + state.test = '✅' + } +}