feat(kit,nuxt): resolve template imports from originating module (#31175)

This commit is contained in:
Daniel Roe 2025-03-01 12:37:06 +00:00
parent 156ab7c93c
commit 0b6c698e55
No known key found for this signature in database
GPG Key ID: CBC814C393D93268
5 changed files with 46 additions and 5 deletions

View File

@ -1,5 +1,6 @@
import { existsSync, promises as fsp } from 'node:fs'
import { basename, isAbsolute, join, parse, relative, resolve } from 'pathe'
import { fileURLToPath } from 'node:url'
import { basename, isAbsolute, join, normalize, parse, relative, resolve } from 'pathe'
import { hash } from 'ohash'
import type { Nuxt, NuxtServerTemplate, NuxtTemplate, NuxtTypeTemplate, ResolvedNuxtTemplate, TSReference } from '@nuxt/schema'
import { withTrailingSlash } from 'ufo'
@ -8,8 +9,9 @@ import type { TSConfig } from 'pkg-types'
import { gte } from 'semver'
import { readPackageJSON } from 'pkg-types'
import { resolveModulePath } from 'exsolve'
import { captureStackTrace } from 'errx'
import { filterInPlace } from './utils'
import { distDirURL, filterInPlace } from './utils'
import { directoryToURL } from './internal/esm'
import { getDirectory } from './module/install'
import { tryUseNuxt, useNuxt } from './context'
@ -27,6 +29,19 @@ export function addTemplate<T> (_template: NuxtTemplate<T> | string) {
// Remove any existing template with the same destination path
filterInPlace(nuxt.options.build.templates, p => normalizeTemplate(p).dst !== template.dst)
try {
const distDir = distDirURL.toString()
const { source } = captureStackTrace().find(e => e.source && !e.source.startsWith(distDir)) ?? {}
if (source) {
const path = normalize(fileURLToPath(source))
if (existsSync(path)) {
template._path = path
}
}
} catch {
// ignore errors as this is an additive feature
}
// Add to templates array
nuxt.options.build.templates.push(template)

View File

@ -19,3 +19,5 @@ export function filterInPlace<T> (array: T[], predicate: (item: T, index: number
}
export const MODE_RE = /\.(server|client)(\.\w+)*$/
export const distDirURL = new URL('.', import.meta.url)

View File

@ -1,6 +1,6 @@
import { parseNodeModulePath } from 'mlly'
import { resolveModulePath } from 'exsolve'
import { isAbsolute, normalize } from 'pathe'
import { isAbsolute, normalize, resolve } from 'pathe'
import type { Plugin } from 'vite'
import { directoryToURL, resolveAlias } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema'
@ -8,6 +8,8 @@ import type { Nuxt } from '@nuxt/schema'
import { pkgDir } from '../../dirs'
import { logger } from '../../utils'
const VIRTUAL_RE = /^\0?virtual:(?:nuxt:)?/
export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
const exclude: string[] = ['virtual:', '\0virtual:', '/__skip_vite', '@vitest/']
let conditions: string[]
@ -29,12 +31,24 @@ export function resolveDeepImportsPlugin (nuxt: Nuxt): Plugin {
conditions = [...resolvedConditions]
},
async resolveId (id, importer) {
if (!importer || isAbsolute(id) || (!isAbsolute(importer) && !importer.startsWith('virtual:') && !importer.startsWith('\0virtual:')) || exclude.some(e => id.startsWith(e))) {
if (!importer || isAbsolute(id) || (!isAbsolute(importer) && !VIRTUAL_RE.test(importer)) || exclude.some(e => id.startsWith(e))) {
return
}
const normalisedId = resolveAlias(normalize(id), nuxt.options.alias)
const normalisedImporter = importer.replace(/^\0?virtual:(?:nuxt:)?/, '')
const isNuxtTemplate = importer.startsWith('virtual:nuxt')
const normalisedImporter = (isNuxtTemplate ? decodeURIComponent(importer) : importer).replace(VIRTUAL_RE, '')
if (nuxt.options.experimental.templateImportResolution !== false && isNuxtTemplate) {
const template = nuxt.options.build.templates.find(t => resolve(nuxt.options.buildDir, t.filename!) === normalisedImporter)
if (template?._path) {
const res = await this.resolve?.(normalisedId, template._path, { skipSelf: true })
if (res !== undefined && res !== null) {
return res
}
}
}
const dir = parseNodeModulePath(normalisedImporter).dir || pkgDir
const res = await this.resolve?.(normalisedId, dir, { skipSelf: true })

View File

@ -646,5 +646,10 @@ export default defineResolvers({
return typeof val === 'boolean' ? val : true
},
},
/**
* Disable resolving imports into Nuxt templates from the path of the module that added the template.
*/
templateImportResolution: true,
},
})

View File

@ -43,6 +43,11 @@ export interface NuxtTemplate<Options = TemplateDefaultOptions> {
getContents?: (data: { nuxt: Nuxt, app: NuxtApp, options: Options }) => string | Promise<string>
/** Write to filesystem */
write?: boolean
/**
* The source path of the template (to try resolving dependencies from).
* @internal
*/
_path?: string
}
export interface NuxtServerTemplate {