Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nuxt): apply more import protections for nitro runtime #25162

Merged
merged 10 commits into from Jan 12, 2024
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
51 changes: 40 additions & 11 deletions packages/nuxt/src/core/plugins/import-protection.ts
manniL marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -3,7 +3,7 @@ import { createUnplugin } from 'unplugin'
import { logger } from '@nuxt/kit'
import { isAbsolute, join, relative } 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,45 @@ 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/, /(^|node_modules\/)nuxt\/(config|kit|schema)/, 'nitropack']) {
danielroe marked this conversation as resolved.
Show resolved Hide resolved
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.'])
}
}

patterns.push(
[new RegExp(escapeRE(join(nuxt.options.srcDir, nuxt.options.serverDir || 'server')) + '\\/(api|routes|middleware|plugins)\\/'),
danielroe marked this conversation as resolved.
Show resolved Hide resolved
'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
11 changes: 6 additions & 5 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)
serverDir: 'server'
} satisfies Partial<NuxtOptions> as NuxtOptions
})
})

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