2022-10-27 15:50:24 +00:00
|
|
|
import { existsSync, readdirSync } from 'node:fs'
|
2023-05-09 17:08:07 +00:00
|
|
|
import { mkdir, readFile } from 'node:fs/promises'
|
2024-01-29 12:23:51 +00:00
|
|
|
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, updateTemplates, useNitro } from '@nuxt/kit'
|
2023-05-09 17:08:07 +00:00
|
|
|
import { dirname, join, relative, resolve } from 'pathe'
|
2023-04-07 16:02:47 +00:00
|
|
|
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
2022-07-07 15:07:37 +00:00
|
|
|
import { joinURL } from 'ufo'
|
2023-08-14 17:07:17 +00:00
|
|
|
import type { Nuxt, NuxtApp, NuxtPage } from 'nuxt/schema'
|
2023-05-09 17:08:07 +00:00
|
|
|
import { createRoutesContext } from 'unplugin-vue-router'
|
|
|
|
import { resolveOptions } from 'unplugin-vue-router/options'
|
|
|
|
import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router'
|
2023-03-11 21:16:01 +00:00
|
|
|
|
2023-08-23 20:38:17 +00:00
|
|
|
import type { NitroRouteConfig } from 'nitropack'
|
|
|
|
import { defu } from 'defu'
|
2021-08-11 21:26:47 +00:00
|
|
|
import { distDir } from '../dirs'
|
2023-04-07 16:02:47 +00:00
|
|
|
import { normalizeRoutes, resolvePagesRoutes } from './utils'
|
2023-08-23 20:38:17 +00:00
|
|
|
import { extractRouteRules, getMappedPages } from './route-rules'
|
|
|
|
import type { PageMetaPluginOptions } from './plugins/page-meta'
|
|
|
|
import { PageMetaPlugin } from './plugins/page-meta'
|
|
|
|
import { RouteInjectionPlugin } from './plugins/route-injection'
|
2021-05-20 11:42:41 +00:00
|
|
|
|
2023-05-22 20:25:42 +00:00
|
|
|
const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/
|
|
|
|
|
2021-05-20 11:42:41 +00:00
|
|
|
export default defineNuxtModule({
|
2022-01-05 18:09:53 +00:00
|
|
|
meta: {
|
2022-04-13 17:18:51 +00:00
|
|
|
name: 'pages'
|
2022-01-05 18:09:53 +00:00
|
|
|
},
|
2023-05-09 17:08:07 +00:00
|
|
|
async setup (_options, nuxt) {
|
|
|
|
const useExperimentalTypedPages = nuxt.options.experimental.typedPages
|
2024-01-18 16:06:00 +00:00
|
|
|
const runtimeDir = resolve(distDir, 'pages/runtime')
|
2022-03-22 18:12:54 +00:00
|
|
|
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
|
|
|
)
|
2021-07-28 11:35:24 +00:00
|
|
|
|
2024-01-18 16:06:00 +00:00
|
|
|
async function resolveRouterOptions () {
|
|
|
|
const context = {
|
|
|
|
files: [] as Array<{ path: string, optional?: boolean }>
|
|
|
|
}
|
|
|
|
// Add default options
|
|
|
|
context.files.push({ path: resolve(runtimeDir, 'router.options'), optional: true })
|
|
|
|
|
2024-01-23 17:44:14 +00:00
|
|
|
for (const layer of nuxt.options._layers) {
|
2024-01-18 16:06:00 +00:00
|
|
|
const path = await findPath(resolve(layer.config.srcDir, 'app/router.options'))
|
|
|
|
if (path) { context.files.push({ path }) }
|
2024-01-23 17:44:14 +00:00
|
|
|
}
|
2024-01-18 16:06:00 +00:00
|
|
|
|
|
|
|
await nuxt.callHook('pages:routerOptions', context)
|
|
|
|
return context.files
|
|
|
|
}
|
|
|
|
|
2022-04-13 17:18:51 +00:00
|
|
|
// Disable module (and use universal router) if pages dir do not exists or user has disabled it
|
2022-10-27 15:50:24 +00:00
|
|
|
const isNonEmptyDir = (dir: string) => existsSync(dir) && readdirSync(dir).length
|
2023-03-09 11:46:08 +00:00
|
|
|
const userPreference = nuxt.options.pages
|
2023-05-15 12:47:30 +00:00
|
|
|
const isPagesEnabled = async () => {
|
2023-03-09 11:46:08 +00:00
|
|
|
if (typeof userPreference === 'boolean') {
|
|
|
|
return userPreference
|
2022-10-27 15:50:24 +00:00
|
|
|
}
|
2024-01-18 16:06:00 +00:00
|
|
|
const routerOptionsFiles = await resolveRouterOptions()
|
|
|
|
if (routerOptionsFiles.filter(p => !p.optional).length > 0) {
|
2022-10-27 15:50:24 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
if (pagesDirs.some(dir => isNonEmptyDir(dir))) {
|
|
|
|
return true
|
|
|
|
}
|
2023-05-15 12:47:30 +00:00
|
|
|
|
|
|
|
const pages = await resolvePagesRoutes()
|
|
|
|
await nuxt.callHook('pages:extend', pages)
|
|
|
|
if (pages.length) { return true }
|
|
|
|
|
2022-10-27 15:50:24 +00:00
|
|
|
return false
|
|
|
|
}
|
2023-05-15 12:47:30 +00:00
|
|
|
nuxt.options.pages = await isPagesEnabled()
|
2022-10-27 15:50:24 +00:00
|
|
|
|
2024-01-29 16:52:03 +00:00
|
|
|
if (nuxt.options.dev && nuxt.options.pages) {
|
|
|
|
// Add plugin to check if pages are enabled without NuxtPage being instantiated
|
|
|
|
addPlugin(resolve(runtimeDir, 'plugins/check-if-page-unused'))
|
|
|
|
}
|
|
|
|
|
2023-10-30 16:55:40 +00:00
|
|
|
nuxt.hook('app:templates', async (app) => {
|
|
|
|
app.pages = await resolvePagesRoutes()
|
|
|
|
await nuxt.callHook('pages:extend', app.pages)
|
|
|
|
})
|
|
|
|
|
2023-03-09 11:46:08 +00:00
|
|
|
// Restart Nuxt when pages dir is added or removed
|
2023-09-12 14:27:28 +00:00
|
|
|
const restartPaths = nuxt.options._layers.flatMap((layer) => {
|
|
|
|
const pagesDir = (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages'
|
|
|
|
return [
|
|
|
|
join(layer.config.srcDir || layer.cwd, 'app/router.options.ts'),
|
|
|
|
join(layer.config.srcDir || layer.cwd, pagesDir)
|
|
|
|
]
|
|
|
|
})
|
2023-07-26 09:16:01 +00:00
|
|
|
|
|
|
|
nuxt.hooks.hook('builder:watch', async (event, relativePath) => {
|
|
|
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
|
|
|
if (restartPaths.some(p => p === path || path.startsWith(p + '/'))) {
|
2023-05-15 12:47:30 +00:00
|
|
|
const newSetting = await isPagesEnabled()
|
2023-03-09 11:46:08 +00:00
|
|
|
if (nuxt.options.pages !== newSetting) {
|
2023-09-19 21:26:15 +00:00
|
|
|
logger.info('Pages', newSetting ? 'enabled' : 'disabled')
|
2023-03-09 11:46:08 +00:00
|
|
|
return nuxt.callHook('restart')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-05-22 13:48:08 +00:00
|
|
|
// adds support for #vue-router alias (used for types) with and without pages integration
|
|
|
|
addTemplate({
|
2023-09-28 07:37:14 +00:00
|
|
|
filename: 'vue-router-stub.d.ts',
|
2023-05-22 13:48:08 +00:00
|
|
|
getContents: () => `export * from '${useExperimentalTypedPages ? 'vue-router/auto' : 'vue-router'}'`
|
|
|
|
})
|
|
|
|
|
2023-09-28 07:37:14 +00:00
|
|
|
nuxt.options.alias['#vue-router'] = join(nuxt.options.buildDir, 'vue-router-stub')
|
2023-05-22 13:48:08 +00:00
|
|
|
|
2022-10-27 15:50:24 +00:00
|
|
|
if (!nuxt.options.pages) {
|
2022-02-21 13:03:42 +00:00
|
|
|
addPlugin(resolve(distDir, 'app/plugins/router'))
|
2022-10-24 08:36:49 +00:00
|
|
|
addTemplate({
|
|
|
|
filename: 'pages.mjs',
|
2024-01-02 10:00:47 +00:00
|
|
|
getContents: () => [
|
|
|
|
'export { useRoute } from \'#app/composables/router\'',
|
|
|
|
'export const START_LOCATION = Symbol(\'router:start-location\')'
|
|
|
|
].join('\n')
|
2022-10-24 08:36:49 +00:00
|
|
|
})
|
2022-10-27 15:50:24 +00:00
|
|
|
addComponent({
|
|
|
|
name: 'NuxtPage',
|
2023-03-06 11:33:40 +00:00
|
|
|
priority: 10, // built-in that we do not expect the user to override
|
2022-10-27 15:50:24 +00:00
|
|
|
filePath: resolve(distDir, 'pages/runtime/page-placeholder')
|
|
|
|
})
|
2021-07-28 11:35:24 +00:00
|
|
|
return
|
|
|
|
}
|
2021-05-20 11:42:41 +00:00
|
|
|
|
2023-05-22 13:48:08 +00:00
|
|
|
addTemplate({
|
2023-09-28 07:37:14 +00:00
|
|
|
filename: 'vue-router-stub.mjs',
|
2023-05-22 13:48:08 +00:00
|
|
|
// TODO: use `vue-router/auto` when we have support for page metadata
|
|
|
|
getContents: () => 'export * from \'vue-router\';'
|
|
|
|
})
|
|
|
|
|
2023-05-09 17:08:07 +00:00
|
|
|
if (useExperimentalTypedPages) {
|
|
|
|
const declarationFile = './types/typed-router.d.ts'
|
|
|
|
|
|
|
|
const options: TypedRouterOptions = {
|
|
|
|
routesFolder: [],
|
|
|
|
dts: resolve(nuxt.options.buildDir, declarationFile),
|
|
|
|
logs: nuxt.options.debug,
|
|
|
|
async beforeWriteFiles (rootPage) {
|
|
|
|
rootPage.children.forEach(child => child.delete())
|
2023-10-30 16:55:40 +00:00
|
|
|
let pages = nuxt.apps.default?.pages
|
|
|
|
if (!pages) {
|
|
|
|
pages = await resolvePagesRoutes()
|
|
|
|
await nuxt.callHook('pages:extend', pages)
|
|
|
|
}
|
2023-05-09 17:08:07 +00:00
|
|
|
function addPage (parent: EditableTreeNode, page: NuxtPage) {
|
|
|
|
// @ts-expect-error TODO: either fix types upstream or figure out another
|
|
|
|
// way to add a route without a file, which must be possible
|
|
|
|
const route = parent.insert(page.path, page.file)
|
|
|
|
if (page.meta) {
|
|
|
|
route.addToMeta(page.meta)
|
|
|
|
}
|
|
|
|
if (page.alias) {
|
|
|
|
route.addAlias(page.alias)
|
|
|
|
}
|
|
|
|
if (page.name) {
|
|
|
|
route.name = page.name
|
|
|
|
}
|
|
|
|
// TODO: implement redirect support
|
|
|
|
// if (page.redirect) {}
|
|
|
|
if (page.children) {
|
|
|
|
page.children.forEach(child => addPage(route, child))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const page of pages) {
|
|
|
|
addPage(rootPage, page)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nuxt.hook('prepare:types', ({ references }) => {
|
|
|
|
// This file will be generated by unplugin-vue-router
|
|
|
|
references.push({ path: declarationFile })
|
|
|
|
})
|
|
|
|
|
|
|
|
const context = createRoutesContext(resolveOptions(options))
|
|
|
|
const dtsFile = resolve(nuxt.options.buildDir, declarationFile)
|
|
|
|
await mkdir(dirname(dtsFile), { recursive: true })
|
|
|
|
await context.scanPages(false)
|
|
|
|
|
|
|
|
if (nuxt.options._prepare) {
|
|
|
|
// TODO: could we generate this from context instead?
|
|
|
|
const dts = await readFile(dtsFile, 'utf-8')
|
|
|
|
addTemplate({
|
|
|
|
filename: 'types/typed-router.d.ts',
|
|
|
|
getContents: () => dts
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regenerate types/typed-router.d.ts when adding or removing pages
|
|
|
|
nuxt.hook('builder:generateApp', async (options) => {
|
|
|
|
if (!options?.filter || options.filter({ filename: 'routes.mjs' } as any)) {
|
|
|
|
await context.scanPages()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-11-02 09:39:42 +00:00
|
|
|
// Add $router types
|
|
|
|
nuxt.hook('prepare:types', ({ references }) => {
|
2023-05-09 17:08:07 +00:00
|
|
|
references.push({ types: useExperimentalTypedPages ? 'vue-router/auto' : 'vue-router' })
|
2021-11-02 09:39:42 +00:00
|
|
|
})
|
|
|
|
|
2022-11-10 13:52:04 +00:00
|
|
|
// Add vue-router route guard imports
|
|
|
|
nuxt.hook('imports:sources', (sources) => {
|
2023-10-30 21:05:02 +00:00
|
|
|
const routerImports = sources.find(s => s.from === '#app/composables/router' && s.imports.includes('onBeforeRouteLeave'))
|
2022-11-10 13:52:04 +00:00
|
|
|
if (routerImports) {
|
2023-05-09 17:08:07 +00:00
|
|
|
routerImports.from = '#vue-router'
|
2022-11-10 13:52:04 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-07-28 11:35:24 +00:00
|
|
|
// Regenerate templates when adding or removing pages
|
2023-09-12 14:27:28 +00:00
|
|
|
const updateTemplatePaths = nuxt.options._layers.flatMap((l) => {
|
|
|
|
const dir = (l.config.rootDir === nuxt.options.rootDir ? nuxt.options : l.config).dir
|
|
|
|
return [
|
|
|
|
join(l.config.srcDir || l.cwd, dir?.pages || 'pages') + '/',
|
|
|
|
join(l.config.srcDir || l.cwd, dir?.layouts || 'layouts') + '/',
|
|
|
|
join(l.config.srcDir || l.cwd, dir?.middleware || 'middleware') + '/'
|
|
|
|
]
|
|
|
|
})
|
2023-07-26 09:16:01 +00:00
|
|
|
|
2024-01-29 16:44:54 +00:00
|
|
|
function isPage (file: string, pages = nuxt.apps.default.pages): boolean {
|
|
|
|
if (!pages) { return false }
|
|
|
|
return pages.some(page => page.file === file) || pages.some(page => page.children && isPage(file, page.children))
|
|
|
|
}
|
2023-07-26 09:16:01 +00:00
|
|
|
nuxt.hook('builder:watch', async (event, relativePath) => {
|
|
|
|
const path = resolve(nuxt.options.srcDir, relativePath)
|
2024-01-29 16:44:54 +00:00
|
|
|
const shouldAlwaysRegenerate = nuxt.options.experimental.scanPageMeta && isPage(path)
|
|
|
|
|
|
|
|
if (event === 'change' && !shouldAlwaysRegenerate) { return }
|
|
|
|
|
|
|
|
if (shouldAlwaysRegenerate || updateTemplatePaths.some(dir => path.startsWith(dir))) {
|
2022-11-02 10:28:41 +00:00
|
|
|
await updateTemplates({
|
|
|
|
filter: template => template.filename === 'routes.mjs'
|
|
|
|
})
|
2021-05-20 11:42:41 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
nuxt.hook('app:resolve', (app) => {
|
2021-10-12 12:51:41 +00:00
|
|
|
// Add default layout for pages
|
2022-08-12 17:47:58 +00:00
|
|
|
if (app.mainComponent!.includes('@nuxt/ui-templates')) {
|
2021-10-12 12:51:41 +00:00
|
|
|
app.mainComponent = resolve(runtimeDir, 'app.vue')
|
2021-05-20 11:42:41 +00:00
|
|
|
}
|
2022-10-10 10:18:20 +00:00
|
|
|
app.middleware.unshift({
|
|
|
|
name: 'validate',
|
|
|
|
path: resolve(runtimeDir, 'validate'),
|
|
|
|
global: true
|
|
|
|
})
|
2021-05-20 11:42:41 +00:00
|
|
|
})
|
|
|
|
|
2023-06-29 09:14:35 +00:00
|
|
|
nuxt.hook('nitro:init', (nitro) => {
|
2023-11-29 00:35:46 +00:00
|
|
|
if (nuxt.options.dev || !nitro.options.static || nuxt.options.router.options.hashMode) { return }
|
2023-06-29 09:14:35 +00:00
|
|
|
// Prerender all non-dynamic page routes when generating app
|
2022-07-12 10:56:40 +00:00
|
|
|
const prerenderRoutes = new Set<string>()
|
2023-07-12 07:26:52 +00:00
|
|
|
nuxt.hook('pages:extend', (pages) => {
|
|
|
|
prerenderRoutes.clear()
|
|
|
|
const processPages = (pages: NuxtPage[], currentPath = '/') => {
|
|
|
|
for (const page of pages) {
|
|
|
|
// Add root of optional dynamic paths and catchalls
|
|
|
|
if (OPTIONAL_PARAM_RE.test(page.path) && !page.children?.length) { prerenderRoutes.add(currentPath) }
|
|
|
|
// Skip dynamic paths
|
|
|
|
if (page.path.includes(':')) { continue }
|
|
|
|
const route = joinURL(currentPath, page.path)
|
|
|
|
prerenderRoutes.add(route)
|
|
|
|
if (page.children) { processPages(page.children, route) }
|
2022-07-07 15:07:37 +00:00
|
|
|
}
|
2023-07-12 07:26:52 +00:00
|
|
|
}
|
|
|
|
processPages(pages)
|
2022-07-07 15:07:37 +00:00
|
|
|
})
|
|
|
|
nuxt.hook('nitro:build:before', (nitro) => {
|
2022-07-12 10:56:40 +00:00
|
|
|
for (const route of nitro.options.prerender.routes || []) {
|
2022-11-03 14:52:03 +00:00
|
|
|
// Skip default route value as we only generate it if it is already
|
|
|
|
// in the detected routes from `~/pages`.
|
|
|
|
if (route === '/') { continue }
|
2022-07-12 10:56:40 +00:00
|
|
|
prerenderRoutes.add(route)
|
|
|
|
}
|
|
|
|
nitro.options.prerender.routes = Array.from(prerenderRoutes)
|
2022-07-07 15:07:37 +00:00
|
|
|
})
|
2023-06-29 09:14:35 +00:00
|
|
|
})
|
2022-07-07 15:07:37 +00:00
|
|
|
|
2022-08-23 14:22:11 +00:00
|
|
|
nuxt.hook('imports:extend', (imports) => {
|
|
|
|
imports.push(
|
2022-07-07 17:28:23 +00:00
|
|
|
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') },
|
2023-05-09 17:08:07 +00:00
|
|
|
{ name: 'useLink', as: 'useLink', from: '#vue-router' }
|
2022-07-07 17:28:23 +00:00
|
|
|
)
|
2023-08-23 20:38:17 +00:00
|
|
|
if (nuxt.options.experimental.inlineRouteRules) {
|
|
|
|
imports.push({ name: 'defineRouteRules', as: 'defineRouteRules', from: resolve(runtimeDir, 'composables') })
|
|
|
|
}
|
2021-12-17 09:15:03 +00:00
|
|
|
})
|
|
|
|
|
2023-08-23 20:38:17 +00:00
|
|
|
if (nuxt.options.experimental.inlineRouteRules) {
|
|
|
|
// Track mappings of absolute files to globs
|
|
|
|
let pageToGlobMap = {} as { [absolutePath: string]: string | null }
|
|
|
|
nuxt.hook('pages:extend', (pages) => { pageToGlobMap = getMappedPages(pages) })
|
|
|
|
|
|
|
|
// Extracted route rules defined inline in pages
|
|
|
|
const inlineRules = {} as { [glob: string]: NitroRouteConfig }
|
|
|
|
|
|
|
|
// Allow telling Nitro to reload route rules
|
|
|
|
let updateRouteConfig: () => void | Promise<void>
|
|
|
|
nuxt.hook('nitro:init', (nitro) => {
|
|
|
|
updateRouteConfig = () => nitro.updateConfig({ routeRules: defu(inlineRules, nitro.options._config.routeRules) })
|
|
|
|
})
|
|
|
|
|
2023-11-09 17:01:13 +00:00
|
|
|
const updatePage = async function updatePage (path: string) {
|
2023-08-23 20:38:17 +00:00
|
|
|
const glob = pageToGlobMap[path]
|
|
|
|
const code = path in nuxt.vfs ? nuxt.vfs[path] : await readFile(path!, 'utf-8')
|
|
|
|
try {
|
|
|
|
const extractedRule = await extractRouteRules(code)
|
|
|
|
if (extractedRule) {
|
|
|
|
if (!glob) {
|
|
|
|
const relativePath = relative(nuxt.options.srcDir, path)
|
2023-09-19 21:26:15 +00:00
|
|
|
logger.error(`Could not set inline route rules in \`~/${relativePath}\` as it could not be mapped to a Nitro route.`)
|
2023-08-23 20:38:17 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
inlineRules[glob] = extractedRule
|
|
|
|
} else if (glob) {
|
|
|
|
delete inlineRules[glob]
|
|
|
|
}
|
|
|
|
} catch (e: any) {
|
|
|
|
if (e.toString().includes('Error parsing route rules')) {
|
|
|
|
const relativePath = relative(nuxt.options.srcDir, path)
|
2023-09-19 21:26:15 +00:00
|
|
|
logger.error(`Error parsing route rules within \`~/${relativePath}\`. They should be JSON-serializable.`)
|
2023-08-23 20:38:17 +00:00
|
|
|
} else {
|
2023-09-19 21:26:15 +00:00
|
|
|
logger.error(e)
|
2023-08-23 20:38:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nuxt.hook('builder:watch', async (event, relativePath) => {
|
|
|
|
const path = join(nuxt.options.srcDir, relativePath)
|
|
|
|
if (!(path in pageToGlobMap)) { return }
|
|
|
|
if (event === 'unlink') {
|
|
|
|
delete inlineRules[path]
|
|
|
|
delete pageToGlobMap[path]
|
|
|
|
} else {
|
|
|
|
await updatePage(path)
|
|
|
|
}
|
|
|
|
await updateRouteConfig?.()
|
|
|
|
})
|
|
|
|
|
|
|
|
nuxt.hooks.hookOnce('pages:extend', async () => {
|
|
|
|
for (const page in pageToGlobMap) { await updatePage(page) }
|
|
|
|
await updateRouteConfig?.()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-29 11:07:52 +00:00
|
|
|
if (nuxt.options.experimental.appManifest) {
|
|
|
|
// Add all redirect paths as valid routes to router; we will handle these in a client-side middleware
|
|
|
|
// when the app manifest is enabled.
|
|
|
|
nuxt.hook('pages:extend', routes => {
|
|
|
|
const nitro = useNitro()
|
|
|
|
for (const path in nitro.options.routeRules) {
|
|
|
|
const rule = nitro.options.routeRules[path]
|
|
|
|
if (!rule.redirect) { continue }
|
|
|
|
routes.push({
|
|
|
|
path: path.replace(/\/[^/]*\*\*/, '/:pathMatch(.*)'),
|
|
|
|
file: resolve(runtimeDir, 'component-stub'),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-17 18:27:23 +00:00
|
|
|
// Extract macros from pages
|
2022-11-02 10:28:41 +00:00
|
|
|
const pageMetaOptions: PageMetaPluginOptions = {
|
2022-01-19 18:07:54 +00:00
|
|
|
dev: nuxt.options.dev,
|
2023-08-24 12:06:44 +00:00
|
|
|
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client
|
2022-01-17 18:27:23 +00:00
|
|
|
}
|
2023-04-10 21:57:13 +00:00
|
|
|
nuxt.hook('modules:done', () => {
|
2023-05-02 11:17:41 +00:00
|
|
|
addVitePlugin(() => PageMetaPlugin.vite(pageMetaOptions))
|
|
|
|
addWebpackPlugin(() => PageMetaPlugin.webpack(pageMetaOptions))
|
2023-04-10 21:57:13 +00:00
|
|
|
})
|
2022-01-17 18:27:23 +00:00
|
|
|
|
2023-01-19 13:01:21 +00:00
|
|
|
// Add prefetching support for middleware & layouts
|
|
|
|
addPlugin(resolve(runtimeDir, 'plugins/prefetch.client'))
|
|
|
|
|
2023-08-07 13:19:48 +00:00
|
|
|
// Add build plugin to ensure template $route is kept in sync with `<NuxtPage>`
|
|
|
|
if (nuxt.options.experimental.templateRouteInjection) {
|
|
|
|
addBuildPlugin(RouteInjectionPlugin(nuxt), { server: false })
|
|
|
|
}
|
|
|
|
|
2021-11-17 11:28:36 +00:00
|
|
|
// Add router plugin
|
2023-01-19 13:01:21 +00:00
|
|
|
addPlugin(resolve(runtimeDir, 'plugins/router'))
|
2021-06-30 16:32:22 +00:00
|
|
|
|
2023-03-03 14:07:42 +00:00
|
|
|
const getSources = (pages: NuxtPage[]): string[] => pages
|
|
|
|
.filter(p => Boolean(p.file))
|
|
|
|
.flatMap(p =>
|
|
|
|
[relative(nuxt.options.srcDir, p.file as string), ...getSources(p.children || [])]
|
|
|
|
)
|
2022-08-16 11:19:39 +00:00
|
|
|
|
|
|
|
// Do not prefetch page chunks
|
2023-10-30 16:55:40 +00:00
|
|
|
nuxt.hook('build:manifest', (manifest) => {
|
2023-02-27 19:36:27 +00:00
|
|
|
if (nuxt.options.dev) { return }
|
2023-10-30 16:55:40 +00:00
|
|
|
const sourceFiles = getSources(nuxt.apps.default.pages || [])
|
2023-05-09 17:08:07 +00:00
|
|
|
|
2022-08-16 11:19:39 +00:00
|
|
|
for (const key in manifest) {
|
|
|
|
if (manifest[key].isEntry) {
|
|
|
|
manifest[key].dynamicImports =
|
|
|
|
manifest[key].dynamicImports?.filter(i => !sourceFiles.includes(i))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-07-28 11:35:24 +00:00
|
|
|
// Add routes template
|
|
|
|
addTemplate({
|
|
|
|
filename: 'routes.mjs',
|
2023-10-30 16:55:40 +00:00
|
|
|
getContents ({ app }) {
|
2024-01-12 10:47:23 +00:00
|
|
|
if (!app.pages) return 'export default []'
|
2024-01-29 16:44:54 +00:00
|
|
|
const { routes, imports } = normalizeRoutes(app.pages, new Set(), nuxt.options.experimental.scanPageMeta)
|
2022-02-07 13:45:47 +00:00
|
|
|
return [...imports, `export default ${routes}`].join('\n')
|
2021-07-28 11:35:24 +00:00
|
|
|
}
|
|
|
|
})
|
2021-06-30 16:32:22 +00:00
|
|
|
|
2022-10-24 08:36:49 +00:00
|
|
|
// Add vue-router import for `<NuxtLayout>` integration
|
|
|
|
addTemplate({
|
|
|
|
filename: 'pages.mjs',
|
2024-01-02 10:00:47 +00:00
|
|
|
getContents: () => 'export { START_LOCATION, useRoute } from \'vue-router\''
|
2022-10-24 08:36:49 +00:00
|
|
|
})
|
|
|
|
|
2022-11-02 08:53:23 +00:00
|
|
|
// Optimize vue-router to ensure we share the same injection symbol
|
|
|
|
nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {}
|
|
|
|
nuxt.options.vite.optimizeDeps.include = nuxt.options.vite.optimizeDeps.include || []
|
|
|
|
nuxt.options.vite.optimizeDeps.include.push('vue-router')
|
|
|
|
|
2023-01-30 18:22:15 +00:00
|
|
|
nuxt.options.vite.resolve = nuxt.options.vite.resolve || {}
|
|
|
|
nuxt.options.vite.resolve.dedupe = nuxt.options.vite.resolve.dedupe || []
|
|
|
|
nuxt.options.vite.resolve.dedupe.push('vue-router')
|
|
|
|
|
2022-03-15 16:57:41 +00:00
|
|
|
// Add router options template
|
|
|
|
addTemplate({
|
|
|
|
filename: 'router.options.mjs',
|
|
|
|
getContents: async () => {
|
2022-10-19 12:43:03 +00:00
|
|
|
// Scan and register app/router.options files
|
2024-01-18 16:06:00 +00:00
|
|
|
const routerOptionsFiles = await resolveRouterOptions()
|
2022-10-19 12:43:03 +00:00
|
|
|
|
2022-03-15 16:57:41 +00:00
|
|
|
const configRouterOptions = genObjectFromRawEntries(Object.entries(nuxt.options.router.options)
|
|
|
|
.map(([key, value]) => [key, genString(value as string)]))
|
2022-04-04 08:23:11 +00:00
|
|
|
|
2022-03-15 16:57:41 +00:00
|
|
|
return [
|
2024-01-18 16:06:00 +00:00
|
|
|
...routerOptionsFiles.map((file, index) => genImport(file.path, `routerOptions${index}`)),
|
2022-03-15 16:57:41 +00:00
|
|
|
`const configRouterOptions = ${configRouterOptions}`,
|
|
|
|
'export default {',
|
|
|
|
'...configRouterOptions,',
|
2022-04-04 08:23:11 +00:00
|
|
|
// We need to reverse spreading order to respect layers priority
|
|
|
|
...routerOptionsFiles.map((_, index) => `...routerOptions${index},`).reverse(),
|
2022-03-15 16:57:41 +00:00
|
|
|
'}'
|
|
|
|
].join('\n')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-01-25 12:29:11 +00:00
|
|
|
addTemplate({
|
2022-02-07 10:20:01 +00:00
|
|
|
filename: 'types/middleware.d.ts',
|
2023-08-14 17:07:17 +00:00
|
|
|
getContents: ({ nuxt, app }: { nuxt: Nuxt, app: NuxtApp }) => {
|
|
|
|
const composablesFile = relative(join(nuxt.options.buildDir, 'types'), resolve(runtimeDir, 'composables'))
|
2022-06-27 12:10:29 +00:00
|
|
|
const namedMiddleware = app.middleware.filter(mw => !mw.global)
|
2022-01-25 12:29:11 +00:00
|
|
|
return [
|
|
|
|
'import type { NavigationGuard } from \'vue-router\'',
|
2022-02-07 13:45:47 +00:00
|
|
|
`export type MiddlewareKey = ${namedMiddleware.map(mw => genString(mw.name)).join(' | ') || 'string'}`,
|
|
|
|
`declare module ${genString(composablesFile)} {`,
|
2022-01-25 12:29:11 +00:00
|
|
|
' interface PageMeta {',
|
|
|
|
' middleware?: MiddlewareKey | NavigationGuard | Array<MiddlewareKey | NavigationGuard>',
|
|
|
|
' }',
|
|
|
|
'}'
|
|
|
|
].join('\n')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-01-26 11:56:24 +00:00
|
|
|
addTemplate({
|
2022-02-07 10:20:01 +00:00
|
|
|
filename: 'types/layouts.d.ts',
|
2023-08-14 17:07:17 +00:00
|
|
|
getContents: ({ nuxt, app }: { nuxt: Nuxt, app: NuxtApp }) => {
|
|
|
|
const composablesFile = relative(join(nuxt.options.buildDir, 'types'), resolve(runtimeDir, 'composables'))
|
2022-01-26 11:56:24 +00:00
|
|
|
return [
|
2023-07-31 08:50:55 +00:00
|
|
|
'import { ComputedRef, MaybeRef } from \'vue\'',
|
2022-03-14 10:47:24 +00:00
|
|
|
`export type LayoutKey = ${Object.keys(app.layouts).map(name => genString(name)).join(' | ') || 'string'}`,
|
2022-02-07 13:45:47 +00:00
|
|
|
`declare module ${genString(composablesFile)} {`,
|
2022-01-26 11:56:24 +00:00
|
|
|
' interface PageMeta {',
|
2023-07-31 08:50:55 +00:00
|
|
|
' layout?: MaybeRef<LayoutKey | false> | ComputedRef<LayoutKey | false>',
|
2022-01-26 11:56:24 +00:00
|
|
|
' }',
|
|
|
|
'}'
|
|
|
|
].join('\n')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-01-29 12:23:51 +00:00
|
|
|
|
|
|
|
// add page meta types if enabled
|
|
|
|
if (nuxt.options.experimental.viewTransition) {
|
|
|
|
addTypeTemplate({
|
|
|
|
filename: 'types/view-transitions.d.ts',
|
|
|
|
getContents: ({ nuxt }) => {
|
|
|
|
const runtimeDir = resolve(distDir, 'pages/runtime')
|
|
|
|
const composablesFile = relative(join(nuxt.options.buildDir, 'types'), resolve(runtimeDir, 'composables'))
|
|
|
|
return [
|
|
|
|
'import { ComputedRef, MaybeRef } from \'vue\'',
|
|
|
|
`declare module ${genString(composablesFile)} {`,
|
|
|
|
' interface PageMeta {',
|
|
|
|
` viewTransition?: boolean | 'always'`,
|
|
|
|
' }',
|
|
|
|
'}',
|
|
|
|
].join('\n')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-14 08:36:52 +00:00
|
|
|
// Add <NuxtPage>
|
|
|
|
addComponent({
|
|
|
|
name: 'NuxtPage',
|
2023-03-06 11:33:40 +00:00
|
|
|
priority: 10, // built-in that we do not expect the user to override
|
2022-10-14 08:36:52 +00:00
|
|
|
filePath: resolve(distDir, 'pages/runtime/page')
|
|
|
|
})
|
|
|
|
|
2022-03-14 10:47:24 +00:00
|
|
|
// Add declarations for middleware keys
|
2022-01-27 11:13:32 +00:00
|
|
|
nuxt.hook('prepare:types', ({ references }) => {
|
2022-02-07 10:20:01 +00:00
|
|
|
references.push({ path: resolve(nuxt.options.buildDir, 'types/middleware.d.ts') })
|
|
|
|
references.push({ path: resolve(nuxt.options.buildDir, 'types/layouts.d.ts') })
|
2023-09-28 07:37:14 +00:00
|
|
|
references.push({ path: resolve(nuxt.options.buildDir, 'vue-router-stub.d.ts') })
|
2022-01-27 11:13:32 +00:00
|
|
|
})
|
2021-05-20 11:42:41 +00:00
|
|
|
}
|
|
|
|
})
|