import { dirname, join, normalize, relative, resolve } from 'pathe' import { createDebugger, createHooks } from 'hookable' import ignore from 'ignore' import type { LoadNuxtOptions } from '@nuxt/kit' import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit' import { resolvePath as _resolvePath } from 'mlly' import type { Nuxt, NuxtHooks, NuxtOptions, RuntimeConfig } from 'nuxt/schema' import type { PackageJson } from 'pkg-types' import { readPackageJSON, resolvePackageJSON } from 'pkg-types' import escapeRE from 'escape-string-regexp' import fse from 'fs-extra' import { withTrailingSlash, withoutLeadingSlash } from 'ufo' import defu from 'defu' import { gt, satisfies } from 'semver' import pagesModule from '../pages/module' import metaModule from '../head/module' import componentsModule from '../components/module' import importsModule from '../imports/module' import { distDir, pkgDir } from '../dirs' import { version } from '../../package.json' import { scriptsStubsPreset } from '../imports/presets' 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' import { TreeShakeComposablesPlugin } from './plugins/tree-shake' import { DevOnlyPlugin } from './plugins/dev-only' import { LayerAliasingPlugin } from './plugins/layer-aliasing' import { addModuleTranspiles } from './modules' import { initNitro } from './nitro' import schemaModule from './schema' import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata' import { AsyncContextInjectionPlugin } from './plugins/async-context' import { resolveDeepImportsPlugin } from './plugins/resolve-deep-imports' import { prehydrateTransformPlugin } from './plugins/prehydrate' export function createNuxt (options: NuxtOptions): Nuxt { const hooks = createHooks() const nuxt: Nuxt = { _version: version, options, hooks, callHook: hooks.callHook, addHooks: hooks.addHooks, hook: hooks.hook, ready: () => initNuxt(nuxt), close: () => Promise.resolve(hooks.callHook('close', nuxt)), vfs: {}, apps: {}, } return nuxt } const nightlies = { 'nitropack': 'nitropack-nightly', 'h3': 'h3-nightly', 'nuxt': 'nuxt-nightly', '@nuxt/schema': '@nuxt/schema-nightly', '@nuxt/kit': '@nuxt/kit-nightly', } const keyDependencies = [ '@nuxt/kit', '@nuxt/schema', ] async function initNuxt (nuxt: Nuxt) { // Register user hooks for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) { if (config.hooks) { nuxt.hooks.addHooks(config.hooks) } } // Restart Nuxt when layer directories are added or removed const layersDir = withTrailingSlash(resolve(nuxt.options.rootDir, 'layers')) nuxt.hook('builder:watch', (event, relativePath) => { const path = resolve(nuxt.options.srcDir, relativePath) if (event === 'addDir' || event === 'unlinkDir') { if (path.startsWith(layersDir)) { return nuxt.callHook('restart', { hard: true }) } } }) // Set nuxt instance for useNuxt nuxtCtx.set(nuxt) nuxt.hook('close', () => nuxtCtx.unset()) const coreTypePackages = nuxt.options.typescript.hoist || [] const packageJSON = await readPackageJSON(nuxt.options.rootDir).catch(() => ({}) as PackageJson) const dependencies = new Set([...Object.keys(packageJSON.dependencies || {}), ...Object.keys(packageJSON.devDependencies || {})]) const paths = Object.fromEntries(await Promise.all(coreTypePackages.map(async (pkg) => { // ignore packages that exist in `package.json` as these can be resolved by TypeScript if (dependencies.has(pkg) && !(pkg in nightlies)) { return [] } // deduplicate types for nightly releases if (pkg in nightlies) { const nightly = nightlies[pkg as keyof typeof nightlies] const path = await _resolvePath(nightly, { url: nuxt.options.modulesDir }).then(r => resolvePackageJSON(r)).catch(() => null) if (path) { return [[pkg, [dirname(path)]], [nightly, [dirname(path)]]] } } const path = await _resolvePath(pkg, { url: nuxt.options.modulesDir }).then(r => resolvePackageJSON(r)).catch(() => null) if (path) { return [[pkg, [dirname(path)]]] } return [] })).then(r => r.flat())) // Set nitro resolutions for types that might be obscured with shamefully-hoist=false nuxt.options.nitro.typescript = defu(nuxt.options.nitro.typescript, { tsConfig: { compilerOptions: { paths: { ...paths } } }, }) // Add nuxt types nuxt.hook('prepare:types', (opts) => { opts.references.push({ types: 'nuxt' }) opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/plugins.d.ts') }) // Add vue shim if (nuxt.options.typescript.shim) { opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/vue-shim.d.ts') }) } // Add shims for `#build/*` imports that do not already have matching types opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/build.d.ts') }) // Add module augmentations directly to NuxtConfig opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') }) opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app.config.d.ts') }) // Set Nuxt resolutions for types that might be obscured with shamefully-hoist=false opts.tsConfig.compilerOptions = defu(opts.tsConfig.compilerOptions, { paths: { ...paths } }) for (const layer of nuxt.options._layers) { const declaration = join(layer.cwd, 'index.d.ts') if (fse.existsSync(declaration)) { opts.references.push({ path: declaration }) } } }) // Prompt to install `@nuxt/scripts` if user has configured it // @ts-expect-error scripts types are not present as the module is not installed if (nuxt.options.scripts) { if (!nuxt.options._modules.some(m => m === '@nuxt/scripts' || m === '@nuxt/scripts-nightly')) { await import('../core/features').then(({ installNuxtModule }) => installNuxtModule('@nuxt/scripts')) } } // Add plugin normalization plugin addBuildPlugin(RemovePluginMetadataPlugin(nuxt)) // Add import protection const config = { rootDir: nuxt.options.rootDir, // Exclude top-level resolutions by plugins exclude: [join(nuxt.options.srcDir, 'index.html')], patterns: nuxtImportProtections(nuxt), } addVitePlugin(() => ImportProtectionPlugin.vite(config)) addWebpackPlugin(() => ImportProtectionPlugin.webpack(config)) // add resolver for modules used in virtual files addVitePlugin(() => resolveDeepImportsPlugin(nuxt)) // Add transform for `onPrehydrate` lifecycle hook addBuildPlugin(prehydrateTransformPlugin(nuxt)) if (nuxt.options.experimental.localLayerAliases) { // Add layer aliasing support for ~, ~~, @ and @@ aliases addVitePlugin(() => LayerAliasingPlugin.vite({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, dev: nuxt.options.dev, root: nuxt.options.srcDir, // skip top-level layer (user's project) as the aliases will already be correctly resolved layers: nuxt.options._layers.slice(1), })) addWebpackPlugin(() => LayerAliasingPlugin.webpack({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, dev: nuxt.options.dev, root: nuxt.options.srcDir, // skip top-level layer (user's project) as the aliases will already be correctly resolved layers: nuxt.options._layers.slice(1), transform: true, })) } nuxt.hook('modules:done', async () => { // Add unctx transform const options = { sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, transformerOptions: { ...nuxt.options.optimization.asyncTransforms, helperModule: await tryResolveModule('unctx', nuxt.options.modulesDir) ?? 'unctx', }, } satisfies UnctxTransformPluginOptions addVitePlugin(() => UnctxTransformPlugin.vite(options)) addWebpackPlugin(() => UnctxTransformPlugin.webpack(options)) // Add composable tree-shaking optimisations const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = { sourcemap: !!nuxt.options.sourcemap.server, composables: nuxt.options.optimization.treeShake.composables.server, } if (Object.keys(serverTreeShakeOptions.composables).length) { addVitePlugin(() => TreeShakeComposablesPlugin.vite(serverTreeShakeOptions), { client: false }) addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false }) } const clientTreeShakeOptions: TreeShakeComposablesPluginOptions = { sourcemap: !!nuxt.options.sourcemap.client, composables: nuxt.options.optimization.treeShake.composables.client, } if (Object.keys(clientTreeShakeOptions.composables).length) { addVitePlugin(() => TreeShakeComposablesPlugin.vite(clientTreeShakeOptions), { server: false }) addWebpackPlugin(() => TreeShakeComposablesPlugin.webpack(clientTreeShakeOptions), { server: false }) } }) if (!nuxt.options.dev) { // DevOnly component tree-shaking - build time only addVitePlugin(() => DevOnlyPlugin.vite({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client })) addWebpackPlugin(() => DevOnlyPlugin.webpack({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client })) } if (nuxt.options.dev) { // Add plugin to check if layouts are defined without NuxtLayout being instantiated addPlugin(resolve(nuxt.options.appDir, 'plugins/check-if-layout-used')) } if (nuxt.options.dev && nuxt.options.features.devLogs) { addPlugin(resolve(nuxt.options.appDir, 'plugins/dev-server-logs')) addServerPlugin(resolve(distDir, 'core/runtime/nitro/dev-server-logs')) nuxt.options.nitro = defu(nuxt.options.nitro, { externals: { inline: [/#internal\/dev-server-logs-options/], }, virtual: { '#internal/dev-server-logs-options': () => `export const rootDir = ${JSON.stringify(nuxt.options.rootDir)};`, }, }) } // Transform initial composable call within `