mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
feat(nuxt)!: support universal global middleware (#5038)
This commit is contained in:
parent
7efdb486db
commit
8c2c80e43e
@ -0,0 +1,3 @@
|
||||
export default defineNuxtRouteMiddleware(() => {
|
||||
console.log('running global middleware')
|
||||
})
|
@ -4,6 +4,8 @@ import { createError } from 'h3'
|
||||
import { defineNuxtPlugin } from '..'
|
||||
import { callWithNuxt } from '../nuxt'
|
||||
import { clearError, navigateTo, throwError, useRuntimeConfig } from '#app'
|
||||
// @ts-ignore
|
||||
import { globalMiddleware } from '#build/middleware'
|
||||
|
||||
interface Route {
|
||||
/** Percentage encoded pathname section of the URL. */
|
||||
@ -201,7 +203,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
|
||||
to.meta = reactive(to.meta || {})
|
||||
nuxtApp._processingMiddleware = true
|
||||
|
||||
const middlewareEntries = new Set<RouteGuard>(nuxtApp._middleware.global)
|
||||
const middlewareEntries = new Set<RouteGuard>([...globalMiddleware, ...nuxtApp._middleware.global])
|
||||
|
||||
for (const middleware of middlewareEntries) {
|
||||
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { dirname, resolve, basename, extname } from 'pathe'
|
||||
import { dirname, resolve } from 'pathe'
|
||||
import defu from 'defu'
|
||||
import { kebabCase } from 'scule'
|
||||
import type { Nuxt, NuxtApp, NuxtPlugin } from '@nuxt/schema'
|
||||
import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils, tryResolveModule } from '@nuxt/kit'
|
||||
|
||||
import * as defaultTemplates from './templates'
|
||||
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
|
||||
|
||||
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
|
||||
return defu(options, {
|
||||
@ -83,6 +83,17 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve middleware/ from all config layers
|
||||
app.middleware = []
|
||||
for (const config of nuxt.options._layers.map(layer => layer.config)) {
|
||||
const middlewareFiles = await resolveFiles(config.srcDir, `${config.dir?.middleware || 'middleware'}/*{${nuxt.options.extensions.join(',')}}`)
|
||||
app.middleware.push(...middlewareFiles.map((file) => {
|
||||
const name = getNameFromPath(file)
|
||||
return { name, path: file, global: hasSuffix(file, '.global') }
|
||||
}))
|
||||
}
|
||||
app.middleware = uniqueBy(app.middleware, 'name')
|
||||
|
||||
// Resolve plugins
|
||||
app.plugins = [
|
||||
...nuxt.options.plugins.map(normalizePlugin)
|
||||
@ -101,18 +112,3 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
||||
// Extend app
|
||||
await nuxt.callHook('app:resolve', app)
|
||||
}
|
||||
|
||||
function getNameFromPath (path: string) {
|
||||
return kebabCase(basename(path).replace(extname(path), '')).replace(/["']/g, '')
|
||||
}
|
||||
|
||||
function uniqueBy <T, K extends keyof T> (arr: T[], key: K) {
|
||||
const res: T[] = []
|
||||
const seen = new Set<T[K]>()
|
||||
for (const item of arr) {
|
||||
if (seen.has(item[key])) { continue }
|
||||
seen.add(item[key])
|
||||
res.push(item)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export async function build (nuxt: Nuxt) {
|
||||
if (nuxt.options.dev) {
|
||||
watch(nuxt)
|
||||
nuxt.hook('builder:watch', async (event, path) => {
|
||||
if (event !== 'change' && /^(app\.|error\.|plugins\/|layouts\/)/i.test(path)) {
|
||||
if (event !== 'change' && /^(app\.|error\.|plugins\/|middleware\/|layouts\/)/i.test(path)) {
|
||||
if (path.startsWith('app')) {
|
||||
app.mainComponent = null
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Nuxt, NuxtApp, NuxtMiddleware } from '@nuxt/schema'
|
||||
import { Nuxt, NuxtApp } from '@nuxt/schema'
|
||||
import { createTransformer } from 'unctx/transform'
|
||||
import { createUnplugin } from 'unplugin'
|
||||
|
||||
@ -8,15 +8,13 @@ export const UnctxTransformPlugin = (nuxt: Nuxt) => {
|
||||
})
|
||||
|
||||
let app: NuxtApp | undefined
|
||||
let middleware: NuxtMiddleware[] = []
|
||||
nuxt.hook('app:resolve', (_app) => { app = _app })
|
||||
nuxt.hook('pages:middleware:extend', (_middlewares) => { middleware = _middlewares })
|
||||
|
||||
return createUnplugin((options: { sourcemap?: boolean } = {}) => ({
|
||||
name: 'unctx:transfrom',
|
||||
enforce: 'post',
|
||||
transformInclude (id) {
|
||||
return Boolean(app?.plugins.find(i => i.src === id) || middleware.find(m => m.path === id))
|
||||
return Boolean(app?.plugins.find(i => i.src === id) || app.middleware.find(m => m.path === id))
|
||||
},
|
||||
transform (code, id) {
|
||||
const result = transformer.transform(code)
|
||||
|
@ -159,7 +159,7 @@ export const schemaTemplate = {
|
||||
// Add layouts template
|
||||
export const layoutTemplate: NuxtTemplate = {
|
||||
filename: 'layouts.mjs',
|
||||
getContents ({ app }) {
|
||||
getContents ({ app }: TemplateContext) {
|
||||
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
|
||||
return [name, `defineAsyncComponent(${genDynamicImport(file)})`]
|
||||
}))
|
||||
@ -170,6 +170,21 @@ export const layoutTemplate: NuxtTemplate = {
|
||||
}
|
||||
}
|
||||
|
||||
// Add middleware template
|
||||
export const middlewareTemplate: NuxtTemplate = {
|
||||
filename: 'middleware.mjs',
|
||||
getContents ({ app }: TemplateContext) {
|
||||
const globalMiddleware = app.middleware.filter(mw => mw.global)
|
||||
const namedMiddleware = app.middleware.filter(mw => !mw.global)
|
||||
const namedMiddlewareObject = genObjectFromRawEntries(namedMiddleware.map(mw => [mw.name, genDynamicImport(mw.path)]))
|
||||
return [
|
||||
...globalMiddleware.map(mw => genImport(mw.path, genSafeVariableName(mw.name))),
|
||||
`export const globalMiddleware = ${genArrayFromRaw(globalMiddleware.map(mw => genSafeVariableName(mw.name)))}`,
|
||||
`export const namedMiddleware = ${namedMiddlewareObject}`
|
||||
].join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
export const clientConfigTemplate: NuxtTemplate = {
|
||||
filename: 'nitro.client.mjs',
|
||||
getContents: () => `
|
||||
|
25
packages/nuxt/src/core/utils.ts
Normal file
25
packages/nuxt/src/core/utils.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { basename, extname } from 'pathe'
|
||||
import { kebabCase, pascalCase } from 'scule'
|
||||
|
||||
export function getNameFromPath (path: string) {
|
||||
return kebabCase(basename(path).replace(extname(path), '')).replace(/["']/g, '')
|
||||
}
|
||||
|
||||
export function uniqueBy <T, K extends keyof T> (arr: T[], key: K) {
|
||||
const res: T[] = []
|
||||
const seen = new Set<T[K]>()
|
||||
for (const item of arr) {
|
||||
if (seen.has(item[key])) { continue }
|
||||
seen.add(item[key])
|
||||
res.push(item)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export function hasSuffix (path: string, suffix: string) {
|
||||
return basename(path).replace(extname(path), '').endsWith(suffix)
|
||||
}
|
||||
|
||||
export function getImportName (name: string) {
|
||||
return pascalCase(name).replace(/[^\w]/g, r => '_' + r.charCodeAt(0))
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath } from '@nuxt/kit'
|
||||
import { resolve } from 'pathe'
|
||||
import { genDynamicImport, genString, genArrayFromRaw, genImport, genObjectFromRawEntries, genSafeVariableName } from 'knitwork'
|
||||
import { genString, genImport, genObjectFromRawEntries } from 'knitwork'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { NuxtApp } from '@nuxt/schema'
|
||||
import { distDir } from '../dirs'
|
||||
import { resolvePagesRoutes, normalizeRoutes, resolveMiddleware } from './utils'
|
||||
import { resolvePagesRoutes, normalizeRoutes } from './utils'
|
||||
import { TransformMacroPlugin, TransformMacroPluginOptions } from './macros'
|
||||
|
||||
export default defineNuxtModule({
|
||||
@ -33,6 +34,7 @@ export default defineNuxtModule({
|
||||
nuxt.hook('builder:watch', async (event, path) => {
|
||||
const dirs = [
|
||||
nuxt.options.dir.pages,
|
||||
nuxt.options.dir.layouts,
|
||||
nuxt.options.dir.middleware
|
||||
].filter(Boolean)
|
||||
|
||||
@ -102,29 +104,11 @@ export default defineNuxtModule({
|
||||
}
|
||||
})
|
||||
|
||||
// Add middleware template
|
||||
addTemplate({
|
||||
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)]))
|
||||
return [
|
||||
...globalMiddleware.map(mw => genImport(mw.path, genSafeVariableName(mw.name))),
|
||||
`export const globalMiddleware = ${genArrayFromRaw(globalMiddleware.map(mw => genSafeVariableName(mw.name)))}`,
|
||||
`export const namedMiddleware = ${namedMiddlewareObject}`
|
||||
].join('\n')
|
||||
}
|
||||
})
|
||||
|
||||
addTemplate({
|
||||
filename: 'types/middleware.d.ts',
|
||||
getContents: async () => {
|
||||
getContents: ({ app }: { app: NuxtApp }) => {
|
||||
const composablesFile = resolve(runtimeDir, 'composables')
|
||||
const middleware = await resolveMiddleware()
|
||||
const namedMiddleware = middleware.filter(mw => !mw.global)
|
||||
const namedMiddleware = app.middleware.filter(mw => !mw.global)
|
||||
return [
|
||||
'import type { NavigationGuard } from \'vue-router\'',
|
||||
`export type MiddlewareKey = ${namedMiddleware.map(mw => genString(mw.name)).join(' | ') || 'string'}`,
|
||||
@ -139,7 +123,7 @@ export default defineNuxtModule({
|
||||
|
||||
addTemplate({
|
||||
filename: 'types/layouts.d.ts',
|
||||
getContents: ({ app }) => {
|
||||
getContents: ({ app }: { app: NuxtApp }) => {
|
||||
const composablesFile = resolve(runtimeDir, 'composables')
|
||||
return [
|
||||
'import { ComputedRef, Ref } from \'vue\'',
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { basename, extname, normalize, relative, resolve } from 'pathe'
|
||||
import { extname, normalize, relative, resolve } from 'pathe'
|
||||
import { encodePath } from 'ufo'
|
||||
import { NuxtMiddleware, NuxtPage } from '@nuxt/schema'
|
||||
import { NuxtPage } from '@nuxt/schema'
|
||||
import { resolveFiles, useNuxt } from '@nuxt/kit'
|
||||
import { kebabCase } from 'scule'
|
||||
import { genImport, genDynamicImport, genArrayFromRaw, genSafeVariableName } from 'knitwork'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { uniqueBy } from '../core/utils'
|
||||
|
||||
enum SegmentParserState {
|
||||
initial,
|
||||
@ -241,39 +241,3 @@ export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> =
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveMiddleware (): Promise<NuxtMiddleware[]> {
|
||||
const nuxt = useNuxt()
|
||||
|
||||
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) {
|
||||
return kebabCase(basename(path).replace(extname(path), '')).replace(/["']/g, '').replace('.global', '')
|
||||
}
|
||||
|
||||
function hasSuffix (path: string, suffix: string) {
|
||||
return basename(path).replace(extname(path), '').endsWith(suffix)
|
||||
}
|
||||
|
||||
function uniqueBy <T, K extends keyof T> (arr: T[], key: K) {
|
||||
const res: T[] = []
|
||||
const seen = new Set<T[K]>()
|
||||
for (const item of arr) {
|
||||
if (seen.has(item[key])) { continue }
|
||||
seen.add(item[key])
|
||||
res.push(item)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -72,7 +72,6 @@ export interface NuxtHooks {
|
||||
'app:templatesGenerated': (app: NuxtApp) => HookResult
|
||||
'builder:generateApp': () => HookResult
|
||||
'pages:extend': (pages: NuxtPage[]) => HookResult
|
||||
'pages:middleware:extend': (middleware: NuxtMiddleware[]) => HookResult
|
||||
|
||||
// Auto imports
|
||||
'autoImports:sources': (presets: ImportPresetWithDeprecation[]) => HookResult
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Hookable } from 'hookable'
|
||||
import type { Ignore } from 'ignore'
|
||||
import type { NuxtHooks, NuxtLayout } from './hooks'
|
||||
import type { NuxtHooks, NuxtLayout, NuxtMiddleware } from './hooks'
|
||||
import type { NuxtOptions } from './config'
|
||||
|
||||
export interface Nuxt {
|
||||
@ -58,6 +58,7 @@ export interface NuxtApp {
|
||||
extensions: string[]
|
||||
plugins: NuxtPlugin[]
|
||||
layouts: Record<string, NuxtLayout>
|
||||
middleware: NuxtMiddleware[]
|
||||
templates: NuxtTemplate[]
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user