[READ-ONLY] Mirror of https://github.com/danielroe/roe.dev. This is the code and content for my personal website, built in Nuxt.
roe.dev
1import { statSync } from 'node:fs'
2import fsp from 'node:fs/promises'
3
4import { join, relative } from 'pathe'
5import {
6 genSafeVariableName,
7 genArrayFromRaw,
8 genDynamicImport,
9 genString,
10 genImport,
11} from 'knitwork'
12import {
13 addComponent,
14 addImports,
15 addTemplate,
16 addRouteMiddleware,
17 createResolver,
18 defineNuxtModule,
19 useNuxt,
20} from 'nuxt/kit'
21import { withLeadingSlash } from 'ufo'
22
23import { pageMeta } from '../shared/page-meta'
24
25export default defineNuxtModule({
26 meta: {
27 name: 'custom-router',
28 },
29 setup () {
30 const nuxt = useNuxt()
31 const resolver = createResolver(import.meta.url)
32 nuxt.options.pages = false
33
34 addImports({
35 name: 'definePageMeta',
36 from: resolver.resolve('./runtime/route-composables'),
37 priority: 20,
38 })
39
40 addImports({
41 name: 'useRoute',
42 from: resolver.resolve('./runtime/route-composables'),
43 priority: 20,
44 })
45
46 addImports({
47 name: 'handleNavigationClicks',
48 from: resolver.resolve('./runtime/route-composables'),
49 })
50
51 addComponent({
52 filePath: resolver.resolve('./runtime/nuxt-link'),
53 name: 'NuxtLink',
54 priority: 20,
55 })
56
57 addComponent({
58 filePath: resolver.resolve('./runtime/NuxtPage.vue'),
59 name: 'NuxtPage',
60 priority: 20,
61 })
62
63 addRouteMiddleware({
64 name: 'path-params',
65 global: true,
66 path: resolver.resolve('./runtime/path.global.ts'),
67 })
68
69 const pagesDir = resolver.resolve('../../app/pages')
70 const isAdminFile = (f: string) =>
71 relative(pagesDir, f).split('/')[0] === 'admin'
72
73 addTemplate({
74 filename: 'routes.mjs',
75 write: true,
76 async getContents () {
77 const files = await readRecursive(pagesDir)
78 const staticImports = files.filter(f => !isAdminFile(f))
79 const componentNames = Object.fromEntries(
80 staticImports.map(f => [f, genSafeVariableName(f)]),
81 )
82 const routes = files.map(f => {
83 const path = withLeadingSlash(
84 relative(pagesDir, f).replace(
85 /(\/?index)?\.vue$/,
86 '',
87 ),
88 )
89 const isDynamic = path.includes('[')
90 return {
91 path: isDynamic
92 ? `/${path
93 .replace(/\[(.*)\]/g, '(?<$1>.+)')
94 .replace(/\//g, '\\/')}/`
95 : genString(path),
96 component: isAdminFile(f)
97 ? `defineAsyncComponent(${genDynamicImport(f, { interopDefault: true })})`
98 : componentNames[f],
99 meta: JSON.stringify(pageMeta[path] || {}),
100 _isDynamic: isDynamic,
101 }
102 })
103 .sort((a, b) => Number(a._isDynamic) - Number(b._isDynamic))
104 .map(({ _isDynamic, ...rest }) => rest)
105
106 return `
107 import { defineAsyncComponent } from 'vue'
108 ${Object.entries(componentNames).map(([path, name]) => genImport(path, { name })).join('\n')}
109 export default ${genArrayFromRaw(routes)}`
110 },
111 })
112 },
113})
114
115async function readRecursive (dir: string) {
116 const files = await fsp.readdir(dir)
117 const result: string[] = []
118 for (const file of files) {
119 const path = join(dir, file)
120 if (statSync(path).isDirectory()) {
121 result.push(...(await readRecursive(path)))
122 }
123 else {
124 result.push(path)
125 }
126 }
127 return result
128}