import { dirname, join, normalize, relative, resolve } from 'pathe' import { createDebugger, createHooks } from 'hookable' import type { LoadNuxtOptions } from '@nuxt/kit' import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit' import { resolvePath as _resolvePath } from 'mlly' import type { Nuxt, NuxtHooks, NuxtOptions } 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 { withoutLeadingSlash } from 'ufo' /* eslint-disable import/no-restricted-paths */ import defu from 'defu' import pagesModule from '../pages/module' import metaModule from '../head/module' import componentsModule from '../components/module' import importsModule from '../imports/module' /* eslint-enable */ import { distDir, pkgDir } from '../dirs' import { version } from '../../package.json' 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' 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' } 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) } } // 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 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 }) } } }) // 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)) 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.client')) 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 `