diff --git a/packages/bridge/package.json b/packages/bridge/package.json index b08b2f626b6..859e53ba947 100644 --- a/packages/bridge/package.json +++ b/packages/bridge/package.json @@ -20,6 +20,7 @@ "@nuxt/nitro": "3.0.0", "@nuxt/postcss8": "^1.1.3", "@vue/composition-api": "^1.2.3", + "@vueuse/head": "^0.6.0", "acorn": "^8.5.0", "defu": "^5.0.0", "enhanced-resolve": "^5.8.3", diff --git a/packages/bridge/src/app.ts b/packages/bridge/src/app.ts index 09c380df96f..16d2865ed33 100644 --- a/packages/bridge/src/app.ts +++ b/packages/bridge/src/app.ts @@ -12,9 +12,12 @@ export function setupAppBridge (_options: any) { // Transpile runtime/ nuxt.options.build.transpile.push(resolve(distDir, 'runtime')) - // Resolve to same vue2 path - nuxt.options.alias.vue = nuxt.options.alias.vue || - resolveModule('vue/dist/vue.runtime.esm.js', { paths: nuxt.options.modulesDir }) + // Alias vue to a vue3-compat version of vue2 + nuxt.options.alias['#vue'] = nuxt.options.alias.vue || resolveModule('vue/dist/vue.runtime.esm.js', { paths: nuxt.options.modulesDir }) + nuxt.options.alias['@vue/shared'] = 'vue' + nuxt.options.alias['@vue/reactivity'] = 'vue' + nuxt.options.alias.vue = resolve(distDir, 'runtime/vue.mjs') + nuxt.options.build.transpile.push('vue') // Deprecate various Nuxt options if (nuxt.options.globalName !== 'nuxt') { diff --git a/packages/bridge/src/global-imports.ts b/packages/bridge/src/global-imports.ts index 5fb4fc2758f..68a1100e013 100644 --- a/packages/bridge/src/global-imports.ts +++ b/packages/bridge/src/global-imports.ts @@ -3,7 +3,6 @@ import globalImports from '../../nuxt3/src/global-imports/module' // TODO: implement these: https://github.com/nuxt/framework/issues/549 const disabled = [ - 'useMeta', 'useAsyncData', 'asyncData' ] @@ -17,6 +16,9 @@ const identifiers = { 'useRouter', 'useRuntimeConfig' ], + '#meta': [ + 'useMeta' + ], '@vue/composition-api': [ // lifecycle 'onActivated', diff --git a/packages/bridge/src/meta.ts b/packages/bridge/src/meta.ts new file mode 100644 index 00000000000..be2788221ab --- /dev/null +++ b/packages/bridge/src/meta.ts @@ -0,0 +1,33 @@ +import { resolve } from 'pathe' +import { addTemplate, useNuxt, installModule } from '@nuxt/kit' +import metaModule from '../../nuxt3/src/meta/module' +import { distDir } from './dirs' + +const checkDocsMsg = 'Please see https://v3.nuxtjs.org for more information.' +const msgPrefix = '[bridge] [meta]' + +interface SetupMetaOptions { + needsExplicitEnable?: boolean +} + +export const setupMeta = async (opts: SetupMetaOptions) => { + const nuxt = useNuxt() + + if (opts.needsExplicitEnable) { + const metaPath = addTemplate({ + filename: 'meta.mjs', + getContents: () => `export const useMeta = () => console.warn('${msgPrefix} To use \`useMeta\`, please set \`bridge.meta\` to \`true\` in your \`nuxt.config\`. ${checkDocsMsg}')` + }) + nuxt.options.alias['#meta'] = metaPath.dst + return + } + + if (nuxt.options.head && typeof nuxt.options.head === 'function') { + throw new TypeError(`${msgPrefix} The head() function in \`nuxt.config\` has been deprecated and in nuxt3 will need to be moved to \`app.vue\`. ${checkDocsMsg}`) + } + + const runtimeDir = resolve(distDir, 'runtime/meta') + nuxt.options.alias['#meta'] = runtimeDir + + await installModule(nuxt, metaModule) +} diff --git a/packages/bridge/src/module.ts b/packages/bridge/src/module.ts index 2a558f2b465..56fb43c561d 100644 --- a/packages/bridge/src/module.ts +++ b/packages/bridge/src/module.ts @@ -7,6 +7,7 @@ import { setupCAPIBridge } from './capi' import { setupBetterResolve } from './resolve' import { setupGlobalImports } from './global-imports' import { setupTypescript } from './typescript' +import { setupMeta } from './meta' export default defineNuxtModule({ name: 'nuxt-bridge', @@ -18,6 +19,7 @@ export default defineNuxtModule({ capi: {}, globalImports: true, constraints: true, + meta: null, // TODO: Remove from 2.16 postcss8: true, typescript: true, @@ -66,5 +68,8 @@ export default defineNuxtModule({ } }) } + if (opts.meta !== false && opts.capi) { + await setupMeta({ needsExplicitEnable: opts.meta === null }) + } } }) diff --git a/packages/bridge/src/runtime/app.plugin.mjs b/packages/bridge/src/runtime/app.plugin.mjs index 82c18b0ef48..0762815b075 100644 --- a/packages/bridge/src/runtime/app.plugin.mjs +++ b/packages/bridge/src/runtime/app.plugin.mjs @@ -1,14 +1,31 @@ +import Vue from 'vue' import { createHooks } from 'hookable/dist/index.mjs' import { setNuxtInstance } from '#app' export default (ctx, inject) => { const nuxt = { + app: { + component: Vue.component.bind(Vue), + config: { + globalProperties: {} + }, + directive: Vue.directive.bind(Vue), + mixin: Vue.mixin.bind(Vue), + mount: () => {}, + provide: inject, + unmount: () => {}, + use (vuePlugin) { + vuePlugin.install(this) + }, + version: Vue.version + }, provide: inject, globalName: 'nuxt', payload: process.client ? ctx.nuxtState : ctx.ssrContext.nuxt, isHydrating: ctx.isHMR, legacyNuxt: ctx.app } + nuxt.hooks = createHooks() nuxt.hook = nuxt.hooks.hook nuxt.callHook = nuxt.hooks.callHook @@ -17,6 +34,10 @@ export default (ctx, inject) => { ctx.app.created = [ctx.app.created] } + if (process.server) { + nuxt.ssrContext = ctx.ssrContext + } + ctx.app.created.push(function () { nuxt.legacyApp = this }) diff --git a/packages/bridge/src/runtime/meta b/packages/bridge/src/runtime/meta new file mode 120000 index 00000000000..211ee41b7fe --- /dev/null +++ b/packages/bridge/src/runtime/meta @@ -0,0 +1 @@ +../../../nuxt3/src/meta/runtime \ No newline at end of file diff --git a/packages/bridge/src/runtime/nitro-bridge.server.mjs b/packages/bridge/src/runtime/nitro-bridge.server.mjs index 1d27b68d886..d0c9a7da23e 100644 --- a/packages/bridge/src/runtime/nitro-bridge.server.mjs +++ b/packages/bridge/src/runtime/nitro-bridge.server.mjs @@ -1,27 +1,49 @@ -export default ({ ssrContext }) => { - ssrContext.renderMeta = () => { - const meta = ssrContext.meta.inject({ - isSSR: ssrContext.nuxt.serverRendered, - ln: process.env.NODE_ENV === 'development' - }) +const vueMetaRenderer = (nuxt) => { + const meta = nuxt.ssrContext.meta.inject({ + isSSR: nuxt.ssrContext.nuxt.serverRendered, + ln: process.env.NODE_ENV === 'development' + }) - return { - htmlAttrs: meta.htmlAttrs.text(), - headAttrs: meta.headAttrs.text(), - headTags: + return { + htmlAttrs: meta.htmlAttrs.text(), + headAttrs: meta.headAttrs.text(), + headTags: meta.title.text() + meta.base.text() + meta.meta.text() + meta.link.text() + meta.style.text() + meta.script.text() + meta.noscript.text(), - bodyAttrs: meta.bodyAttrs.text(), - bodyScriptsPrepend: + bodyAttrs: meta.bodyAttrs.text(), + bodyScriptsPrepend: meta.meta.text({ pbody: true }) + meta.link.text({ pbody: true }) + meta.style.text({ pbody: true }) + meta.script.text({ pbody: true }) + meta.noscript.text({ pbody: true }), - bodyScripts: + bodyScripts: meta.meta.text({ body: true }) + meta.link.text({ body: true }) + meta.style.text({ body: true }) + meta.script.text({ body: true }) + meta.noscript.text({ body: true }) - } } } + +export default defineNuxtPlugin((nuxt) => { + const metaRenderers = [vueMetaRenderer] + + nuxt.callHook('meta:register', metaRenderers) + + nuxt.ssrContext.renderMeta = async () => { + const metadata = { + htmlAttrs: '', + headAttrs: '', + headTags: '', + bodyAttrs: '', + bodyScriptsPrepend: '', + bodyScripts: '' + } + for await (const renderer of metaRenderers) { + const result = await renderer(nuxt) + for (const key in result) { + metadata[key] += result[key] + } + } + return metadata + } +}) diff --git a/packages/bridge/src/runtime/vue.mjs b/packages/bridge/src/runtime/vue.mjs new file mode 100644 index 00000000000..52d77b5b148 --- /dev/null +++ b/packages/bridge/src/runtime/vue.mjs @@ -0,0 +1,7 @@ +import Vue from '#vue' + +export * from '@vue/composition-api' + +export const isFunction = fn => fn instanceof Function + +export { Vue as default } diff --git a/packages/bridge/types.d.ts b/packages/bridge/types.d.ts index b0f4602a5aa..cfacb5968a3 100644 --- a/packages/bridge/types.d.ts +++ b/packages/bridge/types.d.ts @@ -11,6 +11,7 @@ export interface BridgeConfig { swc: boolean resolve: boolean typescript: boolean + meta: boolean | null } // TODO: Also inherit from @nuxt/types.NuxtConfig for legacy type compat diff --git a/packages/kit/src/config/schema/_app.ts b/packages/kit/src/config/schema/_app.ts index e246afa8e60..07cff87001f 100644 --- a/packages/kit/src/config/schema/_app.ts +++ b/packages/kit/src/config/schema/_app.ts @@ -103,6 +103,22 @@ export default { script: [] }, + /** + * Set default configuration for `` on every page. + * + * @version 3 + */ + meta: { + /** Each item in the array maps to a newly-created `` element, where object properties map to attributes. */ + meta: [], + /** Each item in the array maps to a newly-created `` element, where object properties map to attributes. */ + link: [], + /** Each item in the array maps to a newly-created `