diff --git a/packages/nuxt3/src/builder/app.ts b/packages/nuxt3/src/builder/app.ts index 0fd358b549..d9a6acaa13 100644 --- a/packages/nuxt3/src/builder/app.ts +++ b/packages/nuxt3/src/builder/app.ts @@ -1,13 +1,6 @@ import { resolve } from 'path' -import globby from 'globby' import { Builder } from './builder' - -export interface NuxtRoute { - name?: string - path: string - component: string - children?: NuxtRoute[] -} +import { NuxtRoute, resolvePagesRoutes } from './pages' export interface NuxtApp { srcDir: string @@ -18,122 +11,21 @@ export interface NuxtApp { // Scan project structure export async function resolveApp (builder: Builder, srcDir: string): Promise { const { nuxt } = builder - // resolve App.vue - const main = nuxt.resolver.tryResolvePath('~/App') || + + // Create base app object + const app: NuxtApp = { + srcDir, + main: '', + routes: [] + } + + // Resolve App.vue + app.main = nuxt.resolver.tryResolvePath('~/App') || nuxt.resolver.tryResolvePath('~/app') || resolve(nuxt.options.appDir, 'app.vue') - const pagesPattern = `${nuxt.options.dir.pages}/**/*.{${nuxt.options.extensions.join(',')}}` - const pages = await resolveFiles(builder, pagesPattern, srcDir) - const routes = buildRoutes(pages, srcDir, nuxt.options.dir.pages, nuxt.options.extensions) + // Resolve pages/ + app.routes.push(...await resolvePagesRoutes(builder, app)) - console.log('routes', routes) - // TODO: Read pages/ and create routes - // TODO: Detect store - // Use hooks? - // routes can be resolved with @nuxt/pages module to scan pages/ using a hook - // Import plugins - // Middleware - // Layouts - // etc. - - return { - srcDir, - main, - routes: [] - } -} - -async function resolveFiles (builder: Builder, pattern: string, srcDir: string) { - const { nuxt } = builder - - return builder.ignore.filter(await globby(pattern, { - cwd: srcDir, - followSymbolicLinks: nuxt.options.build.followSymlinks - })) -} - -const isDynamicRoute = (s: string) => /^\[.+\]$/.test(s) - -export function buildRoutes ( - files: string[], - srcDir: string, - pagesDir: string, - extensions: string[] -) { - const routes: NuxtRoute[] = [] - - for (const file of files) { - const pathParts = file - .replace(new RegExp(`^${pagesDir}`), '') - .replace(new RegExp(`\\.(${extensions.join('|')})$`), '') - .split('/') - .slice(1) // removing the pagesDir means that the path begins with a '/' - - const route: NuxtRoute = { - name: '', - path: '', - component: resolve(srcDir, file) - } - - let parent = routes - - for (let i = 0; i < pathParts.length; i++) { - const part = pathParts[i] - // Remove square brackets at the start and end. - const isDynamicPart = isDynamicRoute(part) - const normalizedPart = (isDynamicPart - ? part.replace(/^\[(\.{3})?/, '').replace(/\]$/, '') - : part - ).toLowerCase() - - route.name += route.name ? `-${normalizedPart}` : normalizedPart - - const child = parent.find( - parentRoute => parentRoute.name === route.name - ) - if (child) { - child.children = child.children || [] - parent = child.children - route.path = '' - } else if (normalizedPart === 'index' && !route.path) { - route.path += '/' - } else if (normalizedPart !== 'index') { - if (isDynamicPart) { - route.path += `/:${normalizedPart}` - - // Catch-all route - if (/^\[\.{3}/.test(part)) { - route.path += '(.*)' - } else if (i === pathParts.length - 1) { - route.path += '?' - } - } else { - route.path += `/${normalizedPart}` - } - } - } - - parent.push(route) - } - - return prepareRoutes(routes) -} - -function prepareRoutes (routes: NuxtRoute[], hasParent = false) { - for (const route of routes) { - if (route.name) { - route.name = route.name.replace(/-index$/, '') - } - - if (hasParent) { - route.path = route.path.replace(/^\//, '').replace(/\?$/, '') - } - - if (route.children) { - delete route.name - route.children = prepareRoutes(route.children, true) - } - } - return routes + return app } diff --git a/packages/nuxt3/src/builder/pages.ts b/packages/nuxt3/src/builder/pages.ts new file mode 100644 index 0000000000..c7f5c07e0a --- /dev/null +++ b/packages/nuxt3/src/builder/pages.ts @@ -0,0 +1,98 @@ +import { resolve, extname, relative } from 'path' +import { NuxtApp } from './app' +import { resolveFiles } from './utils' + +const isDynamicRoute = (s: string) => /^\[.+\]$/.test(s) + +export interface NuxtRoute { + name?: string + path: string + file: string + children?: NuxtRoute[] +} + +export async function resolvePagesRoutes (builder, app: NuxtApp) { + const { nuxt } = builder + + // TODO: these variables should be overriable by app not global + const pagesDirName = nuxt.options.dir.pages + const extensions = nuxt.options.extensions + + const pagesDir = resolve(app.srcDir, pagesDirName) + const pagesPattern = `${pagesDirName}/**/*.{${extensions.join(',')}}` + const files = await resolveFiles(builder, pagesPattern, app.srcDir) + + const routes: NuxtRoute[] = [] + + for (const file of files) { + const pathParts = relative(pagesDir, file) + .replace(new RegExp(`${extname(file)}$`), '') + .split('/') + + const route: NuxtRoute = { + name: '', + path: '', + file + } + + let parent = routes + + for (let i = 0; i < pathParts.length; i++) { + const part = pathParts[i] + // Remove square brackets at the start and end. + const isDynamicPart = isDynamicRoute(part) + const normalizedPart = (isDynamicPart + ? part.replace(/^\[(\.{3})?/, '').replace(/\]$/, '') + : part + ).toLowerCase() + + route.name += route.name ? `-${normalizedPart}` : normalizedPart + + const child = parent.find( + parentRoute => parentRoute.name === route.name + ) + if (child) { + child.children = child.children || [] + parent = child.children + route.path = '' + } else if (normalizedPart === 'index' && !route.path) { + route.path += '/' + } else if (normalizedPart !== 'index') { + if (isDynamicPart) { + route.path += `/:${normalizedPart}` + + // Catch-all route + if (/^\[\.{3}/.test(part)) { + route.path += '(.*)' + } else if (i === pathParts.length - 1) { + route.path += '?' + } + } else { + route.path += `/${normalizedPart}` + } + } + } + + parent.push(route) + } + + return prepareRoutes(routes) +} + +function prepareRoutes (routes: NuxtRoute[], hasParent = false) { + for (const route of routes) { + if (route.name) { + route.name = route.name.replace(/-index$/, '') + } + + if (hasParent) { + route.path = route.path.replace(/^\//, '').replace(/\?$/, '') + } + + if (route.children) { + delete route.name + route.children = prepareRoutes(route.children, true) + } + } + return routes +} diff --git a/packages/nuxt3/src/builder/utils.ts b/packages/nuxt3/src/builder/utils.ts new file mode 100644 index 0000000000..6b42370045 --- /dev/null +++ b/packages/nuxt3/src/builder/utils.ts @@ -0,0 +1,13 @@ +import { resolve } from 'path' +import globby from 'globby' +import { Builder } from './builder' + +// TODO: move to core resolver +export async function resolveFiles (builder: Builder, pattern: string, srcDir: string) { + const { nuxt } = builder + + return builder.ignore.filter(await globby(pattern, { + cwd: srcDir, + followSymbolicLinks: nuxt.options.build.followSymlinks + })).map(p => resolve(srcDir, p)) +}