feat(nuxt3): extends support for pages & middleware directories (#3783)

Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Kevin Marrec 2022-03-22 19:12:54 +01:00 committed by GitHub
parent 29078bba74
commit 7c0d2e176c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 273 additions and 155 deletions

View File

@ -0,0 +1,3 @@
export default defineNuxtRouteMiddleware(() => {
console.log('Hello from extended middleware !')
})

View File

@ -0,0 +1,11 @@
<template>
<div>
Hello from extended page !
</div>
</template>
<script setup>
definePageMeta({
middleware: 'foo'
})
</script>

View File

@ -12,15 +12,18 @@ export default defineNuxtModule({
name: 'router'
},
setup (_options, nuxt) {
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
const runtimeDir = resolve(distDir, 'pages/runtime')
const pagesDirs = nuxt.options._layers.map(
layer => resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages')
)
// Disable module (and use universal router) if pages dir do not exists
if (!existsSync(pagesDir)) {
if (!pagesDirs.some(dir => existsSync(dir))) {
addPlugin(resolve(distDir, 'app/plugins/router'))
return
}
const runtimeDir = resolve(distDir, 'pages/runtime')
// Add $router types
nuxt.hook('prepare:types', ({ references }) => {
references.push({ types: 'vue-router' })
@ -68,7 +71,7 @@ export default defineNuxtModule({
addTemplate({
filename: 'routes.mjs',
async getContents () {
const pages = await resolvePagesRoutes(nuxt)
const pages = await resolvePagesRoutes()
await nuxt.callHook('pages:extend', pages)
const { routes, imports } = normalizeRoutes(pages)
return [...imports, `export default ${routes}`].join('\n')
@ -99,6 +102,7 @@ export default defineNuxtModule({
filename: 'middleware.mjs',
async getContents () {
const middleware = await resolveMiddleware()
await nuxt.callHook('pages:middleware:extend', middleware)
const globalMiddleware = middleware.filter(mw => mw.global)
const namedMiddleware = middleware.filter(mw => !mw.global)
const namedMiddlewareObject = genObjectFromRawEntries(namedMiddleware.map(mw => [mw.name, genDynamicImport(mw.path)]))

View File

@ -1,6 +1,6 @@
import { basename, extname, normalize, relative, resolve } from 'pathe'
import { encodePath } from 'ufo'
import type { Nuxt, NuxtMiddleware, NuxtPage } from '@nuxt/schema'
import { NuxtMiddleware, NuxtPage } from '@nuxt/schema'
import { resolveFiles, useNuxt } from '@nuxt/kit'
import { kebabCase, pascalCase } from 'scule'
import { genImport, genDynamicImport, genArrayFromRaw } from 'knitwork'
@ -24,14 +24,23 @@ interface SegmentToken {
value: string
}
export async function resolvePagesRoutes (nuxt: Nuxt) {
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
const files = await resolveFiles(pagesDir, `**/*{${nuxt.options.extensions.join(',')}}`)
export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
const nuxt = useNuxt()
const pagesDirs = nuxt.options._layers.map(
layer => resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages')
)
const allRoutes = (await Promise.all(
pagesDirs.map(async (dir) => {
const files = await resolveFiles(dir, `**/*{${nuxt.options.extensions.join(',')}}`)
// Sort to make sure parent are listed first
files.sort()
return generateRoutesFromFiles(files, dir)
})
)).flat()
return generateRoutesFromFiles(files, pagesDir)
return uniqueBy(allRoutes, 'name')
}
export function generateRoutesFromFiles (files: string[], pagesDir: string): NuxtPage[] {
@ -236,11 +245,19 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
export async function resolveMiddleware (): Promise<NuxtMiddleware[]> {
const nuxt = useNuxt()
const middlewareDir = resolve(nuxt.options.srcDir, nuxt.options.dir.middleware)
const files = await resolveFiles(middlewareDir, `*{${nuxt.options.extensions.join(',')}}`)
const middleware = files.map(path => ({ name: getNameFromPath(path), path, global: hasSuffix(path, '.global') }))
await nuxt.callHook('pages:middleware:extend', middleware)
return middleware
const middlewareDirs = nuxt.options._layers.map(
layer => resolve(layer.config.srcDir, layer.config.dir?.middleware || 'middleware')
)
const allMiddlewares = (await Promise.all(
middlewareDirs.map(async (dir) => {
const files = await resolveFiles(dir, `*{${nuxt.options.extensions.join(',')}}`)
return files.map(path => ({ name: getNameFromPath(path), path, global: hasSuffix(path, '.global') }))
})
)).flat()
return uniqueBy(allMiddlewares, 'name')
}
function getNameFromPath (path: string) {
@ -254,3 +271,16 @@ function hasSuffix (path: string, suffix: string) {
export function getImportName (name: string) {
return pascalCase(name).replace(/[^\w]/g, '')
}
function uniqueBy (arr: any[], key: string) {
const res = []
const keys = new Set<string>()
for (const item of arr) {
if (keys.has(item[key])) {
continue
}
keys.add(item[key])
res.push(item)
}
return res
}

View File

@ -2,13 +2,12 @@ import { fileURLToPath } from 'url'
import { describe, expect, it } from 'vitest'
import { setup, $fetch, startServer } from '@nuxt/test-utils'
describe('fixtures:basic', async () => {
await setup({
await setup({
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
server: true
})
})
describe('server api', () => {
describe('server api', () => {
it('should serialize', async () => {
expect(await $fetch('/api/hello')).toBe('Hello API')
expect(await $fetch('/api/hey')).toEqual({
@ -23,9 +22,9 @@ describe('fixtures:basic', async () => {
expect(await $fetch('/api/counter')).toEqual({ count: 2 })
expect(await $fetch('/api/counter')).toEqual({ count: 3 })
})
})
})
describe('pages', () => {
describe('pages', () => {
it('render index', async () => {
const html = await $fetch('/')
@ -92,9 +91,9 @@ describe('fixtures:basic', async () => {
expect(html).toContain('foo: foobar')
expect(html).toContain('group: admin')
})
})
})
describe('navigate', () => {
describe('navigate', () => {
it('should redirect to index with navigateTo', async () => {
const html = await $fetch('/navigate-to/')
@ -103,9 +102,9 @@ describe('fixtures:basic', async () => {
expect(html).toContain('Hello Nuxt 3!')
})
})
})
describe('middlewares', () => {
describe('middlewares', () => {
it('should redirect to index with global middleware', async () => {
const html = await $fetch('/redirect/')
@ -135,9 +134,9 @@ describe('fixtures:basic', async () => {
expect(html).toContain('auth: ')
expect(html).not.toContain('Injected by injectAuth middleware')
})
})
})
describe('layouts', () => {
describe('layouts', () => {
it('should apply custom layout', async () => {
const html = await $fetch('/with-layout')
@ -147,14 +146,39 @@ describe('fixtures:basic', async () => {
expect(html).toContain('with-layout.vue')
expect(html).toContain('Custom Layout:')
})
})
})
describe('reactivity transform', () => {
describe('reactivity transform', () => {
it('should works', async () => {
const html = await $fetch('/')
expect(html).toContain('Sugar Counter 12 x 2 = 24')
})
})
describe('extends support', () => {
describe('pages', () => {
it('extends foo/pages/index.vue', async () => {
const html = await $fetch('/foo')
expect(html).toContain('Hello from extended page of foo!')
})
it('extends bar/pages/override.vue over foo/pages/override.vue', async () => {
const html = await $fetch('/override')
expect(html).toContain('Extended page from bar')
})
})
describe('middlewares', () => {
it('extends foo/middleware/foo', async () => {
const html = await $fetch('/with-middleware')
expect(html).toContain('Injected by extended middleware')
})
it('extends bar/middleware/override.vue over foo/middleware/override.vue', async () => {
const html = await $fetch('/with-middleware-override')
expect(html).toContain('Injected by extended middleware from bar')
})
})
describe('dynamic paths', () => {

View File

@ -0,0 +1,3 @@
export default defineNuxtRouteMiddleware((to) => {
to.meta.override = 'Injected by extended middleware from bar'
})

View File

@ -0,0 +1,3 @@
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({})

View File

@ -0,0 +1,3 @@
<template>
<div>Extended page from bar</div>
</template>

View File

@ -0,0 +1,3 @@
export default defineNuxtRouteMiddleware((to) => {
to.meta.foo = 'Injected by extended middleware'
})

View File

@ -0,0 +1,3 @@
export default defineNuxtRouteMiddleware((to) => {
to.meta.override = 'Injected by extended middleware from foo'
})

View File

@ -0,0 +1,3 @@
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({})

View File

@ -0,0 +1,3 @@
<template>
<div>Hello from extended page of foo!</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div>Extended page from foo</div>
</template>

View File

@ -0,0 +1,9 @@
<script setup>
definePageMeta({
middleware: 'override'
})
</script>
<template>
<div>{{ $route.meta.override }}</div>
</template>

View File

@ -0,0 +1,9 @@
<script setup>
definePageMeta({
middleware: 'foo'
})
</script>
<template>
<div>{{ $route.meta.foo }}</div>
</template>

View File

@ -4,6 +4,10 @@ import { addComponent } from '@nuxt/kit'
export default defineNuxtConfig({
buildDir: process.env.NITRO_BUILD_DIR,
builder: process.env.TEST_WITH_WEBPACK ? 'webpack' : 'vite',
extends: [
'./extends/bar',
'./extends/foo'
],
nitro: {
output: { dir: process.env.NITRO_OUTPUT_DIR }
},