New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(nuxt): add experimental typedPages
option
#20367
Changes from all commits
f1afae9
316a288
6289223
2f54325
6ef6fd3
bd79b91
2622b06
4d8c1e1
6aa00e0
792e1ab
d2a0ff2
e2cf3c1
3e03638
ada04ed
20aae8d
325a818
f38cace
c32738f
a0e5574
1591bb1
51db0dc
2af7b9a
5484cd8
e22dd02
ab80137
2204242
2e34a3b
dc34ed9
2c5b6c4
ebbe880
21f5376
9708b7c
6df21bf
5c164a5
4c19b4b
d19ddf2
edfdad4
35fbd6b
25830a2
8c64788
88dfc3b
2dbee98
8d6fdf9
a837c24
a78b797
d9ac7ca
9350e93
10ea7e7
d31b7b7
0a97086
c4111c2
1c2f263
ed4e2a6
6e79f81
b851b4f
efbf166
f2689b9
02e7cb8
dd13ca4
9976c62
7258433
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
import { existsSync, readdirSync } from 'node:fs' | ||
import { mkdir, readFile } from 'node:fs/promises' | ||
import { addComponent, addPlugin, addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, updateTemplates } from '@nuxt/kit' | ||
import { join, relative, resolve } from 'pathe' | ||
import { dirname, join, relative, resolve } from 'pathe' | ||
import { genImport, genObjectFromRawEntries, genString } from 'knitwork' | ||
import escapeRE from 'escape-string-regexp' | ||
import { joinURL } from 'ufo' | ||
import type { NuxtApp, NuxtPage } from 'nuxt/schema' | ||
import { createRoutesContext } from 'unplugin-vue-router' | ||
import { resolveOptions } from 'unplugin-vue-router/options' | ||
import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router' | ||
|
||
import { distDir } from '../dirs' | ||
import { normalizeRoutes, resolvePagesRoutes } from './utils' | ||
|
@@ -15,7 +19,9 @@ export default defineNuxtModule({ | |
meta: { | ||
name: 'pages' | ||
}, | ||
setup (_options, nuxt) { | ||
async setup (_options, nuxt) { | ||
const useExperimentalTypedPages = nuxt.options.experimental.typedPages | ||
|
||
const pagesDirs = nuxt.options._layers.map( | ||
layer => resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages') | ||
) | ||
|
@@ -67,18 +73,82 @@ export default defineNuxtModule({ | |
return | ||
} | ||
|
||
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()) | ||
const pages = await resolvePagesRoutes() | ||
await nuxt.callHook('pages:extend', pages) | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I will need to add this before it gets merged There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it make a difference to the types? Because at the moment we are not using the unplugin for actual routes generation (until it can support route metadata). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are right, it doesn't make any difference in terms of types |
||
// 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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to take a look, I think the options should be resolved internally anyway π€ |
||
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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could add a method to expose the generation of the dts content. I imagine that having an option not to write to the filesystem could also be helpful in this case. Let me know what you think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be perfect π |
||
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() | ||
} | ||
}) | ||
} | ||
|
||
const runtimeDir = resolve(distDir, 'pages/runtime') | ||
|
||
// Add $router types | ||
nuxt.hook('prepare:types', ({ references }) => { | ||
references.push({ types: 'vue-router' }) | ||
references.push({ types: useExperimentalTypedPages ? 'vue-router/auto' : 'vue-router' }) | ||
}) | ||
|
||
// Add vue-router route guard imports | ||
nuxt.hook('imports:sources', (sources) => { | ||
const routerImports = sources.find(s => s.from === '#app' && s.imports.includes('onBeforeRouteLeave')) | ||
if (routerImports) { | ||
routerImports.from = 'vue-router' | ||
routerImports.from = '#vue-router' | ||
} | ||
}) | ||
|
||
|
@@ -144,7 +214,7 @@ export default defineNuxtModule({ | |
nuxt.hook('imports:extend', (imports) => { | ||
imports.push( | ||
{ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') }, | ||
{ name: 'useLink', as: 'useLink', from: 'vue-router' } | ||
{ name: 'useLink', as: 'useLink', from: '#vue-router' } | ||
) | ||
}) | ||
|
||
|
@@ -177,6 +247,7 @@ export default defineNuxtModule({ | |
await nuxt.callHook('pages:extend', pages) | ||
|
||
const sourceFiles = getSources(pages) | ||
|
||
for (const key in manifest) { | ||
if (manifest[key].isEntry) { | ||
manifest[key].dynamicImports = | ||
|
@@ -185,6 +256,17 @@ export default defineNuxtModule({ | |
} | ||
}) | ||
|
||
// adds support for #vue-router alias | ||
addTemplate({ | ||
filename: 'vue-router.mjs', | ||
// TODO: use `vue-router/auto` when we have support for page metadata | ||
getContents: () => 'export * from \'vue-router\';' | ||
}) | ||
addTemplate({ | ||
filename: 'vue-router.d.ts', | ||
getContents: () => `export * from '${useExperimentalTypedPages ? 'vue-router/auto' : 'vue-router'}'` | ||
}) | ||
|
||
// Add routes template | ||
addTemplate({ | ||
filename: 'routes.mjs', | ||
|
@@ -278,10 +360,13 @@ export default defineNuxtModule({ | |
filePath: resolve(distDir, 'pages/runtime/page') | ||
}) | ||
|
||
nuxt.options.alias['#vue-router'] = join(nuxt.options.buildDir, 'vue-router') | ||
|
||
// Add declarations for middleware keys | ||
nuxt.hook('prepare:types', ({ references }) => { | ||
references.push({ path: resolve(nuxt.options.buildDir, 'types/middleware.d.ts') }) | ||
references.push({ path: resolve(nuxt.options.buildDir, 'types/layouts.d.ts') }) | ||
references.push({ path: resolve(nuxt.options.buildDir, 'vue-router.d.ts') }) | ||
}) | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW are these intentional? It should be the exact same without them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's get rid of them then π