feat(bridge): use useMeta in bridge projects (#664)

This commit is contained in:
Daniel Roe 2021-10-06 14:37:45 +02:00 committed by GitHub
parent dd73a8bcad
commit a07b67ce57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 149 additions and 26 deletions

View File

@ -20,6 +20,7 @@
"@nuxt/nitro": "3.0.0", "@nuxt/nitro": "3.0.0",
"@nuxt/postcss8": "^1.1.3", "@nuxt/postcss8": "^1.1.3",
"@vue/composition-api": "^1.2.3", "@vue/composition-api": "^1.2.3",
"@vueuse/head": "^0.6.0",
"acorn": "^8.5.0", "acorn": "^8.5.0",
"defu": "^5.0.0", "defu": "^5.0.0",
"enhanced-resolve": "^5.8.3", "enhanced-resolve": "^5.8.3",

View File

@ -12,9 +12,12 @@ export function setupAppBridge (_options: any) {
// Transpile runtime/ // Transpile runtime/
nuxt.options.build.transpile.push(resolve(distDir, 'runtime')) nuxt.options.build.transpile.push(resolve(distDir, 'runtime'))
// Resolve to same vue2 path // Alias vue to a vue3-compat version of vue2
nuxt.options.alias.vue = nuxt.options.alias.vue || nuxt.options.alias['#vue'] = nuxt.options.alias.vue || resolveModule('vue/dist/vue.runtime.esm.js', { paths: nuxt.options.modulesDir })
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 // Deprecate various Nuxt options
if (nuxt.options.globalName !== 'nuxt') { if (nuxt.options.globalName !== 'nuxt') {

View File

@ -3,7 +3,6 @@ import globalImports from '../../nuxt3/src/global-imports/module'
// TODO: implement these: https://github.com/nuxt/framework/issues/549 // TODO: implement these: https://github.com/nuxt/framework/issues/549
const disabled = [ const disabled = [
'useMeta',
'useAsyncData', 'useAsyncData',
'asyncData' 'asyncData'
] ]
@ -17,6 +16,9 @@ const identifiers = {
'useRouter', 'useRouter',
'useRuntimeConfig' 'useRuntimeConfig'
], ],
'#meta': [
'useMeta'
],
'@vue/composition-api': [ '@vue/composition-api': [
// lifecycle // lifecycle
'onActivated', 'onActivated',

View File

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

View File

@ -7,6 +7,7 @@ import { setupCAPIBridge } from './capi'
import { setupBetterResolve } from './resolve' import { setupBetterResolve } from './resolve'
import { setupGlobalImports } from './global-imports' import { setupGlobalImports } from './global-imports'
import { setupTypescript } from './typescript' import { setupTypescript } from './typescript'
import { setupMeta } from './meta'
export default defineNuxtModule({ export default defineNuxtModule({
name: 'nuxt-bridge', name: 'nuxt-bridge',
@ -18,6 +19,7 @@ export default defineNuxtModule({
capi: {}, capi: {},
globalImports: true, globalImports: true,
constraints: true, constraints: true,
meta: null,
// TODO: Remove from 2.16 // TODO: Remove from 2.16
postcss8: true, postcss8: true,
typescript: true, typescript: true,
@ -66,5 +68,8 @@ export default defineNuxtModule({
} }
}) })
} }
if (opts.meta !== false && opts.capi) {
await setupMeta({ needsExplicitEnable: opts.meta === null })
}
} }
}) })

View File

@ -1,14 +1,31 @@
import Vue from 'vue'
import { createHooks } from 'hookable/dist/index.mjs' import { createHooks } from 'hookable/dist/index.mjs'
import { setNuxtInstance } from '#app' import { setNuxtInstance } from '#app'
export default (ctx, inject) => { export default (ctx, inject) => {
const nuxt = { 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, provide: inject,
globalName: 'nuxt', globalName: 'nuxt',
payload: process.client ? ctx.nuxtState : ctx.ssrContext.nuxt, payload: process.client ? ctx.nuxtState : ctx.ssrContext.nuxt,
isHydrating: ctx.isHMR, isHydrating: ctx.isHMR,
legacyNuxt: ctx.app legacyNuxt: ctx.app
} }
nuxt.hooks = createHooks() nuxt.hooks = createHooks()
nuxt.hook = nuxt.hooks.hook nuxt.hook = nuxt.hooks.hook
nuxt.callHook = nuxt.hooks.callHook nuxt.callHook = nuxt.hooks.callHook
@ -17,6 +34,10 @@ export default (ctx, inject) => {
ctx.app.created = [ctx.app.created] ctx.app.created = [ctx.app.created]
} }
if (process.server) {
nuxt.ssrContext = ctx.ssrContext
}
ctx.app.created.push(function () { ctx.app.created.push(function () {
nuxt.legacyApp = this nuxt.legacyApp = this
}) })

View File

@ -0,0 +1 @@
../../../nuxt3/src/meta/runtime

View File

@ -1,7 +1,6 @@
export default ({ ssrContext }) => { const vueMetaRenderer = (nuxt) => {
ssrContext.renderMeta = () => { const meta = nuxt.ssrContext.meta.inject({
const meta = ssrContext.meta.inject({ isSSR: nuxt.ssrContext.nuxt.serverRendered,
isSSR: ssrContext.nuxt.serverRendered,
ln: process.env.NODE_ENV === 'development' ln: process.env.NODE_ENV === 'development'
}) })
@ -23,5 +22,28 @@ export default ({ ssrContext }) => {
meta.style.text({ body: true }) + meta.script.text({ body: true }) + meta.style.text({ body: true }) + meta.script.text({ body: true }) +
meta.noscript.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
}
})

View File

@ -0,0 +1,7 @@
import Vue from '#vue'
export * from '@vue/composition-api'
export const isFunction = fn => fn instanceof Function
export { Vue as default }

View File

@ -11,6 +11,7 @@ export interface BridgeConfig {
swc: boolean swc: boolean
resolve: boolean resolve: boolean
typescript: boolean typescript: boolean
meta: boolean | null
} }
// TODO: Also inherit from @nuxt/types.NuxtConfig for legacy type compat // TODO: Also inherit from @nuxt/types.NuxtConfig for legacy type compat

