mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 07:32:01 +00:00
feat(nuxt3): add support for definePageMeta
macro (#2678)
This commit is contained in:
parent
0e984fe496
commit
93ef422b5d
@ -34,15 +34,20 @@ Given the example above, you can use a custom layout like this:
|
|||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script>
|
<script>
|
||||||
export default {
|
// This will also work in `<script setup>`
|
||||||
|
definePageMeta({
|
||||||
layout: "custom",
|
layout: "custom",
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::alert{type=info}
|
||||||
|
Learn more about [defining page meta](/docs/directory-structure/pages#page-metadata).
|
||||||
|
::
|
||||||
|
|
||||||
## Example: using with slots
|
## Example: using with slots
|
||||||
|
|
||||||
You can also take full control (for example, with slots) by using the `<NuxtLayout>` component (which is globally available throughout your application) and set `layout: false` in your component options.
|
You can also take full control (for example, with slots) by using the `<NuxtLayout>` component (which is globally available throughout your application) by setting `layout: false`.
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
@ -53,51 +58,33 @@ You can also take full control (for example, with slots) by using the `<NuxtLayo
|
|||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
definePageMeta({
|
||||||
layout: false,
|
layout: false,
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example: using with `<script setup>`
|
## Example: changing the layout
|
||||||
|
|
||||||
If you are utilizing Vue `<script setup>` [compile-time syntactic sugar](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup), you can use a secondary `<script>` tag to set `layout` options as needed.
|
You can also use a ref or computed property for your layout.
|
||||||
|
|
||||||
::alert{type=info}
|
|
||||||
Learn more about [`<script setup>` and `<script>` tags co-existing](https://v3.vuejs.org/api/sfc-script-setup.html#usage-alongside-normal-script) in the Vue docs.
|
|
||||||
::
|
|
||||||
|
|
||||||
Assuming this directory structure:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
-| layouts/
|
|
||||||
---| custom.vue
|
|
||||||
-| pages/
|
|
||||||
---| my-page.vue
|
|
||||||
```
|
|
||||||
|
|
||||||
And this `custom.vue` layout:
|
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
Some shared layout content:
|
<button @click="enableCustomLayout">Update layout</button>
|
||||||
<slot />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
|
||||||
|
|
||||||
You can set a page layout in `my-page.vue` — alongside the `<script setup>` tag — like this:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
layout: "custom",
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// your setup script
|
const route = useRoute()
|
||||||
|
function enableCustomLayout () {
|
||||||
|
// Note: because it's within a ref, it will persist if
|
||||||
|
// you navigate away and then back to the page.
|
||||||
|
route.layout.value = "custom"
|
||||||
|
}
|
||||||
|
definePageMeta({
|
||||||
|
layout: ref(false),
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
@ -133,3 +133,52 @@ To display the `child.vue` component, you have to insert the `<NuxtNestedPage
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Page Metadata
|
||||||
|
|
||||||
|
You might want to define metadata for each route in your app. You can do this using the `definePageMeta` macro, which will work both in `<script>` and in `<script setup>`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'My home page'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This data can then be accessed throughout the rest of your app from the `route.meta` object.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
console.log(route.meta.title) // My home page
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using nested routes, the page metadata from all these routes will be merged into a single object. For more on route meta, see the [vue-router docs](https://next.router.vuejs.org/guide/advanced/meta.html#route-meta-fields).
|
||||||
|
|
||||||
|
Much like `defineEmits` or `defineProps` (see [Vue docs](https://v3.vuejs.org/api/sfc-script-setup.html#defineprops-and-defineemits)), `definePageMeta` is a **compiler macro**. It will be compiled away so you cannot reference it within your component. Instead, the metadata passed to it will be hoisted out of the component. Therefore, the page meta object cannot reference the component (or values defined on the component). However, it can reference imported bindings.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { someData } from '~/utils/example'
|
||||||
|
|
||||||
|
const title = ref('')
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title,
|
||||||
|
someData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Special Metadata
|
||||||
|
|
||||||
|
Of course, you are welcome to define metadata for your own use throughout your app. But some metadata defined with `definePageMeta` has a particular purpose:
|
||||||
|
|
||||||
|
#### `layout`
|
||||||
|
|
||||||
|
You can define the layout used to render the route. This can be either false (to disable any layout), a string or a ref/computed, if you want to make it reactive in some way. [More about layouts](/docs/directory-structure/layouts).
|
||||||
|
|
||||||
|
#### `transition`
|
||||||
|
|
||||||
|
You can define transition properties for the `<transition>` component that wraps your pages, or pass `false` to disable the `<transition>` wrapper for that route. [More about transitions](https://v3.vuejs.org/guide/transitions-overview.html).
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default defineNuxtComponent({
|
definePageMeta({
|
||||||
layout: 'custom'
|
layout: 'custom'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
definePageMeta({
|
||||||
|
layout: false
|
||||||
|
})
|
||||||
export default {
|
export default {
|
||||||
layout: false,
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
layout: 'custom'
|
layout: 'custom'
|
||||||
})
|
})
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
definePageMeta({
|
||||||
layout: 'custom'
|
layout: 'custom'
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -37,7 +37,7 @@ export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
|||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
transformInclude (id) {
|
transformInclude (id) {
|
||||||
const { pathname, search } = parseURL(id)
|
const { pathname, search } = parseURL(id)
|
||||||
const { type } = parseQuery(search)
|
const { type, macro } = parseQuery(search)
|
||||||
|
|
||||||
// Exclude node_modules by default
|
// Exclude node_modules by default
|
||||||
if (ctx.transform.exclude.some(pattern => id.match(pattern))) {
|
if (ctx.transform.exclude.some(pattern => id.match(pattern))) {
|
||||||
@ -47,7 +47,7 @@ export const TransformPlugin = createUnplugin((ctx: AutoImportContext) => {
|
|||||||
// vue files
|
// vue files
|
||||||
if (
|
if (
|
||||||
pathname.endsWith('.vue') &&
|
pathname.endsWith('.vue') &&
|
||||||
(type === 'template' || type === 'script' || !search)
|
(type === 'template' || type === 'script' || macro || !search)
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
92
packages/nuxt3/src/pages/macros.ts
Normal file
92
packages/nuxt3/src/pages/macros.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
import { parseQuery, parseURL, withQuery } from 'ufo'
|
||||||
|
import { findStaticImports, findExports } from 'mlly'
|
||||||
|
|
||||||
|
export interface TransformMacroPluginOptions {
|
||||||
|
macros: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TransformMacroPlugin = createUnplugin((options: TransformMacroPluginOptions) => {
|
||||||
|
return {
|
||||||
|
name: 'nuxt-pages-macros-transform',
|
||||||
|
enforce: 'post',
|
||||||
|
transformInclude (id) {
|
||||||
|
// We only process SFC files for macros
|
||||||
|
return parseURL(id).pathname.endsWith('.vue')
|
||||||
|
},
|
||||||
|
transform (code, id) {
|
||||||
|
const { search } = parseURL(id)
|
||||||
|
|
||||||
|
// Tree-shake out any runtime references to the macro.
|
||||||
|
// We do this first as it applies to all files, not just those with the query
|
||||||
|
for (const macro in options.macros) {
|
||||||
|
const match = code.match(new RegExp(`\\b${macro}\\s*\\(\\s*`))?.[0]
|
||||||
|
if (match) {
|
||||||
|
code = code.replace(match, `/*#__PURE__*/ false && ${match}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parseQuery(search).macro) { return code }
|
||||||
|
|
||||||
|
// [webpack] Re-export any imports from script blocks in the components
|
||||||
|
// with workaround for vue-loader bug: https://github.com/vuejs/vue-loader/pull/1911
|
||||||
|
const scriptImport = findStaticImports(code).find(i => parseQuery(i.specifier.replace('?macro=true', '')).type === 'script')
|
||||||
|
if (scriptImport) {
|
||||||
|
const specifier = withQuery(scriptImport.specifier.replace('?macro=true', ''), { macro: 'true' })
|
||||||
|
return `export { meta } from "${specifier}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentExports = findExports(code)
|
||||||
|
for (const match of currentExports) {
|
||||||
|
if (match.type !== 'default') { continue }
|
||||||
|
if (match.specifier && match._type === 'named') {
|
||||||
|
// [webpack] Export named exports rather than the default (component)
|
||||||
|
return code.replace(match.code, `export {${Object.values(options.macros).join(', ')}} from "${match.specifier}"`)
|
||||||
|
} else {
|
||||||
|
// ensure we tree-shake any _other_ default exports out of the macro script
|
||||||
|
code = code.replace(match.code, '/*#__PURE__*/ false &&')
|
||||||
|
code += '\nexport default {}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const macro in options.macros) {
|
||||||
|
// Skip already-processed macros
|
||||||
|
if (currentExports.some(e => e.name === options.macros[macro])) { continue }
|
||||||
|
|
||||||
|
const { 0: match, index = 0 } = code.match(new RegExp(`\\b${macro}\\s*\\(\\s*`)) || {} as RegExpMatchArray
|
||||||
|
const macroContent = match ? extractObject(code.slice(index + match.length)) : 'undefined'
|
||||||
|
|
||||||
|
code += `\nexport const ${options.macros[macro]} = ${macroContent}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const starts = {
|
||||||
|
'{': '}',
|
||||||
|
'[': ']',
|
||||||
|
'(': ')',
|
||||||
|
'<': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractObject (code: string) {
|
||||||
|
// Strip comments
|
||||||
|
code = code.replace(/^\s*\/\/.*$/gm, '')
|
||||||
|
|
||||||
|
const stack = []
|
||||||
|
let result = ''
|
||||||
|
do {
|
||||||
|
if (stack[0] === code[0] && result.slice(-1) !== '\\') {
|
||||||
|
stack.shift()
|
||||||
|
} else if (code[0] in starts) {
|
||||||
|
stack.unshift(starts[code[0]])
|
||||||
|
}
|
||||||
|
result += code[0]
|
||||||
|
code = code.slice(1)
|
||||||
|
} while (stack.length && code.length)
|
||||||
|
return result
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { defineNuxtModule, addTemplate, addPlugin, templateUtils } from '@nuxt/kit'
|
import { defineNuxtModule, addTemplate, addPlugin, templateUtils, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
|
||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
import { resolveLayouts, resolvePagesRoutes, addComponentToRoutes } from './utils'
|
import { resolveLayouts, resolvePagesRoutes, normalizeRoutes } from './utils'
|
||||||
|
import { TransformMacroPlugin, TransformMacroPluginOptions } from './macros'
|
||||||
|
|
||||||
export default defineNuxtModule({
|
export default defineNuxtModule({
|
||||||
meta: {
|
meta: {
|
||||||
@ -41,8 +42,18 @@ export default defineNuxtModule({
|
|||||||
const composablesFile = resolve(runtimeDir, 'composables')
|
const composablesFile = resolve(runtimeDir, 'composables')
|
||||||
autoImports.push({ name: 'useRouter', as: 'useRouter', from: composablesFile })
|
autoImports.push({ name: 'useRouter', as: 'useRouter', from: composablesFile })
|
||||||
autoImports.push({ name: 'useRoute', as: 'useRoute', from: composablesFile })
|
autoImports.push({ name: 'useRoute', as: 'useRoute', from: composablesFile })
|
||||||
|
autoImports.push({ name: 'definePageMeta', as: 'definePageMeta', from: composablesFile })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Extract macros from pages
|
||||||
|
const macroOptions: TransformMacroPluginOptions = {
|
||||||
|
macros: {
|
||||||
|
definePageMeta: 'meta'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addVitePlugin(TransformMacroPlugin.vite(macroOptions))
|
||||||
|
addWebpackPlugin(TransformMacroPlugin.webpack(macroOptions))
|
||||||
|
|
||||||
// Add router plugin
|
// Add router plugin
|
||||||
addPlugin(resolve(runtimeDir, 'router'))
|
addPlugin(resolve(runtimeDir, 'router'))
|
||||||
|
|
||||||
@ -52,8 +63,8 @@ export default defineNuxtModule({
|
|||||||
async getContents () {
|
async getContents () {
|
||||||
const pages = await resolvePagesRoutes(nuxt)
|
const pages = await resolvePagesRoutes(nuxt)
|
||||||
await nuxt.callHook('pages:extend', pages)
|
await nuxt.callHook('pages:extend', pages)
|
||||||
const serializedRoutes = addComponentToRoutes(pages)
|
const { routes: serializedRoutes, imports } = normalizeRoutes(pages)
|
||||||
return `export default ${templateUtils.serialize(serializedRoutes)}`
|
return [...imports, `export default ${templateUtils.serialize(serializedRoutes)}`].join('\n')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ComputedRef, /* KeepAliveProps, */ Ref, TransitionProps } from 'vue'
|
||||||
import type { Router, RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { Router, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
import { useNuxtApp } from '#app'
|
import { useNuxtApp } from '#app'
|
||||||
|
|
||||||
@ -8,3 +9,29 @@ export const useRouter = () => {
|
|||||||
export const useRoute = () => {
|
export const useRoute = () => {
|
||||||
return useNuxtApp()._route as RouteLocationNormalizedLoaded
|
return useNuxtApp()._route as RouteLocationNormalizedLoaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PageMeta {
|
||||||
|
[key: string]: any
|
||||||
|
transition?: false | TransitionProps
|
||||||
|
layout?: false | string | Ref<false | string> | ComputedRef<false | string>
|
||||||
|
// TODO: https://github.com/vuejs/vue-next/issues/3652
|
||||||
|
// keepalive?: false | KeepAliveProps
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'vue-router' {
|
||||||
|
interface RouteMeta extends PageMeta {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const warnRuntimeUsage = (method: string) =>
|
||||||
|
console.warn(
|
||||||
|
`${method}() is a compiler-hint helper that is only usable inside ` +
|
||||||
|
'<script setup> of a single file component. Its arguments should be ' +
|
||||||
|
'compiled away and passing it at runtime has no effect.'
|
||||||
|
)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export const definePageMeta = (meta: PageMeta): void => {
|
||||||
|
if (process.dev) {
|
||||||
|
warnRuntimeUsage('definePageMeta')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { defineComponent, h } from 'vue'
|
import { defineComponent, h, Ref } from 'vue'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import layouts from '#build/layouts'
|
import layouts from '#build/layouts'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
type: [String, Boolean],
|
type: [String, Boolean, Object] as unknown as () => string | false | Ref<string | false>,
|
||||||
default: 'default'
|
default: 'default'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup (props, context) {
|
setup (props, context) {
|
||||||
return () => {
|
return () => {
|
||||||
const layout = props.name
|
const layout = (props.name && typeof props.name === 'object' ? props.name.value : props.name) ?? 'default'
|
||||||
if (!layouts[layout]) {
|
if (!layouts[layout]) {
|
||||||
if (process.dev && layout && layout !== 'default') {
|
if (process.dev && layout && layout !== 'default') {
|
||||||
console.warn(`Invalid layout \`${layout}\` selected.`)
|
console.warn(`Invalid layout \`${layout}\` selected.`)
|
||||||
|
@ -1,27 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<NuxtLayout v-if="Component" :name="layout || updatedComponentLayout || Component.type.layout">
|
<NuxtLayout v-if="Component" :name="layout || route.meta.layout">
|
||||||
<transition name="page" mode="out-in">
|
<NuxtTransition :options="route.meta.transition ?? { name: 'page', mode: 'out-in' }">
|
||||||
<!-- <keep-alive> -->
|
|
||||||
<Suspense @pending="() => onSuspensePending(Component)" @resolve="() => onSuspenseResolved(Component)">
|
<Suspense @pending="() => onSuspensePending(Component)" @resolve="() => onSuspenseResolved(Component)">
|
||||||
<component :is="Component" :key="$route.path" />
|
<component :is="Component" :key="route.path" />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<!-- <keep-alive -->
|
</NuxtTransition>
|
||||||
</transition>
|
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
<!-- TODO: Handle 404 placeholder -->
|
<!-- TODO: Handle 404 placeholder -->
|
||||||
</RouterView>
|
</RouterView>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { ref } from 'vue'
|
import { defineComponent, h, Transition } from 'vue'
|
||||||
|
|
||||||
import NuxtLayout from './layout'
|
import NuxtLayout from './layout'
|
||||||
import { useNuxtApp } from '#app'
|
import { useNuxtApp } from '#app'
|
||||||
|
|
||||||
export default {
|
const NuxtTransition = defineComponent({
|
||||||
|
name: 'NuxtTransition',
|
||||||
|
props: {
|
||||||
|
options: [Object, Boolean]
|
||||||
|
},
|
||||||
|
setup (props, { slots }) {
|
||||||
|
return () => props.options ? h(Transition, props.options, slots.default) : slots.default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
name: 'NuxtPage',
|
name: 'NuxtPage',
|
||||||
components: { NuxtLayout },
|
components: { NuxtLayout, NuxtTransition },
|
||||||
props: {
|
props: {
|
||||||
layout: {
|
layout: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -29,15 +36,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup () {
|
setup () {
|
||||||
// Disable HMR reactivity in production
|
|
||||||
const updatedComponentLayout = process.dev ? ref(null) : null
|
|
||||||
|
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
|
||||||
function onSuspensePending (Component) {
|
function onSuspensePending (Component) {
|
||||||
if (process.dev) {
|
|
||||||
updatedComponentLayout.value = Component.type.layout || null
|
|
||||||
}
|
|
||||||
return nuxtApp.callHook('page:start', Component)
|
return nuxtApp.callHook('page:start', Component)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,10 +47,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updatedComponentLayout,
|
|
||||||
onSuspensePending,
|
onSuspensePending,
|
||||||
onSuspenseResolved
|
onSuspenseResolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { basename, extname, relative, resolve } from 'pathe'
|
import { basename, extname, relative, resolve } from 'pathe'
|
||||||
import { encodePath } from 'ufo'
|
import { encodePath } from 'ufo'
|
||||||
import type { Nuxt, NuxtRoute } from '@nuxt/schema'
|
import type { Nuxt, NuxtPage } from '@nuxt/schema'
|
||||||
import { resolveFiles } from '@nuxt/kit'
|
import { resolveFiles } from '@nuxt/kit'
|
||||||
import { kebabCase } from 'scule'
|
import { kebabCase, pascalCase } from 'scule'
|
||||||
|
|
||||||
enum SegmentParserState {
|
enum SegmentParserState {
|
||||||
initial,
|
initial,
|
||||||
@ -32,15 +32,15 @@ export async function resolvePagesRoutes (nuxt: Nuxt) {
|
|||||||
return generateRoutesFromFiles(files, pagesDir)
|
return generateRoutesFromFiles(files, pagesDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateRoutesFromFiles (files: string[], pagesDir: string): NuxtRoute[] {
|
export function generateRoutesFromFiles (files: string[], pagesDir: string): NuxtPage[] {
|
||||||
const routes: NuxtRoute[] = []
|
const routes: NuxtPage[] = []
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const segments = relative(pagesDir, file)
|
const segments = relative(pagesDir, file)
|
||||||
.replace(new RegExp(`${extname(file)}$`), '')
|
.replace(new RegExp(`${extname(file)}$`), '')
|
||||||
.split('/')
|
.split('/')
|
||||||
|
|
||||||
const route: NuxtRoute = {
|
const route: NuxtPage = {
|
||||||
name: '',
|
name: '',
|
||||||
path: '',
|
path: '',
|
||||||
file,
|
file,
|
||||||
@ -183,7 +183,7 @@ function parseSegment (segment: string) {
|
|||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareRoutes (routes: NuxtRoute[], parent?: NuxtRoute) {
|
function prepareRoutes (routes: NuxtPage[], parent?: NuxtPage) {
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
// Remove -index
|
// Remove -index
|
||||||
if (route.name) {
|
if (route.name) {
|
||||||
@ -225,10 +225,18 @@ export async function resolveLayouts (nuxt: Nuxt) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addComponentToRoutes (routes: NuxtRoute[]) {
|
export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> = new Set()): { imports: Set<string>, routes: NuxtPage[]} {
|
||||||
return routes.map(route => ({
|
return {
|
||||||
|
imports: metaImports,
|
||||||
|
routes: routes.map((route) => {
|
||||||
|
const metaImportName = `${pascalCase(route.file.replace(/[^\w]/g, ''))}Meta`
|
||||||
|
metaImports.add(`import { meta as ${metaImportName} } from '${route.file}?macro=true'`)
|
||||||
|
return {
|
||||||
...route,
|
...route,
|
||||||
children: route.children ? addComponentToRoutes(route.children) : [],
|
children: route.children ? normalizeRoutes(route.children, metaImports).routes : [],
|
||||||
|
meta: route.meta || `{${metaImportName}}` as any,
|
||||||
component: `{() => import('${route.file}')}`
|
component: `{() => import('${route.file}')}`
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,10 @@ type RenderResult = {
|
|||||||
export type TSReference = { types: string } | { path: string }
|
export type TSReference = { types: string } | { path: string }
|
||||||
|
|
||||||
export type NuxtPage = {
|
export type NuxtPage = {
|
||||||
name?: string,
|
name?: string
|
||||||
path: string,
|
path: string
|
||||||
file: string,
|
file: string
|
||||||
|
meta?: Record<string, any>
|
||||||
children?: NuxtPage[]
|
children?: NuxtPage[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user