feat(nuxt3): middleware type generation improvements (#2945)

This commit is contained in:
Daniel Roe 2022-01-27 11:13:32 +00:00 committed by GitHub
parent 6e06d2b4c9
commit 965f2abaee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 54 additions and 20 deletions

View File

@ -33,6 +33,7 @@
"cookie-es": "^0.5.0",
"defu": "^5.0.1",
"destr": "^1.1.0",
"escape-string-regexp": "^5.0.0",
"globby": "^13.1.0",
"h3": "^0.3.9",
"hash-sum": "^2.0.0",

View File

@ -1,4 +1,5 @@
import type { AutoImport } from '@nuxt/schema'
import escapeRE from 'escape-string-regexp'
export interface AutoImportContext {
autoImports: AutoImport[]
@ -36,7 +37,7 @@ export function updateAutoImportContext (ctx: AutoImportContext) {
ctx.autoImports = ctx.autoImports.filter(i => i.disabled !== true)
// Create regex
ctx.matchRE = new RegExp(`\\b(${ctx.autoImports.map(i => i.as).join('|')})\\b`, 'g')
ctx.matchRE = new RegExp(`\\b(${ctx.autoImports.map(i => escapeRE(i.as)).join('|')})\\b`, 'g')
// Create map
ctx.map.clear()

View File

@ -3,6 +3,7 @@ import { createUnplugin } from 'unplugin'
import consola from 'consola'
import { isAbsolute, relative, resolve } from 'pathe'
import type { Nuxt } from '@nuxt/schema'
import escapeRE from 'escape-string-regexp'
const _require = createRequire(import.meta.url)
@ -11,8 +12,6 @@ interface ImportProtectionOptions {
patterns: [importPattern: string | RegExp, warning?: string][]
}
const escapeRE = (str: string) => str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
export const vueAppPatterns = (nuxt: Nuxt) => [
[/^(nuxt3|nuxt)/, '`nuxt3`/`nuxt` cannot be imported directly. Instead, import runtime Nuxt composables from `#app` or `#imports`.'],
[/nuxt\.config/, 'Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module.'],

View File

@ -2,6 +2,7 @@ import { templateUtils } from '@nuxt/kit'
import type { Nuxt, NuxtApp } from '@nuxt/schema'
import { relative } from 'pathe'
import escapeRE from 'escape-string-regexp'
type TemplateContext = {
nuxt: Nuxt;
@ -94,7 +95,7 @@ export const pluginsDeclaration = {
filename: 'plugins.d.ts',
write: true,
getContents: (ctx: TemplateContext) => {
const EXTENSION_RE = new RegExp(`(?<=\\w)(${ctx.nuxt.options.extensions.map(e => `\\${e}`).join('|')})$`, 'g')
const EXTENSION_RE = new RegExp(`(?<=\\w)(${ctx.nuxt.options.extensions.map(e => `\\${escapeRE(e)}`).join('|')})$`, 'g')
const tsImports = ctx.app.plugins.map(p => relative(ctx.nuxt.options.buildDir, p.src).replace(EXTENSION_RE, ''))
return `// Generated by Nuxt3'

View File

@ -1,6 +1,7 @@
import { existsSync } from 'fs'
import { defineNuxtModule, addTemplate, addPlugin, templateUtils, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
import { resolve } from 'pathe'
import escapeRE from 'escape-string-regexp'
import { distDir } from '../dirs'
import { resolveLayouts, resolvePagesRoutes, normalizeRoutes, resolveMiddleware, getImportName } from './utils'
import { TransformMacroPlugin, TransformMacroPluginOptions } from './macros'
@ -25,7 +26,13 @@ export default defineNuxtModule({
// Regenerate templates when adding or removing pages
nuxt.hook('builder:watch', async (event, path) => {
const pathPattern = new RegExp(`^(${nuxt.options.dir.pages}|${nuxt.options.dir.layouts})/`)
const dirs = [
nuxt.options.dir.pages,
nuxt.options.dir.layouts,
nuxt.options.dir.middleware
].filter(Boolean)
const pathPattern = new RegExp(`^(${dirs.map(escapeRE).join('|')})/`)
if (event !== 'change' && path.match(pathPattern)) {
await nuxt.callHook('builder:generateApp')
}
@ -83,13 +90,13 @@ export default defineNuxtModule({
filename: 'middleware.mjs',
async getContents () {
const middleware = await resolveMiddleware()
await nuxt.callHook('pages:middleware:extend', middleware)
const middlewareObject = Object.fromEntries(middleware.map(mw => [mw.name, `{() => import('${mw.path}')}`]))
const globalMiddleware = middleware.filter(mw => mw.global)
const namedMiddleware = middleware.filter(mw => !mw.global)
const namedMiddlewareObject = Object.fromEntries(namedMiddleware.map(mw => [mw.name, `{() => import('${mw.path}')}`]))
return [
...globalMiddleware.map(mw => `import ${getImportName(mw.name)} from '${mw.path}'`),
`export const globalMiddleware = [${globalMiddleware.map(mw => getImportName(mw.name)).join(', ')}]`,
`export const namedMiddleware = ${templateUtils.serialize(middlewareObject)}`
`export const namedMiddleware = ${templateUtils.serialize(namedMiddlewareObject)}`
].join('\n')
}
})
@ -100,9 +107,10 @@ export default defineNuxtModule({
getContents: async () => {
const composablesFile = resolve(runtimeDir, 'composables')
const middleware = await resolveMiddleware()
const namedMiddleware = middleware.filter(mw => !mw.global)
return [
'import type { NavigationGuard } from \'vue-router\'',
`export type MiddlewareKey = ${middleware.map(mw => `"${mw.name}"`).join(' | ') || 'string'}`,
`export type MiddlewareKey = ${namedMiddleware.map(mw => `"${mw.name}"`).join(' | ') || 'string'}`,
`declare module '${composablesFile}' {`,
' interface PageMeta {',
' middleware?: MiddlewareKey | NavigationGuard | Array<MiddlewareKey | NavigationGuard>',
@ -130,11 +138,6 @@ export default defineNuxtModule({
}
})
nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: resolve(nuxt.options.buildDir, 'middleware.d.ts') })
references.push({ path: resolve(nuxt.options.buildDir, 'layouts.d.ts') })
})
// Add layouts template
addTemplate({
filename: 'layouts.mjs',
@ -149,5 +152,11 @@ export default defineNuxtModule({
].join('\n')
}
})
// Add declarations for middleware and layout keys
nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: resolve(nuxt.options.buildDir, 'middleware.d.ts') })
references.push({ path: resolve(nuxt.options.buildDir, 'layouts.d.ts') })
})
}
})

View File

@ -3,6 +3,7 @@ import { encodePath } from 'ufo'
import type { Nuxt, NuxtMiddleware, NuxtPage } from '@nuxt/schema'
import { resolveFiles, useNuxt } from '@nuxt/kit'
import { kebabCase, pascalCase } from 'scule'
import escapeRE from 'escape-string-regexp'
enum SegmentParserState {
initial,
@ -37,7 +38,7 @@ export function generateRoutesFromFiles (files: string[], pagesDir: string): Nux
for (const file of files) {
const segments = relative(pagesDir, file)
.replace(new RegExp(`${extname(file)}$`), '')
.replace(new RegExp(`${escapeRE(extname(file))}$`), '')
.split('/')
const route: NuxtPage = {
@ -219,7 +220,9 @@ export async function resolveLayouts (nuxt: Nuxt) {
const layoutDir = resolve(nuxt.options.srcDir, nuxt.options.dir.layouts)
const files = await resolveFiles(layoutDir, `*{${nuxt.options.extensions.join(',')}}`)
return files.map(file => ({ name: getNameFromPath(file), file }))
const layouts = files.map(file => ({ name: getNameFromPath(file), file }))
await nuxt.callHook('pages:layouts:extend', layouts)
return layouts
}
export function normalizeRoutes (routes: NuxtPage[], metaImports: Set<string> = new Set()): { imports: Set<string>, routes: NuxtPage[]} {
@ -243,7 +246,9 @@ 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(',')}}`)
return files.map(path => ({ name: getNameFromPath(path), path, global: hasSuffix(path, '.global') }))
const middleware = files.map(path => ({ name: getNameFromPath(path), path, global: hasSuffix(path, '.global') }))
await nuxt.callHook('pages:middleware:extend', middleware)
return middleware
}
function getNameFromPath (path: string) {

View File

@ -433,6 +433,7 @@ export default {
layouts: 'layouts',
/**
* The middleware directory, each file of which will be auto-registered as a Nuxt middleware.
* @version 3
* @version 2
*/
middleware: 'middleware',

View File

@ -47,6 +47,11 @@ export type NuxtMiddleware = {
global?: boolean
}
export type NuxtLayout = {
name: string
file: string
}
export interface NuxtHooks {
// Kit
'kit:compatibility': (compatibility: NuxtCompatibility, issues: NuxtCompatibilityIssues) => HookResult
@ -58,6 +63,7 @@ export interface NuxtHooks {
'builder:generateApp': () => HookResult
'pages:extend': (pages: NuxtPage[]) => HookResult
'pages:middleware:extend': (middleware: NuxtMiddleware[]) => HookResult
'pages:layouts:extend': (layouts: NuxtLayout[]) => HookResult
// Auto imports
'autoImports:sources': (autoImportSources: AutoImportSource[]) => HookResult

View File

@ -26,6 +26,7 @@
"consola": "^2.15.3",
"defu": "^5.0.1",
"esbuild": "^0.14.14",
"escape-string-regexp": "^5.0.0",
"externality": "^0.1.6",
"fs-extra": "^10.0.0",
"magic-string": "^0.25.7",

View File

@ -1,4 +1,5 @@
import { createUnplugin } from 'unplugin'
import escapeRE from 'escape-string-regexp'
import type { Plugin } from 'vite'
interface DynamicBasePluginOptions {
@ -7,8 +8,6 @@ interface DynamicBasePluginOptions {
globalPublicPath?: string
}
const escapeRE = (str: string) => str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
export const RelativeAssetPlugin = function (): Plugin {
return {
name: 'nuxt:vite-relative-asset',

View File

@ -27,6 +27,7 @@
"css-minimizer-webpack-plugin": "^3.4.1",
"cssnano": "^5.0.16",
"esbuild-loader": "^2.18.0",
"escape-string-regexp": "^5.0.0",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.0",
"glob": "^7.2.0",

View File

@ -4,7 +4,7 @@ import WebpackBar from 'webpackbar'
import consola from 'consola'
import webpack from 'webpack'
import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin'
import { escapeRegExp } from 'lodash-es'
import escapeRegExp from 'escape-string-regexp'
import { joinURL } from 'ufo'
import WarningIgnorePlugin from '../plugins/warning-ignore'
import { WebpackConfigContext, applyPresets, fileName } from '../utils/config'

View File

@ -3182,6 +3182,7 @@ __metadata:
consola: ^2.15.3
defu: ^5.0.1
esbuild: ^0.14.14
escape-string-regexp: ^5.0.0
externality: ^0.1.6
fs-extra: ^10.0.0
magic-string: ^0.25.7
@ -3298,6 +3299,7 @@ __metadata:
css-minimizer-webpack-plugin: ^3.4.1
cssnano: ^5.0.16
esbuild-loader: ^2.18.0
escape-string-regexp: ^5.0.0
file-loader: ^6.2.0
fs-extra: ^10.0.0
glob: ^7.2.0
@ -9556,6 +9558,13 @@ __metadata:
languageName: node
linkType: hard
"escape-string-regexp@npm:^5.0.0":
version: 5.0.0
resolution: "escape-string-regexp@npm:5.0.0"
checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e
languageName: node
linkType: hard
"eslint-config-standard@npm:^16.0.3":
version: 16.0.3
resolution: "eslint-config-standard@npm:16.0.3"
@ -14841,6 +14850,7 @@ __metadata:
cookie-es: ^0.5.0
defu: ^5.0.1
destr: ^1.1.0
escape-string-regexp: ^5.0.0
globby: ^13.1.0
h3: ^0.3.9
hash-sum: ^2.0.0