diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md index 21593ff0d6..0aa50fd46b 100644 --- a/docs/1.getting-started/12.upgrade.md +++ b/docs/1.getting-started/12.upgrade.md @@ -509,6 +509,30 @@ These options have been set to their current values for some time and we do not * `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9) +#### Removal of Deprecated Internal CJS Utils + +🚦 **Impact Level**: Minimal + +##### What Changed + +We have now removed the following utils exported from `@nuxt/kit`: + +* `resolveModule` +* `requireModule` +* `importModule` +* `tryImportModule` +* `tryRequireModule` + +They were previously marked as deprecated and relied on CJS resolutions. + +##### Reasons for Change + +We now use [jiti](https://github.com/unjs/jiti) to resolve modules and other imports internally. It supports native ESM resolution where possible and should be less buggy. + +##### Migration Steps + +You can use [jiti](https://github.com/unjs/jiti) or [mlly](https://github.com/unjs/mlly) to do the same job in your own projects if you were relying on these utilities. + ## Nuxt 2 vs Nuxt 3+ In the table below, there is a quick comparison between 3 versions of Nuxt: diff --git a/packages/kit/index.d.ts b/packages/kit/index.d.ts deleted file mode 100644 index 43b479fd69..0000000000 --- a/packages/kit/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable no-var */ -declare global { - var __NUXT_PREPATHS__: string[] | string | undefined - var __NUXT_PATHS__: string[] | string | undefined -} - -export {} diff --git a/packages/kit/src/index.ts b/packages/kit/src/index.ts index 769cd6c242..d035408183 100644 --- a/packages/kit/src/index.ts +++ b/packages/kit/src/index.ts @@ -32,13 +32,4 @@ export { addTemplate, addTypeTemplate, normalizeTemplate, updateTemplates, write export { logger, useLogger } from './logger' // Internal Utils -// TODO -export { - resolveModule, - requireModule, - importModule, - tryImportModule, - tryRequireModule, -} from './internal/cjs' -export type { ResolveModuleOptions, RequireModuleOptions } from './internal/cjs' export { tryResolveModule } from './internal/esm' diff --git a/packages/kit/src/internal/cjs.ts b/packages/kit/src/internal/cjs.ts deleted file mode 100644 index 697ed2b59e..0000000000 --- a/packages/kit/src/internal/cjs.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { pathToFileURL } from 'node:url' -import { normalize } from 'pathe' -import { interopDefault } from 'mlly' -import { createJiti } from 'jiti' - -// TODO: use create-require for jest environment -const jiti = createJiti(process.cwd(), { interopDefault: true }) - -/** @deprecated Do not use CJS utils */ -export interface ResolveModuleOptions { - paths?: string | string[] -} - -/** @deprecated Do not use CJS utils */ -export interface RequireModuleOptions extends ResolveModuleOptions { - // TODO: use create-require for jest environment - // native?: boolean - /** Clear the require cache (force fresh require) but only if not within `node_modules` */ - clearCache?: boolean - - /** Automatically de-default the result of requiring the module. */ - interopDefault?: boolean -} - -/** @deprecated Do not use CJS utils */ -function isNodeModules (id: string) { - // TODO: Follow symlinks - return /[/\\]node_modules[/\\]/.test(id) -} - -/** @deprecated Do not use CJS utils */ -function clearRequireCache (id: string) { - if (isNodeModules(id)) { - return - } - - const entry = getRequireCacheItem(id) - - if (!entry) { - delete jiti.cache[id] - return - } - - if (entry.parent) { - entry.parent.children = entry.parent.children.filter(e => e.id !== id) - } - - for (const child of entry.children) { - clearRequireCache(child.id) - } - - delete jiti.cache[id] -} - -/** @deprecated Do not use CJS utils */ -function getRequireCacheItem (id: string) { - try { - return jiti.cache[id] - } catch (e) { - // ignore issues accessing require.cache - } -} - -export function getNodeModulesPaths (paths?: string[] | string) { - return ([] as Array).concat( - global.__NUXT_PREPATHS__, - paths || [], - process.cwd(), - global.__NUXT_PATHS__, - ).filter(Boolean) as string[] -} - -/** @deprecated Do not use CJS utils */ -export function resolveModule (id: string, opts: ResolveModuleOptions = {}) { - return normalize(jiti.resolve(id, { - paths: getNodeModulesPaths(opts.paths), - })) -} - -/** @deprecated Do not use CJS utils */ -export function requireModule (id: string, opts: RequireModuleOptions = {}) { - // Resolve id - const resolvedPath = resolveModule(id, opts) - - // Clear require cache if necessary - if (opts.clearCache && !isNodeModules(id)) { - clearRequireCache(resolvedPath) - } - - // Try to require - const requiredModule = jiti(resolvedPath) - - return requiredModule -} - -/** @deprecated Do not use CJS utils */ -export function importModule (id: string, opts: RequireModuleOptions = {}) { - const resolvedPath = resolveModule(id, opts) - if (opts.interopDefault !== false) { - return import(pathToFileURL(resolvedPath).href).then(interopDefault) - } - return import(pathToFileURL(resolvedPath).href) -} - -/** @deprecated Do not use CJS utils */ -export function tryImportModule (id: string, opts: RequireModuleOptions = {}) { - try { - return importModule(id, opts).catch(() => undefined) - } catch { - // intentionally empty as this is a `try-` function - } -} - -/** @deprecated Do not use CJS utils */ -export function tryRequireModule (id: string, opts: RequireModuleOptions = {}) { - try { - return requireModule(id, opts) - } catch { - // intentionally empty as this is a `try-` function - } -} diff --git a/packages/kit/src/module/install.ts b/packages/kit/src/module/install.ts index 7a0488258e..536b18ca86 100644 --- a/packages/kit/src/module/install.ts +++ b/packages/kit/src/module/install.ts @@ -2,10 +2,9 @@ import { existsSync, promises as fsp, lstatSync } from 'node:fs' import type { ModuleMeta, Nuxt, NuxtConfig, NuxtModule } from '@nuxt/schema' import { dirname, isAbsolute, join, resolve } from 'pathe' import { defu } from 'defu' +import { createJiti } from 'jiti' import { useNuxt } from '../context' -import { requireModule } from '../internal/cjs' -import { importModule } from '../internal/esm' -import { resolveAlias, resolvePath } from '../resolve' +import { resolveAlias } from '../resolve' import { logger } from '../logger' const NODE_MODULES_RE = /[/\\]node_modules[/\\]/ @@ -72,15 +71,20 @@ export const normalizeModuleTranspilePath = (p: string) => { export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, nuxt: Nuxt = useNuxt()) { let buildTimeModuleMeta: ModuleMeta = {} + + const jiti = createJiti(nuxt.options.rootDir, { + interopDefault: true, + alias: nuxt.options.alias, + }) + // Import if input is string if (typeof nuxtModule === 'string') { const paths = [join(nuxtModule, 'nuxt'), join(nuxtModule, 'module'), nuxtModule] let error: unknown for (const path of paths) { try { - const src = await resolvePath(path, { fallbackToOriginal: true }) - // Prefer ESM resolution if possible - nuxtModule = await importModule(src, nuxt.options.modulesDir).catch(() => null) ?? requireModule(src, { paths: nuxt.options.modulesDir }) + const src = jiti.esmResolve(path) + nuxtModule = await jiti.import(src) as NuxtModule // nuxt-module-builder generates a module.json with metadata including the version const moduleMetadataPath = join(dirname(src), 'module.json') @@ -94,7 +98,7 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n } } if (typeof nuxtModule !== 'function' && error) { - logger.error(`Error while requiring module \`${nuxtModule}\`: ${error}`) + logger.error(`Error while importing module \`${nuxtModule}\`: ${error}`) throw error } } diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts index da4a05349d..8820a73094 100644 --- a/packages/kit/src/template.ts +++ b/packages/kit/src/template.ts @@ -11,7 +11,6 @@ import { readPackageJSON } from 'pkg-types' import { tryResolveModule } from './internal/esm' import { getDirectory } from './module/install' import { tryUseNuxt, useNuxt } from './context' -import { getNodeModulesPaths } from './internal/cjs' import { resolveNuxtModule } from './resolve' /** @@ -266,7 +265,7 @@ export async function _generateTypes (nuxt: Nuxt) { await Promise.all([...nuxt.options.modules, ...nuxt.options._modules].map(async (id) => { if (typeof id !== 'string') { return } - const pkg = await readPackageJSON(id, { url: getNodeModulesPaths(nuxt.options.modulesDir) }).catch(() => null) + const pkg = await readPackageJSON(id, { url: nuxt.options.modulesDir }).catch(() => null) references.push(({ types: pkg?.name || id })) })) diff --git a/packages/nuxt/index.d.ts b/packages/nuxt/index.d.ts index 2256348248..db90cd08a0 100644 --- a/packages/nuxt/index.d.ts +++ b/packages/nuxt/index.d.ts @@ -2,8 +2,6 @@ declare global { var __NUXT_VERSION__: string var __NUXT_ASYNC_CONTEXT__: boolean - var __NUXT_PREPATHS__: string[] | string | undefined - var __NUXT_PATHS__: string[] | string | undefined interface Navigator { connection?: { diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts index ae0ff2b25b..c8ed9e5bbb 100644 --- a/packages/nuxt/src/core/nitro.ts +++ b/packages/nuxt/src/core/nitro.ts @@ -6,7 +6,7 @@ import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from import { joinURL, withTrailingSlash } from 'ufo' import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, writeTypes } from 'nitro' import type { Nitro, NitroConfig, NitroOptions } from 'nitro/types' -import { findPath, logger, resolveAlias, resolveIgnorePatterns, resolveNuxtModule, resolvePath } from '@nuxt/kit' +import { findPath, logger, resolveAlias, resolveIgnorePatterns, resolveNuxtModule } from '@nuxt/kit' import escapeRE from 'escape-string-regexp' import { defu } from 'defu' import { dynamicEventHandler } from 'h3' @@ -408,7 +408,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { }) const cacheDir = resolve(nuxt.options.buildDir, 'cache/nitro/prerender') - const cacheDriverPath = await resolvePath(join(distDir, 'core/runtime/nitro/cache-driver')) + const cacheDriverPath = join(distDir, 'core/runtime/nitro/cache-driver.js') await fsp.rm(cacheDir, { recursive: true, force: true }).catch(() => {}) nitro.options._config.storage = defu(nitro.options._config.storage, { 'internal:nuxt:prerender': { diff --git a/packages/nuxt/src/core/runtime/nitro/cache-driver.ts b/packages/nuxt/src/core/runtime/nitro/cache-driver.js similarity index 80% rename from packages/nuxt/src/core/runtime/nitro/cache-driver.ts rename to packages/nuxt/src/core/runtime/nitro/cache-driver.js index 60587fa21f..59a19e3b07 100644 --- a/packages/nuxt/src/core/runtime/nitro/cache-driver.ts +++ b/packages/nuxt/src/core/runtime/nitro/cache-driver.js @@ -1,10 +1,18 @@ +// @ts-check + import { defineDriver } from 'unstorage' import fsDriver from 'unstorage/drivers/fs-lite' import lruCache from 'unstorage/drivers/lru-cache' -const normalizeFsKey = (item: string) => item.replaceAll(':', '_') +/** + * @param {string} item + */ +const normalizeFsKey = item => item.replaceAll(':', '_') -export default defineDriver((opts: { base: string }) => { +/** + * @param {{ base: string }} opts + */ +export default defineDriver((opts) => { const fs = fsDriver({ base: opts.base }) const lru = lruCache({ max: 1000 }) diff --git a/packages/vite/package.json b/packages/vite/package.json index 406758cb7f..45484c8e0a 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -47,6 +47,7 @@ "externality": "^1.0.2", "get-port-please": "^3.1.2", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", + "jiti": "^2.0.0-beta.3", "knitwork": "^1.1.0", "magic-string": "^0.30.10", "mlly": "^1.7.1", diff --git a/packages/vite/src/css.ts b/packages/vite/src/css.ts index 954d630a8c..6457c78e43 100644 --- a/packages/vite/src/css.ts +++ b/packages/vite/src/css.ts @@ -1,9 +1,7 @@ -import { fileURLToPath, pathToFileURL } from 'node:url' -import { requireModule, tryResolveModule } from '@nuxt/kit' import type { Nuxt, NuxtOptions } from '@nuxt/schema' import type { InlineConfig as ViteConfig } from 'vite' -import { interopDefault } from 'mlly' import type { Plugin } from 'postcss' +import { createJiti } from 'jiti' function sortPlugins ({ plugins, order }: NuxtOptions['postcss']): string[] { const names = Object.keys(plugins) @@ -18,27 +16,23 @@ export async function resolveCSSOptions (nuxt: Nuxt): Promise } css.postcss.plugins = [] - const postcssOptions = nuxt.options.postcss - const cwd = fileURLToPath(new URL('.', import.meta.url)) + const jiti = createJiti(nuxt.options.rootDir, { + interopDefault: true, + alias: nuxt.options.alias, + }) + for (const pluginName of sortPlugins(postcssOptions)) { const pluginOptions = postcssOptions.plugins[pluginName] if (!pluginOptions) { continue } - const path = await tryResolveModule(pluginName, nuxt.options.modulesDir) - - let pluginFn: (opts: Record) => Plugin - // TODO: use jiti v2 - if (path) { - pluginFn = await import(pathToFileURL(path).href).then(interopDefault) - } else { - console.warn(`[nuxt] could not import postcss plugin \`${pluginName}\` with ESM. Please report this as a bug.`) - // fall back to cjs - pluginFn = requireModule(pluginName, { paths: [cwd] }) - } + const path = jiti.esmResolve(pluginName) + const pluginFn = (await jiti.import(path)) as (opts: Record) => Plugin if (typeof pluginFn === 'function') { css.postcss.plugins.push(pluginFn(pluginOptions)) + } else { + console.warn(`[nuxt] could not import postcss plugin \`${pluginName}\`. Please report this as a bug.`) } } diff --git a/packages/webpack/package.json b/packages/webpack/package.json index 78b6a9c2bc..f5dc7c1a9e 100644 --- a/packages/webpack/package.json +++ b/packages/webpack/package.json @@ -40,6 +40,7 @@ "globby": "^14.0.2", "h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e", "hash-sum": "^2.0.0", + "jiti": "^2.0.0-beta.3", "knitwork": "^1.1.0", "lodash-es": "4.17.21", "magic-string": "^0.30.10", diff --git a/packages/webpack/src/utils/postcss.ts b/packages/webpack/src/utils/postcss.ts index 3bafd486a5..9a6a7bf463 100644 --- a/packages/webpack/src/utils/postcss.ts +++ b/packages/webpack/src/utils/postcss.ts @@ -1,9 +1,7 @@ -import { fileURLToPath, pathToFileURL } from 'node:url' import createResolver from 'postcss-import-resolver' -import { interopDefault } from 'mlly' -import { requireModule, tryResolveModule } from '@nuxt/kit' import type { Nuxt, NuxtOptions } from '@nuxt/schema' import { defu } from 'defu' +import { createJiti } from 'jiti' import type { Plugin } from 'postcss' const isPureObject = (obj: unknown): obj is Object => obj !== null && !Array.isArray(obj) && typeof obj === 'object' @@ -38,28 +36,25 @@ export async function getPostcssConfig (nuxt: Nuxt) { sourceMap: nuxt.options.webpack.cssSourceMap, }) + const jiti = createJiti(nuxt.options.rootDir, { + interopDefault: true, + alias: nuxt.options.alias, + }) + // Keep the order of default plugins if (!Array.isArray(postcssOptions.plugins) && isPureObject(postcssOptions.plugins)) { // Map postcss plugins into instances on object mode once - const cwd = fileURLToPath(new URL('.', import.meta.url)) const plugins: Plugin[] = [] for (const pluginName of sortPlugins(postcssOptions)) { const pluginOptions = postcssOptions.plugins[pluginName] if (!pluginOptions) { continue } - const path = await tryResolveModule(pluginName, nuxt.options.modulesDir) - - let pluginFn: (opts: Record) => Plugin - // TODO: use jiti v2 - if (path) { - pluginFn = await import(pathToFileURL(path).href).then(interopDefault) - } else { - console.warn(`[nuxt] could not import postcss plugin \`${pluginName}\` with ESM. Please report this as a bug.`) - // fall back to cjs - pluginFn = requireModule(pluginName, { paths: [cwd] }) - } + const path = jiti.esmResolve(pluginName) + const pluginFn = (await jiti.import(path)) as (opts: Record) => Plugin if (typeof pluginFn === 'function') { plugins.push(pluginFn(pluginOptions)) + } else { + console.warn(`[nuxt] could not import postcss plugin \`${pluginName}\`. Please report this as a bug.`) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 114896b59f..1187adf65c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -670,6 +670,9 @@ importers: h3: specifier: npm:h3-nightly@2.0.0-1718872656.6765a6e version: h3-nightly@2.0.0-1718872656.6765a6e + jiti: + specifier: 2.0.0-beta.3 + version: 2.0.0-beta.3 knitwork: specifier: ^1.1.0 version: 1.1.0 @@ -791,6 +794,9 @@ importers: hash-sum: specifier: ^2.0.0 version: 2.0.0 + jiti: + specifier: 2.0.0-beta.3 + version: 2.0.0-beta.3 knitwork: specifier: ^1.1.0 version: 1.1.0