2024-01-29 16:44:54 +00:00
import { runInNewContext } from 'node:vm'
2023-07-04 05:24:50 +00:00
import fs from 'node:fs'
2022-06-27 12:10:29 +00:00
import { extname , normalize , relative , resolve } from 'pathe'
2023-09-26 00:09:12 +00:00
import { encodePath , joinURL , withLeadingSlash } from 'ufo'
2023-09-19 21:26:15 +00:00
import { logger , resolveFiles , useNuxt } from '@nuxt/kit'
2023-04-07 16:02:47 +00:00
import { genArrayFromRaw , genDynamicImport , genImport , genSafeVariableName } from 'knitwork'
2022-01-27 11:13:32 +00:00
import escapeRE from 'escape-string-regexp'
2023-01-30 20:24:58 +00:00
import { filename } from 'pathe/utils'
import { hash } from 'ohash'
2023-07-04 05:24:50 +00:00
import { transform } from 'esbuild'
import { parse } from 'acorn'
import type { CallExpression , ExpressionStatement , ObjectExpression , Program , Property } from 'estree'
2023-02-13 22:42:04 +00:00
import type { NuxtPage } from 'nuxt/schema'
2020-08-18 18:34:08 +00:00
2023-03-11 21:16:01 +00:00
import { uniqueBy } from '../core/utils'
2023-12-23 14:22:58 +00:00
import { toArray } from '../utils'
2024-02-26 17:39:26 +00:00
import { distDir } from '../dirs'
2023-03-11 21:16:01 +00:00
2021-03-18 14:26:41 +00:00
enum SegmentParserState {
initial ,
static ,
dynamic ,
2022-04-26 16:10:05 +00:00
optional ,
2021-06-21 12:09:08 +00:00
catchall ,
2021-03-18 14:26:41 +00:00
}
enum SegmentTokenType {
static ,
dynamic ,
2022-04-26 16:10:05 +00:00
optional ,
2021-06-21 12:09:08 +00:00
catchall ,
2021-03-18 14:26:41 +00:00
}
interface SegmentToken {
type : SegmentTokenType
value : string
}
2023-09-12 09:46:35 +00:00
interface ScannedFile {
relativePath : string
absolutePath : string
}
2022-03-22 18:12:54 +00:00
export async function resolvePagesRoutes ( ) : Promise < NuxtPage [ ] > {
const nuxt = useNuxt ( )
const pagesDirs = nuxt . options . _layers . map (
2023-09-12 14:27:28 +00:00
layer = > resolve ( layer . config . srcDir , ( layer . config . rootDir === nuxt . options . rootDir ? nuxt.options : layer.config ) . dir ? . pages || 'pages' )
2022-03-22 18:12:54 +00:00
)
2020-08-18 18:34:08 +00:00
2023-09-12 09:46:35 +00:00
const scannedFiles : ScannedFile [ ] = [ ]
for ( const dir of pagesDirs ) {
const files = await resolveFiles ( dir , ` **/*{ ${ nuxt . options . extensions . join ( ',' ) } } ` )
scannedFiles . push ( . . . files . map ( file = > ( { relativePath : relative ( dir , file ) , absolutePath : file } ) ) )
}
2024-01-14 11:54:23 +00:00
// sort scanned files using en-US locale to make the result consistent across different system locales
scannedFiles . sort ( ( a , b ) = > a . relativePath . localeCompare ( b . relativePath , 'en-US' ) )
2023-09-12 09:46:35 +00:00
2024-01-29 16:44:54 +00:00
const allRoutes = await generateRoutesFromFiles ( uniqueBy ( scannedFiles , 'relativePath' ) , {
shouldExtractBuildMeta : nuxt.options.experimental.scanPageMeta || nuxt . options . experimental . typedPages ,
2024-02-26 17:39:26 +00:00
shouldUseServerComponents : ! ! nuxt . options . experimental . componentIslands ,
2024-01-29 16:44:54 +00:00
vfs : nuxt.vfs
} )
2021-05-20 11:42:41 +00:00
2022-03-25 11:55:05 +00:00
return uniqueBy ( allRoutes , 'path' )
2021-01-18 12:22:38 +00:00
}
2024-01-29 16:44:54 +00:00
type GenerateRoutesFromFilesOptions = {
shouldExtractBuildMeta? : boolean
2024-02-26 17:39:26 +00:00
shouldUseServerComponents? : boolean
2024-01-29 16:44:54 +00:00
vfs? : Record < string , string >
}
export async function generateRoutesFromFiles ( files : ScannedFile [ ] , options : GenerateRoutesFromFilesOptions = { } ) : Promise < NuxtPage [ ] > {
2022-01-17 18:27:23 +00:00
const routes : NuxtPage [ ] = [ ]
2020-08-18 18:34:08 +00:00
for ( const file of files ) {
2023-09-12 09:46:35 +00:00
const segments = file . relativePath
. replace ( new RegExp ( ` ${ escapeRE ( extname ( file . relativePath ) ) } $ ` ) , '' )
2020-08-18 18:34:08 +00:00
. split ( '/' )
2022-01-17 18:27:23 +00:00
const route : NuxtPage = {
2020-08-18 18:34:08 +00:00
name : '' ,
path : '' ,
2023-09-12 09:46:35 +00:00
file : file.absolutePath ,
2024-03-09 06:48:15 +00:00
children : [ ]
2020-08-18 18:34:08 +00:00
}
2021-05-20 11:42:41 +00:00
// Array where routes should be added, useful when adding child routes
2020-08-18 18:34:08 +00:00
let parent = routes
2024-03-06 14:38:39 +00:00
const lastSegment = segments [ segments . length - 1 ]
if ( lastSegment . endsWith ( '.server' ) ) {
segments [ segments . length - 1 ] = lastSegment . replace ( '.server' , '' )
2024-02-26 17:39:26 +00:00
if ( options . shouldUseServerComponents ) {
route . mode = 'server'
}
2024-03-06 14:38:39 +00:00
} else if ( lastSegment . endsWith ( '.client' ) ) {
segments [ segments . length - 1 ] = lastSegment . replace ( '.client' , '' )
route . mode = 'client'
2024-02-26 17:39:26 +00:00
}
2021-01-18 12:22:38 +00:00
for ( let i = 0 ; i < segments . length ; i ++ ) {
const segment = segments [ i ]
2020-08-18 18:34:08 +00:00
2021-01-18 12:22:38 +00:00
const tokens = parseSegment ( segment )
const segmentName = tokens . map ( ( { value } ) = > value ) . join ( '' )
2020-08-18 18:34:08 +00:00
2021-01-18 12:22:38 +00:00
// ex: parent/[slug].vue -> parent-slug
2023-02-13 22:56:39 +00:00
route . name += ( route . name && '/' ) + segmentName
2021-01-18 12:22:38 +00:00
// ex: parent.vue + parent/child.vue
2023-09-26 00:09:12 +00:00
const path = withLeadingSlash ( joinURL ( route . path , getRoutePath ( tokens ) . replace ( /\/index$/ , '/' ) ) )
2023-09-11 10:50:19 +00:00
const child = parent . find ( parentRoute = > parentRoute . name === route . name && parentRoute . path === path )
2022-08-01 07:51:46 +00:00
2022-08-12 17:47:58 +00:00
if ( child && child . children ) {
2020-08-18 18:34:08 +00:00
parent = child . children
route . path = ''
2021-01-18 12:22:38 +00:00
} else if ( segmentName === 'index' && ! route . path ) {
2020-08-18 18:34:08 +00:00
route . path += '/'
2021-01-18 12:22:38 +00:00
} else if ( segmentName !== 'index' ) {
route . path += getRoutePath ( tokens )
2020-08-18 18:34:08 +00:00
}
}
2024-01-29 16:44:54 +00:00
if ( options . shouldExtractBuildMeta && options . vfs ) {
const fileContent = file . absolutePath in options . vfs ? options . vfs [ file . absolutePath ] : fs . readFileSync ( file . absolutePath , 'utf-8' )
Object . assign ( route , await getRouteMeta ( fileContent , file . absolutePath ) )
2023-07-04 05:24:50 +00:00
}
2020-08-18 18:34:08 +00:00
parent . push ( route )
}
return prepareRoutes ( routes )
}
2023-07-04 05:24:50 +00:00
const SFC_SCRIPT_RE = /<script\s*[^>]*>([\s\S]*?)<\/script\s*[^>]*>/i
2023-08-23 20:38:17 +00:00
export function extractScriptContent ( html : string ) {
2023-07-04 05:24:50 +00:00
const match = html . match ( SFC_SCRIPT_RE )
if ( match && match [ 1 ] ) {
return match [ 1 ] . trim ( )
}
return null
}
const PAGE_META_RE = /(definePageMeta\([\s\S]*?\))/
2024-01-29 16:44:54 +00:00
const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
2023-07-04 05:24:50 +00:00
2024-02-04 21:27:32 +00:00
const pageContentsCache : Record < string , string > = { }
2024-01-29 16:44:54 +00:00
const metaCache : Record < string , Partial < Record < keyof NuxtPage , any > >> = { }
2024-02-04 21:27:32 +00:00
async function getRouteMeta ( contents : string , absolutePath : string ) : Promise < Partial < Record < keyof NuxtPage , any > >> {
// set/update pageContentsCache, invalidate metaCache on cache mismatch
2024-02-07 10:00:19 +00:00
if ( ! ( absolutePath in pageContentsCache ) || pageContentsCache [ absolutePath ] !== contents ) {
pageContentsCache [ absolutePath ] = contents
2024-02-04 21:27:32 +00:00
delete metaCache [ absolutePath ]
}
if ( absolutePath in metaCache ) { return metaCache [ absolutePath ] }
2023-07-04 05:24:50 +00:00
2024-01-29 16:44:54 +00:00
const script = extractScriptContent ( contents )
if ( ! script ) {
2024-02-04 21:27:32 +00:00
metaCache [ absolutePath ] = { }
2024-01-29 16:44:54 +00:00
return { }
}
if ( ! PAGE_META_RE . test ( script ) ) {
2024-02-04 21:27:32 +00:00
metaCache [ absolutePath ] = { }
2024-01-29 16:44:54 +00:00
return { }
}
2023-07-04 05:24:50 +00:00
const js = await transform ( script , { loader : 'ts' } )
const ast = parse ( js . code , {
sourceType : 'module' ,
2024-01-29 16:44:54 +00:00
ecmaVersion : 'latest' ,
ranges : true
2023-07-04 05:24:50 +00:00
} ) as unknown as Program
const pageMetaAST = ast . body . find ( node = > node . type === 'ExpressionStatement' && node . expression . type === 'CallExpression' && node . expression . callee . type === 'Identifier' && node . expression . callee . name === 'definePageMeta' )
2024-01-29 16:44:54 +00:00
if ( ! pageMetaAST ) {
2024-02-04 21:27:32 +00:00
metaCache [ absolutePath ] = { }
2024-01-29 16:44:54 +00:00
return { }
}
2023-07-04 05:24:50 +00:00
const pageMetaArgument = ( ( pageMetaAST as ExpressionStatement ) . expression as CallExpression ) . arguments [ 0 ] as ObjectExpression
2024-01-29 16:44:54 +00:00
const extractedMeta = { } as Partial < Record < keyof NuxtPage , any > >
const extractionKeys = [ 'name' , 'path' , 'alias' , 'redirect' ] as const
const dynamicProperties = new Set < keyof NuxtPage > ( )
for ( const key of extractionKeys ) {
const property = pageMetaArgument . properties . find ( property = > property . type === 'Property' && property . key . type === 'Identifier' && property . key . name === key ) as Property
if ( ! property ) { continue }
if ( property . value . type === 'ObjectExpression' ) {
const valueString = js . code . slice ( property . value . range ! [ 0 ] , property . value . range ! [ 1 ] )
try {
extractedMeta [ key ] = JSON . parse ( runInNewContext ( ` JSON.stringify( ${ valueString } ) ` , { } ) )
} catch {
console . debug ( ` [nuxt] Skipping extraction of \` ${ key } \` metadata as it is not JSON-serializable (reading \` ${ absolutePath } \` ). ` )
dynamicProperties . add ( key )
continue
}
}
if ( property . value . type === 'ArrayExpression' ) {
const values = [ ]
for ( const element of property . value . elements ) {
if ( ! element ) {
continue
}
if ( element . type !== 'Literal' || typeof element . value !== 'string' ) {
console . debug ( ` [nuxt] Skipping extraction of \` ${ key } \` metadata as it is not an array of string literals (reading \` ${ absolutePath } \` ). ` )
dynamicProperties . add ( key )
continue
}
values . push ( element . value )
}
extractedMeta [ key ] = values
continue
}
if ( property . value . type !== 'Literal' || typeof property . value . value !== 'string' ) {
console . debug ( ` [nuxt] Skipping extraction of \` ${ key } \` metadata as it is not a string literal or array of string literals (reading \` ${ absolutePath } \` ). ` )
dynamicProperties . add ( key )
continue
}
extractedMeta [ key ] = property . value . value
}
const extraneousMetaKeys = pageMetaArgument . properties
. filter ( property = > property . type === 'Property' && property . key . type === 'Identifier' && ! ( extractionKeys as unknown as string [ ] ) . includes ( property . key . name ) )
// @ts-expect-error inferred types have been filtered out
. map ( property = > property . key . name )
if ( extraneousMetaKeys . length ) {
dynamicProperties . add ( 'meta' )
}
if ( dynamicProperties . size ) {
extractedMeta . meta ? ? = { }
extractedMeta . meta [ DYNAMIC_META_KEY ] = dynamicProperties
}
2023-07-04 05:24:50 +00:00
2024-02-04 21:27:32 +00:00
metaCache [ absolutePath ] = extractedMeta
2024-01-29 16:44:54 +00:00
return extractedMeta
2023-07-04 05:24:50 +00:00
}
2021-01-18 12:22:38 +00:00
function getRoutePath ( tokens : SegmentToken [ ] ) : string {
return tokens . reduce ( ( path , token ) = > {
return (
path +
2022-04-26 16:10:05 +00:00
( token . type === SegmentTokenType . optional
? ` : ${ token . value } ? `
: token . type === SegmentTokenType . dynamic
2023-04-03 09:56:44 +00:00
? ` : ${ token . value } () `
2022-04-26 16:10:05 +00:00
: token . type === SegmentTokenType . catchall
? ` : ${ token . value } (.*)* `
2023-06-25 16:40:30 +00:00
: encodePath ( token . value ) . replace ( /:/g , '\\:' ) )
2021-01-18 12:22:38 +00:00
)
} , '/' )
}
2021-06-21 12:09:08 +00:00
const PARAM_CHAR_RE = /[\w\d_.]/
2021-01-18 12:22:38 +00:00
function parseSegment ( segment : string ) {
2021-06-21 12:09:08 +00:00
let state : SegmentParserState = SegmentParserState . initial
2021-01-18 12:22:38 +00:00
let i = 0
let buffer = ''
const tokens : SegmentToken [ ] = [ ]
function consumeBuffer ( ) {
if ( ! buffer ) {
return
}
if ( state === SegmentParserState . initial ) {
throw new Error ( 'wrong state' )
}
tokens . push ( {
type :
state === SegmentParserState . static
? SegmentTokenType . static
2021-06-21 12:09:08 +00:00
: state === SegmentParserState . dynamic
? SegmentTokenType . dynamic
2022-04-26 16:10:05 +00:00
: state === SegmentParserState . optional
? SegmentTokenType . optional
: SegmentTokenType . catchall ,
2021-01-18 12:22:38 +00:00
value : buffer
} )
buffer = ''
}
while ( i < segment . length ) {
const c = segment [ i ]
switch ( state ) {
case SegmentParserState . initial :
buffer = ''
if ( c === '[' ) {
state = SegmentParserState . dynamic
} else {
i --
state = SegmentParserState . static
}
break
case SegmentParserState . static :
if ( c === '[' ) {
consumeBuffer ( )
state = SegmentParserState . dynamic
} else {
buffer += c
}
break
2021-06-21 12:09:08 +00:00
case SegmentParserState . catchall :
2021-01-18 12:22:38 +00:00
case SegmentParserState . dynamic :
2022-04-26 16:10:05 +00:00
case SegmentParserState . optional :
2021-06-21 12:09:08 +00:00
if ( buffer === '...' ) {
buffer = ''
state = SegmentParserState . catchall
}
2022-04-26 16:10:05 +00:00
if ( c === '[' && state === SegmentParserState . dynamic ) {
state = SegmentParserState . optional
}
2023-09-11 08:13:24 +00:00
if ( c === ']' && ( state !== SegmentParserState . optional || segment [ i - 1 ] === ']' ) ) {
2021-10-20 18:12:55 +00:00
if ( ! buffer ) {
throw new Error ( 'Empty param' )
} else {
consumeBuffer ( )
}
2021-01-18 12:22:38 +00:00
state = SegmentParserState . initial
} else if ( PARAM_CHAR_RE . test ( c ) ) {
buffer += c
} else {
2023-01-14 01:13:48 +00:00
2021-10-20 18:12:55 +00:00
// console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`)
2021-01-18 12:22:38 +00:00
}
break
}
i ++
}
if ( state === SegmentParserState . dynamic ) {
throw new Error ( ` Unfinished param " ${ buffer } " ` )
}
consumeBuffer ( )
return tokens
}
2023-02-13 22:56:39 +00:00
function findRouteByName ( name : string , routes : NuxtPage [ ] ) : NuxtPage | undefined {
for ( const route of routes ) {
if ( route . name === name ) {
return route
}
}
return findRouteByName ( name , routes )
}
function prepareRoutes ( routes : NuxtPage [ ] , parent? : NuxtPage , names = new Set < string > ( ) ) {
2020-08-18 18:34:08 +00:00
for ( const route of routes ) {
2021-01-18 12:22:38 +00:00
// Remove -index
2020-08-18 18:34:08 +00:00
if ( route . name ) {
2023-02-13 22:56:39 +00:00
route . name = route . name
. replace ( /\/index$/ , '' )
. replace ( /\//g , '-' )
if ( names . has ( route . name ) ) {
const existingRoute = findRouteByName ( route . name , routes )
const extra = existingRoute ? . name ? ` is the same as \` ${ existingRoute . file } \` ` : 'is a duplicate'
2023-09-19 21:26:15 +00:00
logger . warn ( ` Route name generated for \` ${ route . file } \` ${ extra } . You may wish to set a custom name using \` definePageMeta \` within the page file. ` )
2023-02-13 22:56:39 +00:00
}
2020-08-18 18:34:08 +00:00
}
2021-01-18 12:22:38 +00:00
// Remove leading / if children route
2023-12-29 08:47:11 +00:00
if ( parent && route . path [ 0 ] === '/' ) {
2021-01-18 12:22:38 +00:00
route . path = route . path . slice ( 1 )
2020-08-18 18:34:08 +00:00
}
2022-08-12 17:47:58 +00:00
if ( route . children ? . length ) {
2023-02-13 22:56:39 +00:00
route . children = prepareRoutes ( route . children , route , names )
2021-01-18 12:22:38 +00:00
}
2022-08-12 17:47:58 +00:00
if ( route . children ? . find ( childRoute = > childRoute . path === '' ) ) {
2020-08-18 18:34:08 +00:00
delete route . name
}
2023-02-13 22:56:39 +00:00
if ( route . name ) {
names . add ( route . name )
}
2020-08-18 18:34:08 +00:00
}
2021-01-18 12:22:38 +00:00
2020-08-18 18:34:08 +00:00
return routes
}
2021-06-30 16:32:22 +00:00
2024-01-29 16:44:54 +00:00
function serializeRouteValue ( value : any , skipSerialisation = false ) {
2024-03-09 06:48:15 +00:00
if ( skipSerialisation || value === undefined ) { return undefined }
2024-01-29 16:44:54 +00:00
return JSON . stringify ( value )
}
type NormalizedRoute = Partial < Record < Exclude < keyof NuxtPage , ' file ' > , string >> & { component? : string }
type NormalizedRouteKeys = ( keyof NormalizedRoute ) [ ]
export function normalizeRoutes ( routes : NuxtPage [ ] , metaImports : Set < string > = new Set ( ) , overrideMeta = false ) : { imports : Set < string > , routes : string } {
2022-01-17 18:27:23 +00:00
return {
imports : metaImports ,
2022-12-12 12:25:00 +00:00
routes : genArrayFromRaw ( routes . map ( ( page ) = > {
2024-01-29 16:44:54 +00:00
const markedDynamic = page . meta ? . [ DYNAMIC_META_KEY ] ? ? new Set ( )
const metaFiltered : Record < string , any > = { }
let skipMeta = true
for ( const key in page . meta || { } ) {
if ( key !== DYNAMIC_META_KEY && page . meta ! [ key ] !== undefined ) {
skipMeta = false
metaFiltered [ key ] = page . meta ! [ key ]
2023-12-29 10:17:07 +00:00
}
}
2024-01-29 16:44:54 +00:00
const skipAlias = toArray ( page . alias ) . every ( val = > ! val )
const route : NormalizedRoute = {
path : serializeRouteValue ( page . path ) ,
name : serializeRouteValue ( page . name ) ,
meta : serializeRouteValue ( metaFiltered , skipMeta ) ,
alias : serializeRouteValue ( toArray ( page . alias ) , skipAlias ) ,
2024-03-09 06:48:15 +00:00
redirect : serializeRouteValue ( page . redirect )
2024-01-29 16:44:54 +00:00
}
2023-03-03 14:07:42 +00:00
2024-02-07 10:00:19 +00:00
for ( const key of [ 'path' , 'name' , 'meta' , 'alias' , 'redirect' ] satisfies NormalizedRouteKeys ) {
if ( route [ key ] === undefined ) {
delete route [ key ]
}
}
2023-03-03 14:07:42 +00:00
if ( page . children ? . length ) {
2024-01-29 16:44:54 +00:00
route . children = normalizeRoutes ( page . children , metaImports , overrideMeta ) . routes
2023-03-03 14:07:42 +00:00
}
// Without a file, we can't use `definePageMeta` to extract route-level meta from the file
if ( ! page . file ) {
return route
}
2022-12-12 12:25:00 +00:00
const file = normalize ( page . file )
2024-03-16 00:07:38 +00:00
const pageImportName = genSafeVariableName ( filename ( file ) + hash ( file ) )
const metaImportName = pageImportName + 'Meta'
2022-11-02 10:28:41 +00:00
metaImports . add ( genImport ( ` ${ file } ?macro=true ` , [ { name : 'default' , as : metaImportName } ] ) )
2022-09-05 07:53:01 +00:00
2024-03-16 00:07:38 +00:00
if ( page . _sync ) {
metaImports . add ( genImport ( file , [ { name : 'default' , as : pageImportName } ] ) )
}
const pageImport = page . _sync && page . mode !== 'client' ? pageImportName : genDynamicImport ( file , { interopDefault : true } )
2024-01-29 16:44:54 +00:00
const metaRoute : NormalizedRoute = {
name : ` ${ metaImportName } ?.name ?? ${ route . name } ` ,
path : ` ${ metaImportName } ?.path ?? ${ route . path } ` ,
meta : ` ${ metaImportName } || {} ` ,
alias : ` ${ metaImportName } ?.alias || [] ` ,
redirect : ` ${ metaImportName } ?.redirect ` ,
2024-02-26 17:39:26 +00:00
component : page.mode === 'server'
? ` () => createIslandPage( ${ route . name } ) `
2024-03-06 14:38:39 +00:00
: page . mode === 'client'
2024-03-16 00:07:38 +00:00
? ` () => createClientPage( ${ pageImport } ) `
: pageImport
2024-02-26 17:39:26 +00:00
}
if ( page . mode === 'server' ) {
metaImports . add ( `
let _createIslandPage
async function createIslandPage ( name ) {
_createIslandPage || = await import ( $ { JSON . stringify ( resolve ( distDir , 'components/runtime/server-component' ) ) } ) . then ( r = > r . createIslandPage )
return _createIslandPage ( name )
} ; ` )
2024-03-06 14:38:39 +00:00
} else if ( page . mode === 'client' ) {
metaImports . add ( `
let _createClientPage
async function createClientPage ( loader ) {
_createClientPage || = await import ( $ { JSON . stringify ( resolve ( distDir , 'components/runtime/client-component' ) ) } ) . then ( r = > r . createClientPage )
return _createClientPage ( loader ) ;
} ` )
2022-01-17 18:27:23 +00:00
}
2022-12-12 12:25:00 +00:00
2024-01-29 16:44:54 +00:00
if ( route . children != null ) {
metaRoute . children = route . children
}
2022-12-12 12:25:00 +00:00
2024-01-29 16:44:54 +00:00
if ( overrideMeta ) {
metaRoute . name = ` ${ metaImportName } ?.name `
metaRoute . path = ` ${ metaImportName } ?.path ?? '' `
// skip and retain fallback if marked dynamic
// set to extracted value or fallback if none extracted
for ( const key of [ 'name' , 'path' ] satisfies NormalizedRouteKeys ) {
2024-03-09 06:48:15 +00:00
if ( markedDynamic . has ( key ) ) { continue }
2024-01-29 16:44:54 +00:00
metaRoute [ key ] = route [ key ] ? ? metaRoute [ key ]
}
// set to extracted value or delete if none extracted
for ( const key of [ 'meta' , 'alias' , 'redirect' ] satisfies NormalizedRouteKeys ) {
2024-03-09 06:48:15 +00:00
if ( markedDynamic . has ( key ) ) { continue }
2024-01-29 16:44:54 +00:00
if ( route [ key ] == null ) {
delete metaRoute [ key ]
continue
}
metaRoute [ key ] = route [ key ]
}
} else {
if ( route . meta != null ) {
2024-01-31 09:54:01 +00:00
metaRoute . meta = ` { ...( ${ metaImportName } || {}), ... ${ route . meta } } `
2024-01-29 16:44:54 +00:00
}
if ( route . alias != null ) {
metaRoute . alias = ` ${ route . alias } .concat( ${ metaImportName } ?.alias || []) `
}
if ( route . redirect != null ) {
metaRoute . redirect = route . redirect
}
}
return metaRoute
2022-02-07 13:45:47 +00:00
} ) )
2022-01-17 18:27:23 +00:00
}
2021-10-20 18:49:15 +00:00
}
2023-08-23 20:38:17 +00:00
export function pathToNitroGlob ( path : string ) {
if ( ! path ) {
return null
}
// Ignore pages with multiple dynamic parameters.
if ( path . indexOf ( ':' ) !== path . lastIndexOf ( ':' ) ) {
return null
}
return path . replace ( /\/(?:[^:/]+)?:\w+.*$/ , '/**' )
}