feat(kit,nuxt,schema,vite,webpack): nitropack v3 nightly (#27702)

This commit is contained in:
Daniel Roe 2024-06-26 15:18:05 +02:00 committed by GitHub
parent 5f819ab88e
commit 8f95cac34c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 953 additions and 202 deletions

View File

@ -90,7 +90,7 @@ declare module '#app' {
}
}
declare module 'nitropack' {
declare module 'nitro/types' {
interface NitroRuntimeHooks {
'your-nitro-hook': () => void;
}

View File

@ -583,7 +583,7 @@ export default defineNuxtModule({
interface MyModuleNitroRules {
myModule?: { foo: 'bar' }
}
declare module 'nitropack' {
declare module 'nitro/types' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}

View File

@ -34,6 +34,9 @@
"typecheck:docs": "DOCS_TYPECHECK=true pnpm nuxi prepare && nuxt-content-twoslash verify --content-dir docs"
},
"resolutions": {
"nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
"typescript": "5.5.2",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/ui-templates": "workspace:*",
@ -69,11 +72,11 @@
"eslint-typegen": "0.2.4",
"execa": "9.3.0",
"globby": "14.0.1",
"h3": "1.12.0",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"happy-dom": "14.12.3",
"jiti": "1.21.6",
"markdownlint-cli": "0.41.0",
"nitropack": "2.9.6",
"nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
"nuxi": "3.12.0",
"nuxt": "workspace:*",
"nuxt-content-twoslash": "0.0.10",
@ -95,5 +98,10 @@
"engines": {
"node": "^16.10.0 || >=18.0.0"
},
"version": ""
"version": "",
"pnpm": {
"patchedDependencies": {
"ofetch@1.3.4": "patches/ofetch@1.3.4.patch"
}
}
}

View File

@ -8,6 +8,7 @@ export default defineBuildConfig({
externals: [
'@nuxt/schema',
'nitropack',
'nitro',
'webpack',
'vite',
'h3',

View File

@ -49,7 +49,7 @@
"devDependencies": {
"@types/hash-sum": "1.0.2",
"@types/semver": "7.5.8",
"nitropack": "2.9.6",
"nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
"unbuild": "latest",
"vite": "5.3.1",
"vitest": "1.6.0",

View File

@ -1,4 +1,4 @@
import type { Nitro, NitroDevEventHandler, NitroEventHandler } from 'nitropack'
import type { Nitro, NitroDevEventHandler, NitroEventHandler } from 'nitro/types'
import type { Import } from 'unimport'
import { normalize } from 'pathe'
import { useNuxt } from './context'
@ -12,7 +12,7 @@ function normalizeHandlerMethod (handler: NitroEventHandler) {
// retrieve method from handler file name
const [, method = undefined] = handler.handler.match(/\.(get|head|patch|post|put|delete|connect|options|trace)(\.\w+)*$/) || []
return {
method,
method: method as 'get' | 'head' | 'patch' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | undefined,
...handler,
handler: normalize(handler.handler),
}

View File

@ -1,5 +1,5 @@
import type { NuxtHooks, NuxtMiddleware } from '@nuxt/schema'
import type { NitroRouteConfig } from 'nitropack'
import type { NitroRouteConfig } from 'nitro/types'
import { defu } from 'defu'
import { useNuxt } from './context'
import { logger } from './logger'

View File

@ -80,7 +80,7 @@
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"globby": "^14.0.1",
"h3": "^1.12.0",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hookable": "^5.5.3",
"ignore": "^5.3.1",
"jiti": "^1.21.6",
@ -88,7 +88,7 @@
"knitwork": "^1.1.0",
"magic-string": "^0.30.10",
"mlly": "^1.7.1",
"nitropack": "^2.9.6",
"nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
"nuxi": "^3.12.0",
"nypm": "^0.3.8",
"ofetch": "^1.3.4",

View File

@ -1,5 +1,5 @@
import type { FetchError, FetchOptions } from 'ofetch'
import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitropack'
import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod as _AvailableRouterMethod } from 'nitro/types'
import type { MaybeRef, Ref } from 'vue'
import { computed, reactive, toValue } from 'vue'
import { hash } from 'ohash'

View File

@ -1,7 +1,7 @@
import type { Ref } from 'vue'
import { getCurrentScope, onScopeDispose, ref } from 'vue'
import { injectHead } from '@unhead/vue'
import { useNuxtApp } from '#app'
import { useNuxtApp } from '../nuxt'
export type Politeness = 'assertive' | 'polite' | 'off'

View File

@ -7,7 +7,7 @@ import { getContext } from 'unctx'
import type { SSRContext, createRenderer } from 'vue-bundle-renderer/runtime'
import type { EventHandlerRequest, H3Event } from 'h3'
import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema'
import type { RenderResponse } from 'nitropack'
import type { RenderResponse } from 'nitro/types'
import type { LogObject } from 'consola'
import type { MergeHead, VueHeadClient } from '@unhead/vue'

View File

@ -4,8 +4,8 @@ import { cpus } from 'node:os'
import { join, relative, resolve } from 'pathe'
import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from 'radix3'
import { joinURL, withTrailingSlash } from 'ufo'
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
import type { Nitro, NitroConfig, NitroOptions } from 'nitropack'
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 escapeRE from 'escape-string-regexp'
import { defu } from 'defu'
@ -95,22 +95,18 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
: false,
scanDirs: nuxt.options._layers.map(layer => (layer.config.serverDir || layer.config.srcDir) && resolve(layer.cwd, layer.config.serverDir || resolve(layer.config.srcDir, 'server'))).filter(Boolean),
renderer: resolve(distDir, 'core/runtime/nitro/renderer'),
errorHandler: resolve(distDir, 'core/runtime/nitro/error'),
nodeModulesDirs: nuxt.options.modulesDir,
handlers: nuxt.options.serverHandlers,
devHandlers: [],
baseURL: nuxt.options.app.baseURL,
virtual: {
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config'],
'#internal/nuxt/app-config': () => nuxt.vfs['#build/app.config'].replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, ''),
'#spa-template': async () => `export const template = ${JSON.stringify(await spaLoadingTemplate(nuxt))}`,
},
routeRules: {
'/__nuxt_error': { cache: false },
},
appConfig: nuxt.options.appConfig,
appConfigFiles: nuxt.options._layers.map(
layer => resolve(layer.config.srcDir, 'app.config'),
),
typescript: {
strict: true,
generateTsConfig: true,
@ -213,6 +209,19 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
logLevel: logLevelMapReverse[nuxt.options.logLevel],
} satisfies NitroConfig)
if (nuxt.options.experimental.serverAppConfig && nitroConfig.imports) {
nitroConfig.imports.imports ||= []
nitroConfig.imports.imports.push({
name: 'useAppConfig',
from: resolve(distDir, 'core/runtime/nitro/app-config'),
})
}
// add error handler
if (!nitroConfig.errorHandler && (nuxt.options.dev || !nuxt.options.experimental.noVueServer)) {
nitroConfig.errorHandler = resolve(distDir, 'core/runtime/nitro/error')
}
// Resolve user-provided paths
nitroConfig.srcDir = resolve(nuxt.options.rootDir, nuxt.options.srcDir, nitroConfig.srcDir!)
nitroConfig.ignore = [...(nitroConfig.ignore || []), ...resolveIgnorePatterns(nitroConfig.srcDir), `!${join(nuxt.options.buildDir, 'dist/client', nuxt.options.app.buildAssetsDir, '**/*')}`]
@ -383,7 +392,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
// Init nitro
const nitro = await createNitro(nitroConfig, {
// @ts-expect-error this will be valid in a future version of Nitro
compatibilityDate: nuxt.options.compatibilityDate,
})
@ -463,21 +471,22 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
if (!nuxt.options.dev && nuxt.options.experimental.noVueServer) {
nitro.hooks.hook('rollup:before', (nitro) => {
if (nitro.options.preset === 'nitro-prerender') { return }
if (nitro.options.preset === 'nitro-prerender') {
nitro.options.errorHandler = resolve(distDir, 'core/runtime/nitro/error')
return
}
const nuxtErrorHandler = nitro.options.handlers.findIndex(h => h.route === '/__nuxt_error')
if (nuxtErrorHandler >= 0) {
nitro.options.handlers.splice(nuxtErrorHandler, 1)
}
nitro.options.renderer = undefined
nitro.options.errorHandler = '#internal/nitro/error'
})
}
// Add typed route responses
nuxt.hook('prepare:types', async (opts) => {
if (!nuxt.options.dev) {
await scanHandlers(nitro)
await writeTypes(nitro)
}
// Exclude nitro output dir from typescript

View File

@ -62,6 +62,7 @@ export function createNuxt (options: NuxtOptions): Nuxt {
const nightlies = {
'nitropack': 'nitropack-nightly',
'nitro': 'nitro-nightly',
'h3': 'h3-nightly',
'nuxt': 'nuxt-nightly',
'@nuxt/schema': '@nuxt/schema-nightly',

View File

@ -34,7 +34,7 @@ export const nuxtImportProtections = (nuxt: { options: NuxtOptions }, options: {
])
}
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nuxt\/(config|kit|schema)/, 'nitropack']) {
for (const i of [/(^|node_modules\/)@nuxt\/(kit|test-utils)/, /(^|node_modules\/)nuxi/, /(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?runtime|types)/, /(^|node_modules\/)nuxt\/(config|kit|schema)/]) {
patterns.push([i, 'This module cannot be imported' + (options.isNitro ? ' in server runtime.' : ' in the Vue part of your app.')])
}

View File

@ -0,0 +1,38 @@
import type { H3Event } from 'h3'
import { klona } from 'klona'
// @ts-expect-error virtual file
import _inlineAppConfig from '#internal/nuxt/app-config'
// App config
const _sharedAppConfig = _deepFreeze(klona(_inlineAppConfig))
export function useAppConfig (event?: H3Event) {
// Backwards compatibility with ambient context
if (!event) {
return _sharedAppConfig
}
if (!event.context.nuxt) {
event.context.nuxt = {}
}
// Reuse cached app config from event context
if (event.context.nuxt.appConfig) {
return event.context.nuxt.appConfig
}
// Prepare app config for event context
const appConfig = klona(_inlineAppConfig)
event.context.nuxt.appConfig = appConfig
return appConfig
}
// --- Utils ---
function _deepFreeze (object: Record<string, any>) {
const propNames = Object.getOwnPropertyNames(object)
for (const name of propNames) {
const value = object[name]
if (value && typeof value === 'object') {
_deepFreeze(value)
}
}
return Object.freeze(object)
}

View File

@ -7,7 +7,7 @@ import { withTrailingSlash } from 'ufo'
import { getContext } from 'unctx'
import { isVNode } from 'vue'
import type { NitroApp } from '#internal/nitro/app'
import type { NitroApp } from 'nitro/types'
// @ts-expect-error virtual file
import { rootDir } from '#internal/dev-server-logs-options'

View File

@ -1,10 +1,8 @@
import { joinURL, withQuery } from 'ufo'
import type { NitroErrorHandler } from 'nitropack'
import type { H3Error } from 'h3'
import { getRequestHeaders, send, setResponseHeader, setResponseStatus } from 'h3'
import { useRuntimeConfig } from '#internal/nitro'
import { useNitroApp } from '#internal/nitro/app'
import { isJsonRequest, normalizeError } from '#internal/nitro/utils'
import type { NitroErrorHandler } from 'nitro/types'
import type { H3Error, H3Event } from 'h3'
import { getRequestHeader, getRequestHeaders, send, setResponseHeader, setResponseStatus } from 'h3'
import { useNitroApp, useRuntimeConfig } from 'nitro/runtime'
import type { NuxtPayload } from '#app'
export default <NitroErrorHandler> async function errorhandler (error: H3Error, event) {
@ -86,3 +84,65 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
return send(event, html)
}
/**
* Nitro internal functions extracted from https://github.com/unjs/nitro/blob/main/src/runtime/internal/utils.ts
*/
function isJsonRequest (event: H3Event) {
// If the client specifically requests HTML, then avoid classifying as JSON.
if (hasReqHeader(event, 'accept', 'text/html')) {
return false
}
return (
hasReqHeader(event, 'accept', 'application/json') ||
hasReqHeader(event, 'user-agent', 'curl/') ||
hasReqHeader(event, 'user-agent', 'httpie/') ||
hasReqHeader(event, 'sec-fetch-mode', 'cors') ||
event.path.startsWith('/api/') ||
event.path.endsWith('.json')
)
}
function hasReqHeader (event: H3Event, name: string, includes: string) {
const value = getRequestHeader(event, name)
return (
value && typeof value === 'string' && value.toLowerCase().includes(includes)
)
}
function normalizeError (error: any) {
// temp fix for https://github.com/unjs/nitro/issues/759
// TODO: investigate vercel-edge not using unenv pollyfill
const cwd = typeof process.cwd === 'function' ? process.cwd() : '/'
const stack = ((error.stack as string) || '')
.split('\n')
.splice(1)
.filter(line => line.includes('at '))
.map((line) => {
const text = line
.replace(cwd + '/', './')
.replace('webpack:/', '')
.replace('file://', '')
.trim()
return {
text,
internal:
(line.includes('node_modules') && !line.includes('.cache')) ||
line.includes('internal') ||
line.includes('new Promise'),
}
})
const statusCode = error.statusCode || 500
const statusMessage =
error.statusMessage ?? (statusCode === 404 ? 'Not Found' : '')
const message = error.message || error.toString()
return {
stack,
statusCode,
statusMessage,
message,
}
}

View File

@ -1,5 +1,5 @@
import { joinRelativeURL } from 'ufo'
import { useRuntimeConfig } from '#internal/nitro'
import { useRuntimeConfig } from 'nitro/runtime'
export function baseURL (): string {
// TODO: support passing event to `useRuntimeConfig`

View File

@ -6,7 +6,7 @@ import {
getRequestDependencies,
renderResourceHeaders,
} from 'vue-bundle-renderer/runtime'
import type { RenderResponse } from 'nitropack'
import type { RenderResponse } from 'nitro/types'
import type { Manifest } from 'vite'
import type { H3Event } from 'h3'
import { appendResponseHeader, createError, getQuery, getResponseStatus, getResponseStatusText, readBody, writeEarlyHints } from 'h3'
@ -21,8 +21,7 @@ import type { HeadEntryOptions } from '@unhead/schema'
import type { Link, Script, Style } from '@unhead/vue'
import { createServerHead } from '@unhead/vue'
import { defineRenderHandler, getRouteRules, useRuntimeConfig, useStorage } from '#internal/nitro'
import { useNitroApp } from '#internal/nitro/app'
import { defineRenderHandler, getRouteRules, useNitroApp, useRuntimeConfig, useStorage } from 'nitro/runtime'
// @ts-expect-error virtual file
import unheadPlugins from '#internal/unhead-plugins.mjs'

View File

@ -239,7 +239,28 @@ import type { H3Event } from 'h3'
import type { LogObject } from 'consola'
import type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from 'nuxt/app'
declare module 'nitropack' {
declare module 'nitro/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string
}
interface NitroRuntimeConfig extends RuntimeConfig {}
interface NitroRouteConfig {
ssr?: boolean
experimentalNoScripts?: boolean
}
interface NitroRouteRules {
ssr?: boolean
experimentalNoScripts?: boolean
appMiddleware?: Record<string, boolean>
}
interface NitroRuntimeHooks {
'dev:ssr-logs': (ctx: { logs: LogObject[], path: string }) => void | Promise<void>
'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event }) => void | Promise<void>
'render:island': (islandResponse: NuxtIslandResponse, context: { event: H3Event, islandContext: NuxtIslandContext }) => void | Promise<void>
}
}
declare module 'nitropack/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string
@ -315,17 +336,19 @@ export const appConfigTemplate: NuxtTemplate = {
write: true,
getContents ({ app, nuxt }) {
return `
import { updateAppConfig } from '#app/config'
import { defuFn } from 'defu'
const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
/** client **/
// Vite - webpack is handled directly in #app/config
if (import.meta.hot) {
if (import.meta.dev && !import.meta.nitro && import.meta.hot) {
const { updateAppConfig } = await import('#app/config')
import.meta.hot.accept((newModule) => {
updateAppConfig(newModule.default)
})
}
/** client-end **/
${app.configs.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join('\n')}
@ -339,7 +362,7 @@ export const publicPathTemplate: NuxtTemplate = {
getContents ({ nuxt }) {
return [
'import { joinRelativeURL } from \'ufo\'',
!nuxt.options.dev && 'import { useRuntimeConfig } from \'#internal/nitro\'',
!nuxt.options.dev && 'import { useRuntimeConfig } from \'nitro/runtime\'',
nuxt.options.dev
? `const appConfig = ${JSON.stringify(nuxt.options.app)}`

View File

@ -8,7 +8,7 @@ import { createRoutesContext } from 'unplugin-vue-router'
import { resolveOptions } from 'unplugin-vue-router/options'
import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-vue-router'
import type { NitroRouteConfig } from 'nitropack'
import type { NitroRouteConfig } from 'nitro/types'
import { defu } from 'defu'
import { distDir } from '../dirs'
import { normalizeRoutes, resolvePagesRoutes, resolveRoutePaths } from './utils'
@ -117,7 +117,12 @@ export default defineNuxtModule({
addTypeTemplate({
filename: 'types/middleware.d.ts',
getContents: () => [
'declare module \'nitropack\' {',
'declare module \'nitropack/types\' {',
' interface NitroRouteConfig {',
' appMiddleware?: string | string[] | Record<string, boolean>',
' }',
'}',
'declare module \'nitro/types\' {',
' interface NitroRouteConfig {',
' appMiddleware?: string | string[] | Record<string, boolean>',
' }',
@ -442,7 +447,12 @@ export default defineNuxtModule({
' middleware?: MiddlewareKey | NavigationGuard | Array<MiddlewareKey | NavigationGuard>',
' }',
'}',
'declare module \'nitropack\' {',
'declare module \'nitropack/types\' {',
' interface NitroRouteConfig {',
' appMiddleware?: MiddlewareKey | MiddlewareKey[] | Record<MiddlewareKey, boolean>',
' }',
'}',
'declare module \'nitro/types\' {',
' interface NitroRouteConfig {',
' appMiddleware?: MiddlewareKey | MiddlewareKey[] | Record<MiddlewareKey, boolean>',
' }',

View File

@ -5,7 +5,7 @@ import { walk } from 'estree-walker'
import { transform } from 'esbuild'
import { parse } from 'acorn'
import type { NuxtPage } from '@nuxt/schema'
import type { NitroRouteConfig } from 'nitropack'
import type { NitroRouteConfig } from 'nitro/types'
import { normalize } from 'pathe'
import { extractScriptContent, pathToNitroGlob } from './utils'

View File

@ -2,7 +2,7 @@ import type { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
import { getCurrentInstance } from 'vue'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteRecordRedirectOption } from 'vue-router'
import { useRoute } from 'vue-router'
import type { NitroRouteConfig } from 'nitropack'
import type { NitroRouteConfig } from 'nitro/types'
import { useNuxtApp } from '#app/nuxt'
import type { NuxtError } from '#app'

View File

@ -1,4 +1,4 @@
/// <reference types="nitropack" />
/// <reference types="nitro/types" />
import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
@ -14,7 +14,28 @@ declare global {
}
// Note: Keep in sync with packages/nuxt/src/core/templates.ts
declare module 'nitropack' {
declare module 'nitro/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string
}
interface NitroRuntimeConfig extends RuntimeConfig {}
interface NitroRouteConfig {
ssr?: boolean
experimentalNoScripts?: boolean
}
interface NitroRouteRules {
ssr?: boolean
experimentalNoScripts?: boolean
appMiddleware?: Record<string, boolean>
}
interface NitroRuntimeHooks {
'dev:ssr-logs': (ctx: { logs: LogObject[], path: string }) => void | Promise<void>
'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event }) => void | Promise<void>
'render:island': (islandResponse: NuxtIslandResponse, context: { event: H3Event, islandContext: NuxtIslandContext }) => void | Promise<void>
}
}
declare module 'nitropack/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string

View File

@ -1,4 +1,4 @@
/// <reference types="nitropack" />
/// <reference types="nitro/types" />
import type { DefineNuxtConfig } from 'nuxt/config'
import type { RuntimeConfig, SchemaDefinition } from 'nuxt/schema'
import type { H3Event } from 'h3'
@ -13,7 +13,28 @@ declare global {
}
// Note: Keep in sync with packages/nuxt/src/core/templates.ts
declare module 'nitropack' {
declare module 'nitro/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string
}
interface NitroRuntimeConfig extends RuntimeConfig {}
interface NitroRouteConfig {
ssr?: boolean
experimentalNoScripts?: boolean
}
interface NitroRouteRules {
ssr?: boolean
experimentalNoScripts?: boolean
appMiddleware?: Record<string, boolean>
}
interface NitroRuntimeHooks {
'dev:ssr-logs': (ctx: { logs: LogObject[], path: string }) => void | Promise<void>
'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event }) => void | Promise<void>
'render:island': (islandResponse: NuxtIslandResponse, context: { event: H3Event, islandContext: NuxtIslandContext }) => void | Promise<void>
}
}
declare module 'nitropack/types' {
interface NitroRuntimeConfigApp {
buildAssetsDir: string
cdnURL: string

View File

@ -31,6 +31,7 @@ export default defineBuildConfig({
'vue',
'unctx',
'hookable',
'nitro',
'nitropack',
'webpack',
'webpack-bundle-analyzer',

View File

@ -47,9 +47,9 @@
"@vue/language-core": "2.0.22",
"c12": "1.11.1",
"esbuild-loader": "4.2.0",
"h3": "1.12.0",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"ignore": "5.3.1",
"nitropack": "2.9.6",
"nitro": "npm:nitro-nightly@3.0.0-beta-28648657.9a717203",
"ofetch": "1.3.4",
"unbuild": "latest",
"unctx": "2.3.1",

View File

@ -106,6 +106,12 @@ export default defineUntypedSchema({
*/
externalVue: true,
/**
* Enable accessing `appConfig` from server routes.
*
* @deprecated This option is not recommended.
*/
serverAppConfig: false,
/**
* Emit `app:chunkError` hook when there is an error loading vite/webpack
* chunks.

View File

@ -5,7 +5,7 @@ export default defineUntypedSchema({
/**
* Configuration for Nitro.
* @see https://nitro.unjs.io/config/
* @type {typeof import('nitropack')['NitroConfig']}
* @type {typeof import('nitro/types')['NitroConfig']}
*/
nitro: {
runtimeConfig: {
@ -38,7 +38,7 @@ export default defineUntypedSchema({
* Global route options applied to matching server routes.
* @experimental This is an experimental feature and API may change in the future.
* @see https://nitro.unjs.io/config/#routerules
* @type {typeof import('nitropack')['NitroConfig']['routeRules']}
* @type {typeof import('nitro/types')['NitroConfig']['routeRules']}
*/
routeRules: {},
@ -61,14 +61,14 @@ export default defineUntypedSchema({
* { route: '/path/foo/**:name', handler: '~/server/foohandler.ts' }
* ]
* ```
* @type {typeof import('nitropack')['NitroEventHandler'][]}
* @type {typeof import('nitro/types')['NitroEventHandler'][]}
*/
serverHandlers: [],
/**
* Nitro development-only server handlers.
* @see https://nitro.unjs.io/guide/routing
* @type {typeof import('nitropack')['NitroDevEventHandler'][]}
* @type {typeof import('nitro/types')['NitroDevEventHandler'][]}
*/
devServerHandlers: [],
})

View File

@ -34,7 +34,7 @@ export default defineUntypedSchema({
$resolve: (val) => {
const defaults = [
// Nitro auto-imported/augmented dependencies
'nitropack',
'nitro/types',
'defu',
'h3',
'consola',

View File

@ -3,7 +3,7 @@ import type { ServerOptions as ViteServerOptions, UserConfig as ViteUserConfig }
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
import type { Options as VueJsxPluginOptions } from '@vitejs/plugin-vue-jsx'
import type { SchemaDefinition } from 'untyped'
import type { NitroRuntimeConfig, NitroRuntimeConfigApp } from 'nitropack'
import type { NitroRuntimeConfig, NitroRuntimeConfigApp } from 'nitro/types'
import type { SnakeCase } from 'scule'
import type { ConfigSchema } from '../../schema/config'
import type { Nuxt } from './nuxt'

View File

@ -6,7 +6,7 @@ import type { Manifest } from 'vue-bundle-renderer'
import type { EventHandler } from 'h3'
import type { Import, InlinePreset, Unimport } from 'unimport'
import type { Compiler, Configuration, Stats } from 'webpack'
import type { Nitro, NitroConfig } from 'nitropack'
import type { Nitro, NitroConfig } from 'nitro/types'
import type { Schema, SchemaDefinition } from 'untyped'
import type { RouteLocationRaw } from 'vue-router'
import type { VueCompilerOptions } from '@vue/language-core'

View File

@ -46,7 +46,7 @@
"estree-walker": "^3.0.3",
"externality": "^1.0.2",
"get-port-please": "^3.1.2",
"h3": "^1.12.0",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"knitwork": "^1.1.0",
"magic-string": "^0.30.10",
"mlly": "^1.7.1",

View File

@ -112,7 +112,7 @@ export async function buildClient (ctx: ViteBuildContext) {
...ctx.config.resolve?.alias,
'#internal/nuxt/paths': resolve(ctx.nuxt.options.buildDir, 'paths.mjs'),
'#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/client'),
'#internal/nitro': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs'),
'nitro/runtime': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs'),
},
dedupe: [
'vue',

View File

@ -61,7 +61,7 @@ export async function buildServer (ctx: ViteBuildContext) {
},
ssr: {
external: [
'#internal/nitro', '#internal/nitro/utils',
'nitro/runtime',
],
noExternal: [
...transpile({ isServer: true, isDev: ctx.nuxt.options.dev }),
@ -80,7 +80,7 @@ export async function buildServer (ctx: ViteBuildContext) {
ssr: true,
rollupOptions: {
input: { server: entry },
external: ['#internal/nitro', '#internal/nuxt/paths'],
external: ['nitro/runtime', '#internal/nuxt/paths', '#internal/nuxt/app-config'],
output: {
entryFileNames: '[name].mjs',
format: 'module',
@ -104,8 +104,8 @@ export async function buildServer (ctx: ViteBuildContext) {
} satisfies vite.InlineConfig, ctx.nuxt.options.vite.$server || {}))
if (!ctx.nuxt.options.dev) {
const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir)
.then(r => import(r!)).then(r => r.dependencies ? Object.keys(r.dependencies) : []).catch(() => [])
const nitroDependencies = await tryResolveModule('nitro/runtime/meta', ctx.nuxt.options.modulesDir)
.then(r => import(r!)).then(r => r.runtimeDependencies || []).catch(() => [])
if (Array.isArray(serverConfig.ssr!.external)) {
serverConfig.ssr!.external.push(
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build

View File

@ -37,8 +37,10 @@
"estree-walker": "^3.0.3",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^9.0.2",
"h3": "^1.12.0",
"globby": "^14.0.1",
"h3": "npm:h3-nightly@2.0.0-1718872656.6765a6e",
"hash-sum": "^2.0.0",
"knitwork": "^1.1.0",
"lodash-es": "4.17.21",
"magic-string": "^0.30.10",
"memfs": "^4.9.3",
@ -75,6 +77,7 @@
"@types/pify": "5.0.4",
"@types/webpack-bundle-analyzer": "4.7.0",
"@types/webpack-hot-middleware": "2.25.9",
"rollup": "4.18.0",
"unbuild": "latest",
"vue": "3.4.30"
},

View File

@ -53,9 +53,9 @@ function serverStandalone (ctx: WebpackConfigContext) {
'#',
...ctx.options.build.transpile,
]
const external = ['#internal/nitro']
const external = ['nitro/runtime']
if (!ctx.nuxt.options.dev) {
external.push('#internal/nuxt/paths')
external.push('#internal/nuxt/paths', '#internal/nuxt/app-config')
}
if (!Array.isArray(ctx.config.externals)) { return }

View File

@ -0,0 +1,128 @@
import { pathToFileURL } from 'node:url'
import { globby } from 'globby'
import { genSafeVariableName } from 'knitwork'
import { resolve } from 'pathe'
import type { Plugin } from 'rollup'
const PLUGIN_NAME = 'dynamic-require'
const HELPER_DYNAMIC = `\0${PLUGIN_NAME}.mjs`
const DYNAMIC_REQUIRE_RE = /import\("\.\/" ?\+(.*)\).then/g
interface Options {
dir: string
inline: boolean
ignore: string[]
outDir?: string
prefix?: string
}
interface Chunk {
id: string
src: string
name: string
meta?: {
id?: string
ids?: string[]
moduleIds?: string[]
}
}
interface TemplateContext {
chunks: Chunk[]
}
export function dynamicRequire ({ dir, ignore, inline }: Options): Plugin {
return {
name: PLUGIN_NAME,
transform (code: string, _id: string) {
return {
code: code.replace(
DYNAMIC_REQUIRE_RE,
`import('${HELPER_DYNAMIC}').then(r => r.default || r).then(dynamicRequire => dynamicRequire($1)).then`,
),
map: null,
}
},
resolveId (id: string) {
return id === HELPER_DYNAMIC ? id : null
},
// TODO: Async chunk loading over network!
// renderDynamicImport () {
// return {
// left: 'fetch(', right: ')'
// }
// },
async load (_id: string) {
if (_id !== HELPER_DYNAMIC) {
return null
}
// Scan chunks
let files = []
try {
const wpManifest = resolve(dir, './server.manifest.json')
files = await import(pathToFileURL(wpManifest).href).then(r =>
Object.keys(r.files).filter(file => !ignore.includes(file)),
)
} catch {
files = await globby('**/*.{cjs,mjs,js}', {
cwd: dir,
absolute: false,
ignore,
})
}
const chunks = (
await Promise.all(
files.map(async id => ({
id,
src: resolve(dir, id).replace(/\\/g, '/'),
name: genSafeVariableName(id),
meta: await getWebpackChunkMeta(resolve(dir, id)),
})),
)
).filter(chunk => chunk.meta) as Chunk[]
return inline ? TMPL_INLINE({ chunks }) : TMPL_LAZY({ chunks })
},
}
}
async function getWebpackChunkMeta (src: string) {
const chunk = await import(pathToFileURL(src).href).then(
r => r.default || r || {},
)
const { id, ids, modules } = chunk
if (!id && !ids) {
return null // Not a webpack chunk
}
return {
id,
ids,
moduleIds: Object.keys(modules || {}),
}
}
function TMPL_INLINE ({ chunks }: TemplateContext) {
return `${chunks
.map(i => `import * as ${i.name} from '${i.src}'`)
.join('\n')}
const dynamicChunks = {
${chunks.map(i => ` ['${i.id}']: ${i.name}`).join(',\n')}
};
export default function dynamicRequire(id) {
return Promise.resolve(dynamicChunks[id]);
};`
}
function TMPL_LAZY ({ chunks }: TemplateContext) {
return `
const dynamicChunks = {
${chunks.map(i => ` ['${i.id}']: () => import('${i.src}')`).join(',\n')}
};
export default function dynamicRequire(id) {
return dynamicChunks[id]();
};`
}

View File

@ -125,7 +125,7 @@ function baseAlias (ctx: WebpackConfigContext) {
...ctx.alias,
}
if (ctx.isClient) {
ctx.alias['#internal/nitro'] = resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs')
ctx.alias['nitro/runtime'] = resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs')
}
}

View File

@ -1,6 +1,7 @@
import pify from 'pify'
import webpack from 'webpack'
import type { NodeMiddleware } from 'h3'
import { resolve } from 'pathe'
import { defineEventHandler, fromNodeMiddleware } from 'h3'
import type { MultiWatching } from 'webpack-dev-middleware'
import webpackDevMiddleware from 'webpack-dev-middleware'
@ -9,7 +10,8 @@ import type { Compiler, Stats, Watching } from 'webpack'
import { defu } from 'defu'
import type { NuxtBuilder } from '@nuxt/schema'
import { joinURL } from 'ufo'
import { logger, useNuxt } from '@nuxt/kit'
import { logger, useNitro, useNuxt } from '@nuxt/kit'
import type { InputPluginOption } from 'rollup'
import { composableKeysPlugin } from '../../vite/src/plugins/composable-keys'
import { DynamicBasePlugin } from './plugins/dynamic-base'
@ -18,6 +20,7 @@ import { createMFS } from './utils/mfs'
import { registerVirtualModules } from './virtual-modules'
import { client, server } from './configs'
import { applyPresets, createWebpackConfigContext, getWebpackConfig } from './utils/config'
import { dynamicRequire } from './nitro/plugins/dynamic-require'
// TODO: Support plugins
// const plugins: string[] = []
@ -32,6 +35,26 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
return getWebpackConfig(ctx)
})
/** Inject rollup plugin for Nitro to handle dynamic imports from webpack chunks */
const nitro = useNitro()
const dynamicRequirePlugin = dynamicRequire({
dir: resolve(nuxt.options.buildDir, 'dist/server'),
inline:
nitro.options.node === false || nitro.options.inlineDynamicImports,
ignore: [
'client.manifest.mjs',
'server.js',
'server.cjs',
'server.mjs',
'server.manifest.mjs',
],
})
const prerenderRollupPlugins = nitro.options._config.rollupConfig!.plugins as InputPluginOption[]
const rollupPlugins = nitro.options.rollupConfig!.plugins as InputPluginOption[]
prerenderRollupPlugins.push(dynamicRequirePlugin)
rollupPlugins.push(dynamicRequirePlugin)
await nuxt.callHook('webpack:config', webpackConfigs)
// Initialize shared MFS for dev

View File

@ -0,0 +1,33 @@
diff --git a/dist/node.d.cts b/dist/node.d.cts
index d3a39ff53717d267ff4581af714533ff7229799c..4e3db1f3d6defb7b0c40d11589c0ff6cb8391ad5 100644
--- a/dist/node.d.cts
+++ b/dist/node.d.cts
@@ -1,5 +1,5 @@
import { $ as $Fetch } from './shared/ofetch.8459ad38.cjs';
-export { F as FetchError, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.cjs';
+export { C as CreateFetchOptions, g as Fetch, b as FetchContext, F as FetchError, d as FetchOptions, h as FetchRequest, f as FetchResponse, G as GlobalOptions, I as IFetchError, M as MappedResponseType, R as ResponseMap, e as ResponseType, S as SearchParameters, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
declare function createNodeFetch(): (input: RequestInfo, init?: RequestInit) => any;
declare const fetch: typeof globalThis.fetch;
diff --git a/dist/node.d.mts b/dist/node.d.mts
index 3d8b330375ce60178c05292179ec8bac764ae516..bdcc322bd8554fc7e61d5d9760cb9991560560eb 100644
--- a/dist/node.d.mts
+++ b/dist/node.d.mts
@@ -1,5 +1,5 @@
import { $ as $Fetch } from './shared/ofetch.8459ad38.mjs';
-export { F as FetchError, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
+export { C as CreateFetchOptions, g as Fetch, b as FetchContext, F as FetchError, d as FetchOptions, h as FetchRequest, f as FetchResponse, G as GlobalOptions, I as IFetchError, M as MappedResponseType, R as ResponseMap, e as ResponseType, S as SearchParameters, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
declare function createNodeFetch(): (input: RequestInfo, init?: RequestInit) => any;
declare const fetch: typeof globalThis.fetch;
diff --git a/dist/node.d.ts b/dist/node.d.ts
index 6a5419d1939000a15958b362f44bf49fb1800207..4b319d2c3051e966274268670e243c5f99e2904d 100644
--- a/dist/node.d.ts
+++ b/dist/node.d.ts
@@ -1,5 +1,5 @@
import { $ as $Fetch } from './shared/ofetch.8459ad38.js';
-export { F as FetchError, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.js';
+export { C as CreateFetchOptions, g as Fetch, b as FetchContext, F as FetchError, d as FetchOptions, h as FetchRequest, f as FetchResponse, G as GlobalOptions, I as IFetchError, M as MappedResponseType, R as ResponseMap, e as ResponseType, S as SearchParameters, c as createFetch, a as createFetchError } from './shared/ofetch.8459ad38.mjs';
declare function createNodeFetch(): (input: RequestInfo, init?: RequestInit) => any;
declare const fetch: typeof globalThis.fetch;

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,8 @@ import { consola } from 'consola'
import { determineBumpType, loadWorkspace } from './_utils'
const nightlyPackages = {
nitropack: 'nitropack-nightly',
h3: 'h3-nightly',
// nitro: 'nitro-nightly',
// h3: 'h3-nightly',
nuxi: 'nuxi-nightly',
}

View File

@ -4,13 +4,16 @@ import { describe, expect, it } from 'vitest'
import { joinURL, withQuery } from 'ufo'
import { isCI, isWindows } from 'std-env'
import { join, normalize } from 'pathe'
import { $fetch, createPage, fetch, isDev, setup, startServer, url, useTestContext } from '@nuxt/test-utils/e2e'
import { $fetch as _$fetch, createPage, fetch, isDev, setup, startServer, url, useTestContext } from '@nuxt/test-utils/e2e'
import { $fetchComponent } from '@nuxt/test-utils/experimental'
import { expectNoClientErrors, expectWithPolling, gotoPath, isRenderingJson, parseData, parsePayload, renderPage } from './utils'
import type { NuxtIslandResponse } from '#app'
// TODO: update @nuxt/test-utils
const $fetch = _$fetch as import('nitro/types').$Fetch<unknown, import('nitro/types').NitroFetchRequest>
const isWebpack = process.env.TEST_BUILDER === 'webpack'
const isTestingAppManifest = process.env.TEST_MANIFEST !== 'manifest-off'

View File

@ -32,10 +32,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"210k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1341k"`)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1345k"`)
const packages = modules.files
.filter(m => m.endsWith('package.json'))
@ -55,6 +55,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
"@vue/runtime-dom",
"@vue/server-renderer",
"@vue/shared",
"db0",
"devalue",
"entities",
"estree-walker",
@ -72,10 +73,10 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"532k"`)
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"531k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"76.2k"`)
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"80.3k"`)
const packages = modules.files
.filter(m => m.endsWith('package.json'))
@ -86,6 +87,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
"@unhead/dom",
"@unhead/shared",
"@unhead/ssr",
"db0",
"devalue",
"hookable",
"unhead",

View File

@ -5,7 +5,7 @@ import { createUnplugin } from 'unplugin'
import { withoutLeadingSlash } from 'ufo'
// (defined in nuxt/src/core/nitro.ts)
declare module 'nitropack' {
declare module 'nitro/types' {
interface NitroRouteConfig {
ssr?: boolean
}
@ -242,6 +242,7 @@ export default defineNuxtConfig({
inlineStyles: id => !!id && !id.includes('assets.vue'),
},
experimental: {
serverAppConfig: true,
typedPages: true,
clientFallback: true,
restoreState: true,

View File

@ -3,10 +3,13 @@ import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { isWindows } from 'std-env'
import { join } from 'pathe'
import { $fetch, fetch, setup } from '@nuxt/test-utils/e2e'
import { $fetch as _$fetch, fetch, setup } from '@nuxt/test-utils/e2e'
import { expectWithPolling, renderPage } from './utils'
// TODO: update @nuxt/test-utils
const $fetch = _$fetch as import('nitro/types').$Fetch<unknown, import('nitro/types').NitroFetchRequest>
const isWebpack = process.env.TEST_BUILDER === 'webpack'
// TODO: fix HMR on Windows

View File

@ -31,15 +31,6 @@
],
"#app/*": [
"./packages/nuxt/src/app/*"
],
"#internal/nitro": [
"./node_modules/nitropack/dist/runtime"
],
"#internal/nitro/app": [
"./node_modules/nitropack/dist/runtime/app"
],
"#internal/nitro/utils": [
"./node_modules/nitropack/dist/runtime/utils"
]
}
},