Skip to content

Commit

Permalink
fix(nuxt): apply more import protections for nitro runtime (#25162)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Jan 12, 2024
1 parent 58f0627 commit c4b6560
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 25 deletions.
7 changes: 2 additions & 5 deletions packages/nuxt/src/core/nitro.ts
Expand Up @@ -17,7 +17,7 @@ import { template as defaultSpaLoadingTemplate } from '@nuxt/ui-templates/templa
import { version as nuxtVersion } from '../../package.json'
import { distDir } from '../dirs'
import { toArray } from '../utils'
import { ImportProtectionPlugin } from './plugins/import-protection'
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'

export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
// Resolve config
Expand Down Expand Up @@ -339,10 +339,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
nitroConfig.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][]
],
patterns: nuxtImportProtections(nuxt, { isNitro: true }),
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/]
})
)
Expand Down
4 changes: 2 additions & 2 deletions packages/nuxt/src/core/nuxt.ts
Expand Up @@ -16,7 +16,7 @@ import importsModule from '../imports/module'
/* eslint-enable */
import { distDir, pkgDir } from '../dirs'
import { version } from '../../package.json'
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
import { ImportProtectionPlugin, nuxtImportProtections } from './plugins/import-protection'
import type { UnctxTransformPluginOptions } from './plugins/unctx'
import { UnctxTransformPlugin } from './plugins/unctx'
import type { TreeShakeComposablesPluginOptions } from './plugins/tree-shake'
Expand Down Expand Up @@ -89,7 +89,7 @@ async function initNuxt (nuxt: Nuxt) {
rootDir: nuxt.options.rootDir,
// Exclude top-level resolutions by plugins
exclude: [join(nuxt.options.rootDir, 'index.html')],
patterns: vueAppPatterns(nuxt)
patterns: nuxtImportProtections(nuxt)
}
addVitePlugin(() => ImportProtectionPlugin.vite(config))
addWebpackPlugin(() => ImportProtectionPlugin.webpack(config))
Expand Down
55 changes: 43 additions & 12 deletions packages/nuxt/src/core/plugins/import-protection.ts
@@ -1,9 +1,9 @@
import { createRequire } from 'node:module'
import { createUnplugin } from 'unplugin'
import { logger } from '@nuxt/kit'
import { isAbsolute, join, relative } from 'pathe'
import { isAbsolute, join, relative, resolve } from 'pathe'
import escapeRE from 'escape-string-regexp'
import type { Nuxt } from 'nuxt/schema'
import type { NuxtOptions } from 'nuxt/schema'

const _require = createRequire(import.meta.url)

Expand All @@ -13,16 +13,47 @@ interface ImportProtectionOptions {
exclude?: Array<RegExp | string>
}

export const vueAppPatterns = (nuxt: Nuxt) => [
[/^(nuxt|nuxt3|nuxt-nightly)$/, '`nuxt`/`nuxt3`/`nuxt-nightly` 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: any) =>
[new RegExp(`^${escapeRE(m as string)}$`), 'Importing directly from module entry points is not allowed.']),
...[/(^|node_modules\/)@nuxt\/kit/, /(^|node_modules\/)nuxt\/(config|kit|schema)/, /^nitropack/]
.map(i => [i, 'This module cannot be imported in the Vue part of your app.']),
[new RegExp(escapeRE(join(nuxt.options.srcDir, (nuxt.options.dir as any).server || 'server')) + '\\/(api|routes|middleware|plugins)\\/'), 'Importing from server is not allowed in the Vue part of your app.']
] as ImportProtectionOptions['patterns']
export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: { isNitro?: boolean } = {}) => {
const patterns: ImportProtectionOptions['patterns'] = []

patterns.push([
/^(nuxt|nuxt3|nuxt-nightly)$/,
'`nuxt`, `nuxt3` or `nuxt-nightly` cannot be imported directly.' + (options.isNitro ? '' : ' Instead, import runtime Nuxt composables from `#app` or `#imports`.')
])

patterns.push([
/^((|~|~~|@|@@)\/)?nuxt\.config(\.|$)/,
'Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module.'
])

patterns.push([/(^|node_modules\/)@vue\/composition-api/])

for (const mod of nuxt.options.modules.filter(m => typeof m === 'string')) {
patterns.push([
new RegExp(`^${escapeRE(mod as string)}$`),
'Importing directly from module entry-points is not allowed.'
])
}

for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nuxt\/(config|kit|schema)/, 'nitropack']) {
patterns.push([i, 'This module cannot be imported' + (options.isNitro ? 'in server runtime.' : ' in the Vue part of your app.')])
}

if (options.isNitro) {
for (const i of ['#app', /^#build(\/|$)/]) {
patterns.push([i, 'Vue app aliases are not allowed in server runtime.'])
}
}

if (!options.isNitro) {
patterns.push([
new RegExp(escapeRE(relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, nuxt.options.serverDir || 'server'))) + '\\/(api|routes|middleware|plugins)\\/'),
'Importing from server is not allowed in the Vue part of your app.'
])
}

return patterns
}

export const ImportProtectionPlugin = createUnplugin(function (options: ImportProtectionOptions) {
const cache: Record<string, Map<string | RegExp, boolean>> = {}
Expand Down
13 changes: 7 additions & 6 deletions packages/nuxt/test/import-protection.test.ts
@@ -1,6 +1,7 @@
import { normalize } from 'pathe'
import { describe, expect, it } from 'vitest'
import { ImportProtectionPlugin, vueAppPatterns } from '../src/core/plugins/import-protection'
import { ImportProtectionPlugin, nuxtImportProtections } from '../src/core/plugins/import-protection'
import type { NuxtOptions } from '../schema'

const testsToTriggerOn = [
['~/nuxt.config', 'app.vue', true],
Expand Down Expand Up @@ -39,13 +40,13 @@ describe('import protection', () => {
const transformWithImportProtection = (id: string, importer: string) => {
const plugin = ImportProtectionPlugin.rollup({
rootDir: '/root',
patterns: vueAppPatterns({
patterns: nuxtImportProtections({
options: {
modules: ['some-nuxt-module'],
srcDir: 'src/',
dir: { server: 'server' }
}
} as any)
srcDir: '/root/src/',
serverDir: '/root/src/server'
} satisfies Partial<NuxtOptions> as NuxtOptions
})
})

return (plugin as any).resolveId(id, importer)
Expand Down

0 comments on commit c4b6560

Please sign in to comment.