diff --git a/packages/nuxt/src/core/plugins/import-protection.ts b/packages/nuxt/src/core/plugins/import-protection.ts index f0f2492b1e..3c7f614df6 100644 --- a/packages/nuxt/src/core/plugins/import-protection.ts +++ b/packages/nuxt/src/core/plugins/import-protection.ts @@ -1,7 +1,7 @@ import { createRequire } from 'node:module' import { createUnplugin } from 'unplugin' import { logger } from '@nuxt/kit' -import { isAbsolute, join, relative, resolve } from 'pathe' +import { isAbsolute, join, relative } from 'pathe' import type { Nuxt } from '@nuxt/schema' import escapeRE from 'escape-string-regexp' @@ -21,7 +21,7 @@ export const vueAppPatterns = (nuxt: Nuxt) => [ [new RegExp(`^${escapeRE(m as string)}$`), 'Importing directly from module entry points is not allowed.']), ...[/(^|node_modules\/)@nuxt\/kit/, /^nitropack/] .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')) + '\\/(api|routes|middleware|plugins)\\/'), 'Importing from server is not allowed 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 ImportProtectionPlugin = createUnplugin(function (options: ImportProtectionOptions) { diff --git a/packages/nuxt/test/import-protection.test.ts b/packages/nuxt/test/import-protection.test.ts new file mode 100644 index 0000000000..4621a6eaa6 --- /dev/null +++ b/packages/nuxt/test/import-protection.test.ts @@ -0,0 +1,48 @@ +import { normalize } from 'pathe' +import { describe, expect, it } from 'vitest' +import { ImportProtectionPlugin, vueAppPatterns } from '../src/core/plugins/import-protection' + +const testsToTriggerOn = [ + ['~/nuxt.config', 'app.vue', true], + ['./nuxt.config', 'app.vue', true], + ['./nuxt.config.ts', 'app.vue', true], + ['nuxt.config.ts', 'app.vue', true], + ['./.nuxt/nuxt.config', 'app.vue', false], + ['.nuxt/nuxt.config', 'app.vue', false], + ['nuxt', 'components/Component.vue', true], + ['nuxt3', 'components/Component.vue', true], + ['/root/node_modules/@vue/composition-api', 'components/Component.vue', true], + ['@vue/composition-api', 'components/Component.vue', true], + ['@nuxt/kit', 'components/Component.vue', true], + ['/root/node_modules/@nuxt/kit', 'components/Component.vue', true], + ['some-nuxt-module', 'components/Component.vue', true], + ['/root/src/server/api/test.ts', 'components/Component.vue', true], + ['src/server/api/test.ts', 'components/Component.vue', true] +] as const + +describe('import protection', () => { + it.each(testsToTriggerOn)('should protect %s', async (id, importer, isProtected) => { + const result = await transformWithImportProtection(id, importer) + if (!isProtected) { + expect(result).toBeNull() + } else { + expect(result).toBeDefined() + expect(normalize(result)).contains('unenv/runtime/mock/proxy') + } + }) +}) + +const transformWithImportProtection = (id: string, importer: string) => { + const plugin = ImportProtectionPlugin.rollup({ + rootDir: '/root', + patterns: vueAppPatterns({ + options: { + modules: ['some-nuxt-module'], + srcDir: 'src/', + dir: { server: 'server' } + } + } as any) + }) + + return (plugin as any).resolveId(id, importer) +}