View File

@ -103,6 +103,22 @@ export default {
script: [] script: []
}, },
/**
* Set default configuration for `<head>` on every page.
*
* @version 3
*/
meta: {
/** Each item in the array maps to a newly-created `<meta>` element, where object properties map to attributes. */
meta: [],
/** Each item in the array maps to a newly-created `<link>` element, where object properties map to attributes. */
link: [],
/** Each item in the array maps to a newly-created `<style>` element, where object properties map to attributes. */
style: [],
/** Each item in the array maps to a newly-created `<script>` element, where object properties map to attributes. */
script: []
},
/** /**
* Configuration for the Nuxt `fetch()` hook. * Configuration for the Nuxt `fetch()` hook.
* @version 2 * @version 2

View File

@ -1,3 +1,4 @@
/* eslint-disable no-use-before-define */
import { getCurrentInstance, reactive } from 'vue' import { getCurrentInstance, reactive } from 'vue'
import type { App, VNode } from 'vue' import type { App, VNode } from 'vue'
import { createHooks, Hookable } from 'hookable' import { createHooks, Hookable } from 'hookable'
@ -21,6 +22,7 @@ export interface RuntimeNuxtHooks {
'app:rendered': () => HookResult 'app:rendered': () => HookResult
'page:start': (Component?: VNode) => HookResult 'page:start': (Component?: VNode) => HookResult
'page:finish': (Component?: VNode) => HookResult 'page:finish': (Component?: VNode) => HookResult
'meta:register': (metaRenderers: Array<(nuxt: NuxtApp) => NuxtMeta | Promise<NuxtMeta>>) => HookResult
} }
export interface NuxtApp { export interface NuxtApp {

View File

@ -1,5 +1,6 @@
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { addPlugin, addTemplate, defineNuxtModule } from '@nuxt/kit' import { addPlugin, addTemplate, defineNuxtModule, isNuxt3 } from '@nuxt/kit'
import defu from 'defu'
import { distDir } from '../dirs' import { distDir } from '../dirs'
import type { MetaObject } from './runtime' import type { MetaObject } from './runtime'
@ -10,7 +11,7 @@ export default defineNuxtModule({
viewport: 'width=device-width, initial-scale=1' viewport: 'width=device-width, initial-scale=1'
}, },
setup (options, nuxt) { setup (options, nuxt) {
const runtimeDir = resolve(distDir, 'meta/runtime') const runtimeDir = nuxt.options.alias['#meta'] || resolve(distDir, 'meta/runtime')
// Transpile @nuxt/meta and @vueuse/head // Transpile @nuxt/meta and @vueuse/head
nuxt.options.build.transpile.push(runtimeDir, '@vueuse/head') nuxt.options.build.transpile.push(runtimeDir, '@vueuse/head')
@ -19,17 +20,17 @@ export default defineNuxtModule({
nuxt.options.alias['#meta'] = runtimeDir nuxt.options.alias['#meta'] = runtimeDir
// Global meta // Global meta
const globalMeta: MetaObject = { const globalMeta: MetaObject = defu(nuxt.options.meta, {
meta: [ meta: [
{ charset: options.charset }, { charset: options.charset },
{ name: 'viewport', content: options.viewport } { name: 'viewport', content: options.viewport }
] ]
} })
// Add global meta configuration // Add global meta configuration
addTemplate({ addTemplate({
filename: 'meta.config.mjs', filename: 'meta.config.mjs',
getContents: () => 'export default ' + JSON.stringify({ globalMeta }) getContents: () => 'export default ' + JSON.stringify({ globalMeta, mixinKey: isNuxt3() ? 'created' : 'setup' })
}) })
// Add generic plugin // Add generic plugin

View File

@ -9,11 +9,12 @@ export default defineNuxtPlugin((nuxt) => {
useMeta(metaConfig.globalMeta) useMeta(metaConfig.globalMeta)
nuxt.app.mixin({ nuxt.app.mixin({
created () { [metaConfig.mixinKey] () {
const instance = getCurrentInstance() const instance = getCurrentInstance()
if (!instance?.type || !('head' in instance.type)) { return } const options = instance?.type || /* nuxt2 */ instance?.proxy?.$options
if (!options || !('head' in options)) { return }
useMeta((instance.type as any).head) useMeta(options.head)
} }
}) })

View File

@ -3,11 +3,17 @@
</template> </template>
<script> <script>
import { useMeta } from '#meta'
export default defineComponent({ export default defineComponent({
setup () { setup () {
useMeta({ meta: [{ name: 'description', content: 'This is a page to demo Nuxt Bridge.' }] })
return { return {
version: ref('2') version: ref('2')
} }
},
head: {
title: 'Bridge test fixture'
} }
}) })
</script> </script>

View File

@ -1427,6 +1427,7 @@ __metadata:
"@types/fs-extra": ^9.0.13 "@types/fs-extra": ^9.0.13
"@types/node-fetch": ^3.0.2 "@types/node-fetch": ^3.0.2
"@vue/composition-api": ^1.2.3 "@vue/composition-api": ^1.2.3
"@vueuse/head": ^0.6.0
acorn: ^8.5.0 acorn: ^8.5.0
defu: ^5.0.0 defu: ^5.0.0
enhanced-resolve: ^5.8.3 enhanced-resolve: ^5.8.3