mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +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 { defineNuxtPlugin } from '..'
|
||||||
import { callWithNuxt } from '../nuxt'
|
import { callWithNuxt } from '../nuxt'
|
||||||
import { clearError, navigateTo, throwError, useRuntimeConfig } from '#app'
|
import { clearError, navigateTo, throwError, useRuntimeConfig } from '#app'
|
||||||
|
// @ts-ignore
|
||||||
|
import { globalMiddleware } from '#build/middleware'
|
||||||
|
|
||||||
interface Route {
|
interface Route {
|
||||||
/** Percentage encoded pathname section of the URL. */
|
/** Percentage encoded pathname section of the URL. */
|
||||||
@ -201,7 +203,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
|
|||||||
to.meta = reactive(to.meta || {})
|
to.meta = reactive(to.meta || {})
|
||||||
nuxtApp._processingMiddleware = true
|
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) {
|
for (const middleware of middlewareEntries) {
|
||||||
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
|
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { promises as fsp } from 'node:fs'
|
import { promises as fsp } from 'node:fs'
|
||||||
import { dirname, resolve, basename, extname } from 'pathe'
|
import { dirname, resolve } from 'pathe'
|
||||||
import defu from 'defu'
|
import defu from 'defu'
|
||||||
import { kebabCase } from 'scule'
|
|
||||||
import type { Nuxt, NuxtApp, NuxtPlugin } from '@nuxt/schema'
|
import type { Nuxt, NuxtApp, NuxtPlugin } from '@nuxt/schema'
|
||||||
import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils, tryResolveModule } from '@nuxt/kit'
|
import { findPath, resolveFiles, normalizePlugin, normalizeTemplate, compileTemplate, templateUtils, tryResolveModule } from '@nuxt/kit'
|
||||||
|
|
||||||
import * as defaultTemplates from './templates'
|
import * as defaultTemplates from './templates'
|
||||||
|
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
|
||||||
|
|
||||||
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
|
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
|
||||||
return defu(options, {
|
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
|
// Resolve plugins
|
||||||
app.plugins = [
|
app.plugins = [
|
||||||
...nuxt.options.plugins.map(normalizePlugin)
|
...nuxt.options.plugins.map(normalizePlugin)
|
||||||
@ -101,18 +112,3 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
|||||||
// Extend app
|
// Extend app
|
||||||
await nuxt.callHook('app:resolve', 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) {
|
if (nuxt.options.dev) {
|
||||||
watch(nuxt)
|
watch(nuxt)
|
||||||
nuxt.hook('builder:watch', async (event, path) => {
|
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')) {
|
if (path.startsWith('app')) {
|
||||||
app.mainComponent = null
|
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 { createTransformer } from 'unctx/transform'
|
||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } from 'unplugin'
|
||||||
|
|
||||||
@ -8,15 +8,13 @@ export const UnctxTransformPlugin = (nuxt: Nuxt) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let app: NuxtApp | undefined
|
let app: NuxtApp | undefined
|
||||||
let middleware: NuxtMiddleware[] = []
|
|
||||||
nuxt.hook('app:resolve', (_app) => { app = _app })
|
nuxt.hook('app:resolve', (_app) => { app = _app })
|
||||||
nuxt.hook('pages:middleware:extend', (_middlewares) => { middleware = _middlewares })
|
|
||||||
|
|
||||||
return createUnplugin((options: { sourcemap?: boolean } = {}) => ({
|
return createUnplugin((options: { sourcemap?: boolean } = {}) => ({
|
||||||
name: 'unctx:transfrom',
|
name: 'unctx:transfrom',
|
||||||
enforce: 'post',
|
enforce: 'post',
|
||||||
transformInclude (id) {
|
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) {
|
transform (code, id) {
|
||||||
const result = transformer.transform(code)
|
const result = transformer.transform(code)
|
||||||
|
@ -159,7 +159,7 @@ export const schemaTemplate = {
|
|||||||
// Add layouts template
|
// Add layouts template
|
||||||
export const layoutTemplate: NuxtTemplate = {
|
export const layoutTemplate: NuxtTemplate = {
|
||||||
filename: 'layouts.mjs',
|
filename: 'layouts.mjs',
|
||||||
getContents ({ app }) {
|
getContents ({ app }: TemplateContext) {
|
||||||
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
|
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
|
||||||
return [name, `defineAsyncComponent(${genDynamicImport(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 = {
|
export const clientConfigTemplate: NuxtTemplate = {
|
||||||
filename: 'nitro.client.mjs',
|
filename: 'nitro.client.mjs',
|
||||||
getContents: () => `
|
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 { existsSync } from 'node:fs'
|
||||||
import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath } from '@nuxt/kit'
|
import { defineNuxtModule, addTemplate, addPlugin, addVitePlugin, addWebpackPlugin, findPath } from '@nuxt/kit'
|
||||||
import { resolve } from 'pathe'
|
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 escapeRE from 'escape-string-regexp'
|
||||||
|
import { NuxtApp } from '@nuxt/schema'
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
import { resolvePagesRoutes, normalizeRoutes, resolveMiddleware } from './utils'
|
import { resolvePagesRoutes, normalizeRoutes } from './utils'
|
||||||
import { TransformMacroPlugin, TransformMacroPluginOptions } from './macros'
|
import { TransformMacroPlugin, TransformMacroPluginOptions } from './macros'
|
||||||
|
|
||||||
export default defineNuxtModule({
|
export default defineNuxtModule({
|
||||||
@ -33,6 +34,7 @@ export default defineNuxtModule({
|
|||||||
nuxt.hook('builder:watch', async (event, path) => {
|
nuxt.hook('builder:watch', async (event, path) => {
|
||||||
const dirs = [
|
const dirs = [
|
||||||
nuxt.options.dir.pages,
|
nuxt.options.dir.pages,
|
||||||
|
nuxt.options.dir.layouts,
|
||||||
nuxt.options.dir.middleware
|
nuxt.options.dir.middleware
|
||||||
].filter(Boolean)
|
].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({
|
addTemplate({
|
||||||
filename: 'types/middleware.d.ts',
|
filename: 'types/middleware.d.ts',
|
||||||
getContents: async () => {
|
getContents: ({ app }: { app: NuxtApp }) => {
|
||||||
const composablesFile = resolve(runtimeDir, 'composables')
|
const composablesFile = resolve(runtimeDir, 'composables')
|
||||||
const middleware = await resolveMiddleware()
|
const namedMiddleware = app.middleware.filter(mw => !mw.global)
|
||||||
const namedMiddleware = middleware.filter(mw => !mw.global)
|
|
||||||
return [
|
return [
|
||||||
'import type { NavigationGuard } from \'vue-router\'',
|
'import type { NavigationGuard } from \'vue-router\'',
|
||||||
`export type MiddlewareKey = ${namedMiddleware.map(mw => genString(mw.name)).join(' | ') || 'string'}`,
|
`export type MiddlewareKey = ${namedMiddleware.map(mw => genString(mw.name)).join(' | ') || 'string'}`,
|
||||||
@ -139,7 +123,7 @@ export default defineNuxtModule({
|
|||||||
|
|
||||||
addTemplate({
|
addTemplate({
|
||||||
filename: 'types/layouts.d.ts',
|
filename: 'types/layouts.d.ts',
|
||||||
getContents: ({ app }) => {
|
getContents: ({ app }: { app: NuxtApp }) => {
|
||||||
const composablesFile = resolve(runtimeDir, 'composables')
|
const composablesFile = resolve(runtimeDir, 'composables')
|
||||||
return [
|
return [
|
||||||
'import { ComputedRef, Ref } from \'vue\'',
|
'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 { encodePath } from 'ufo'
|
||||||
import { NuxtMiddleware, NuxtPage } from '@nuxt/schema'
|
import { NuxtPage } from '@nuxt/schema'
|
||||||
import { resolveFiles, useNuxt } from '@nuxt/kit'
|
import { resolveFiles, useNuxt } from '@nuxt/kit'
|
||||||
import { kebabCase } from 'scule'
|
|
||||||
import { genImport, genDynamicImport, genArrayFromRaw, genSafeVariableName } from 'knitwork'
|
import { genImport, genDynamicImport, genArrayFromRaw, genSafeVariableName } from 'knitwork'
|
||||||
import escapeRE from 'escape-string-regexp'
|
import escapeRE from 'escape-string-regexp'
|
||||||
|
import { uniqueBy } from '../core/utils'
|
||||||
|
|
||||||
enum SegmentParserState {
|
enum SegmentParserState {
|
||||||
initial,
|
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
|
'app:templatesGenerated': (app: NuxtApp) => HookResult
|
||||||
'builder:generateApp': () => HookResult
|
'builder:generateApp': () => HookResult
|
||||||
'pages:extend': (pages: NuxtPage[]) => HookResult
|
'pages:extend': (pages: NuxtPage[]) => HookResult
|
||||||
'pages:middleware:extend': (middleware: NuxtMiddleware[]) => HookResult
|
|
||||||
|
|
||||||
// Auto imports
|
// Auto imports
|
||||||
'autoImports:sources': (presets: ImportPresetWithDeprecation[]) => HookResult
|
'autoImports:sources': (presets: ImportPresetWithDeprecation[]) => HookResult
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Hookable } from 'hookable'
|
import type { Hookable } from 'hookable'
|
||||||
import type { Ignore } from 'ignore'
|
import type { Ignore } from 'ignore'
|
||||||
import type { NuxtHooks, NuxtLayout } from './hooks'
|
import type { NuxtHooks, NuxtLayout, NuxtMiddleware } from './hooks'
|
||||||
import type { NuxtOptions } from './config'
|
import type { NuxtOptions } from './config'
|
||||||
|
|
||||||
export interface Nuxt {
|
export interface Nuxt {
|
||||||
@ -58,6 +58,7 @@ export interface NuxtApp {
|
|||||||
extensions: string[]
|
extensions: string[]
|
||||||
plugins: NuxtPlugin[]
|
plugins: NuxtPlugin[]
|
||||||
layouts: Record<string, NuxtLayout>
|
layouts: Record<string, NuxtLayout>
|
||||||
|
middleware: NuxtMiddleware[]
|
||||||
templates: NuxtTemplate[]
|
templates: NuxtTemplate[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user