feat(nuxt3): add import protection patterns (#2834)

This commit is contained in:
Daniel Roe 2022-01-24 13:25:23 +00:00 committed by GitHub
parent d5cbe09ced
commit 7553849371
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 1 deletions

View File

@ -1,6 +1,7 @@
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { wpfs, getNitroContext, createDevServer, resolveMiddleware, build, prepare, generate, writeTypes, scanMiddleware } from '@nuxt/nitro' import { wpfs, getNitroContext, createDevServer, resolveMiddleware, build, prepare, generate, writeTypes, scanMiddleware } from '@nuxt/nitro'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { ImportProtectionPlugin } from './plugins/import-protection'
export function initNitro (nuxt: Nuxt) { export function initNitro (nuxt: Nuxt) {
// Create contexts // Create contexts
@ -27,6 +28,17 @@ export function initNitro (nuxt: Nuxt) {
nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close')) nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close'))
nitroDevContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template)) nitroDevContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
// Register nuxt3 protection patterns
nitroDevContext._internal.hooks.hook('nitro:rollup:before', (ctx) => {
ctx.rollupConfig.plugins.push(ImportProtectionPlugin.rollup({
rootDir: nuxt.options.rootDir,
patterns: [
...['#app', /^#build(\/|$)/]
.map(p => [p, 'Vue app aliases are not allowed in server routes.']) as [RegExp | string, string][]
]
}))
})
// Add typed route responses // Add typed route responses
nuxt.hook('prepare:types', (opts) => { nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: resolve(nuxt.options.buildDir, 'nitro.d.ts') }) opts.references.push({ path: resolve(nuxt.options.buildDir, 'nitro.d.ts') })

View File

@ -1,13 +1,14 @@
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { createHooks } from 'hookable' import { createHooks } from 'hookable'
import type { Nuxt, NuxtOptions, NuxtConfig, ModuleContainer, NuxtHooks } from '@nuxt/schema' import type { Nuxt, NuxtOptions, NuxtConfig, ModuleContainer, NuxtHooks } from '@nuxt/schema'
import { loadNuxtConfig, LoadNuxtOptions, nuxtCtx, installModule, addComponent } from '@nuxt/kit' import { loadNuxtConfig, LoadNuxtOptions, nuxtCtx, installModule, addComponent, addVitePlugin, addWebpackPlugin } from '@nuxt/kit'
import pagesModule from '../pages/module' import pagesModule from '../pages/module'
import metaModule from '../meta/module' import metaModule from '../meta/module'
import componentsModule from '../components/module' import componentsModule from '../components/module'
import autoImportsModule from '../auto-imports/module' import autoImportsModule from '../auto-imports/module'
import { distDir, pkgDir } from '../dirs' import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json' import { version } from '../../package.json'
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
import { initNitro } from './nitro' import { initNitro } from './nitro'
import { addModuleTranspiles } from './modules' import { addModuleTranspiles } from './modules'
@ -50,6 +51,15 @@ async function initNuxt (nuxt: Nuxt) {
} }
}) })
// Add import protection
const config = {
rootDir: nuxt.options.rootDir,
patterns: vueAppPatterns(nuxt)
}
addVitePlugin(ImportProtectionPlugin.vite(config))
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
// Init user modules // Init user modules
await nuxt.callHook('modules:before', { nuxt } as ModuleContainer) await nuxt.callHook('modules:before', { nuxt } as ModuleContainer)
const modulesToInstall = [ const modulesToInstall = [

View File

@ -0,0 +1,54 @@
import { createRequire } from 'module'
import { createUnplugin } from 'unplugin'
import consola from 'consola'
import { isAbsolute, relative, resolve } from 'pathe'
import type { Nuxt } from '@nuxt/schema'
const _require = createRequire(import.meta.url)
interface ImportProtectionOptions {
rootDir: string
patterns: [importPattern: string | RegExp, warning?: string][]
}
const escapeRE = (str: string) => str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
export const vueAppPatterns = (nuxt: Nuxt) => [
[/(^|node_modules\/)(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.'],
[/(^|node_modules\/)@vue\/composition-api/],
...nuxt.options.modules.filter(m => typeof m === 'string').map((m: string) =>
[new RegExp(`^${escapeRE(m)}$`), 'Importing directly from module entrypoints is not allowed.']),
...['#static-assets', '#static', '#config', '#assets', '#storage', '#server-middleware']
.map(i => [i, 'Nitro aliases cannot be imported in the Vue part of your app.']),
...[/(^|node_modules\/)@nuxt\/kit/, /(^|node_modules\/)@nuxt\/nitro/]
.map(i => [i, 'This module cannot be imported in the Vue part of your app.']),
[new RegExp(escapeRE(resolve(nuxt.options.srcDir, (nuxt.options.dir as any).server || 'server'))), 'Importing from server middleware is not allowed in the Vue part of your app.']
] as ImportProtectionOptions['patterns']
export const ImportProtectionPlugin = createUnplugin(function (options: ImportProtectionOptions) {
const cache: Record<string, Map<string | RegExp, boolean>> = {}
return {
name: 'nuxt:import-protection',
enforce: 'pre',
resolveId (id, importer) {
const invalidImports = options.patterns.filter(([pattern]) => pattern instanceof RegExp ? pattern.test(id) : pattern === id)
let matched: boolean
for (const match of invalidImports) {
cache[id] = cache[id] || new Map()
const [pattern, warning] = match
// Skip if already warned
if (cache[id].has(pattern)) { continue }
const relativeImporter = isAbsolute(importer) ? relative(options.rootDir, importer) : importer
consola.error(warning || 'Invalid import', `[importing \`${id}\` from \`${relativeImporter}\`]`)
cache[id].set(pattern, true)
matched = true
}
if (matched) {
return _require.resolve('unenv/runtime/mock/proxy')
}
return null
}
}
})