mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat(nuxt): upgrade nitro + reduce node-specific usage (#22515)
Co-authored-by: Heb <xsh4k3@gmail.com>
This commit is contained in:
parent
7b35a1fe4f
commit
a2f2a4748e
@ -14,7 +14,7 @@ Nuxt automatically scans files inside these directories to register API and serv
|
||||
|
||||
Each file should export a default function defined with `defineEventHandler()` or `eventHandler()` (alias).
|
||||
|
||||
The handler can directly return JSON data, a `Promise` or use `event.node.res.end()` to send a response.
|
||||
The handler can directly return JSON data, a `Promise`, or use `event.node.res.end()` to send a response.
|
||||
|
||||
**Example:** Create the `/api/hello` route with `server/api/hello.ts` file:
|
||||
|
||||
|
@ -12,7 +12,7 @@ Within your pages, components, and plugins you can use `useRequestEvent` to acce
|
||||
const event = useRequestEvent()
|
||||
|
||||
// Get the URL
|
||||
const url = event.node.req.url
|
||||
const url = event.path
|
||||
```
|
||||
|
||||
::alert{icon=👉}
|
||||
|
@ -64,7 +64,7 @@
|
||||
"happy-dom": "10.10.4",
|
||||
"jiti": "1.19.3",
|
||||
"markdownlint-cli": "^0.33.0",
|
||||
"nitropack": "2.5.2",
|
||||
"nitropack": "2.6.0",
|
||||
"nuxi": "workspace:*",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-vitest": "0.10.2",
|
||||
|
@ -44,7 +44,7 @@
|
||||
"@types/lodash-es": "4.17.8",
|
||||
"@types/semver": "7.5.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"nitropack": "2.5.2",
|
||||
"nitropack": "2.6.0",
|
||||
"unbuild": "latest",
|
||||
"vite": "4.4.9",
|
||||
"vitest": "0.33.0",
|
||||
|
@ -41,7 +41,7 @@
|
||||
"listhen": "1.3.0",
|
||||
"mlly": "1.4.0",
|
||||
"mri": "1.2.0",
|
||||
"nitropack": "2.5.2",
|
||||
"nitropack": "2.6.0",
|
||||
"ohash": "1.1.3",
|
||||
"pathe": "1.1.1",
|
||||
"perfect-debounce": "1.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { join, resolve } from 'pathe'
|
||||
import { createApp, eventHandler, lazyEventHandler, toNodeListener } from 'h3'
|
||||
import { createApp, eventHandler, lazyEventHandler, send, toNodeListener } from 'h3'
|
||||
import { listen } from 'listhen'
|
||||
import type { NuxtAnalyzeMeta } from '@nuxt/schema'
|
||||
import { defu } from 'defu'
|
||||
@ -77,7 +77,7 @@ export default defineNuxtCommand({
|
||||
|
||||
const serveFile = (filePath: string) => lazyEventHandler(async () => {
|
||||
const contents = await fsp.readFile(filePath, 'utf-8')
|
||||
return eventHandler((event) => { event.node.res.end(contents) })
|
||||
return eventHandler(event => send(event, contents))
|
||||
})
|
||||
|
||||
console.info('Starting stats server...')
|
||||
|
@ -81,7 +81,7 @@
|
||||
"knitwork": "^1.0.0",
|
||||
"magic-string": "^0.30.2",
|
||||
"mlly": "^1.4.0",
|
||||
"nitropack": "^2.5.2",
|
||||
"nitropack": "^2.6.0",
|
||||
"nuxi": "workspace:../nuxi",
|
||||
"nypm": "^0.3.0",
|
||||
"ofetch": "^1.1.1",
|
||||
|
@ -2,7 +2,7 @@ import type { Ref } from 'vue'
|
||||
import { getCurrentInstance, nextTick, onUnmounted, ref, toRaw, watch } from 'vue'
|
||||
import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
|
||||
import { parse, serialize } from 'cookie-es'
|
||||
import { deleteCookie, getCookie, setCookie } from 'h3'
|
||||
import { deleteCookie, getCookie, getRequestHeader, setCookie } from 'h3'
|
||||
import type { H3Event } from 'h3'
|
||||
import destr from 'destr'
|
||||
import { isEqual } from 'ohash'
|
||||
@ -80,7 +80,7 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
||||
|
||||
function readRawCookies (opts: CookieOptions = {}): Record<string, string> | undefined {
|
||||
if (import.meta.server) {
|
||||
return parse(useRequestEvent()?.node.req.headers.cookie || '', opts)
|
||||
return parse(getRequestHeader(useRequestEvent(), 'cookie') || '', opts)
|
||||
} else if (import.meta.client) {
|
||||
return parse(document.cookie, opts)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { H3Event } from 'h3'
|
||||
import { setResponseStatus as _setResponseStatus } from 'h3'
|
||||
import { setResponseStatus as _setResponseStatus, getRequestHeaders } from 'h3'
|
||||
import type { NuxtApp } from '../nuxt'
|
||||
import { useNuxtApp } from '../nuxt'
|
||||
|
||||
@ -7,7 +7,8 @@ export function useRequestHeaders<K extends string = string> (include: K[]): { [
|
||||
export function useRequestHeaders (): Readonly<Record<string, string>>
|
||||
export function useRequestHeaders (include?: any[]) {
|
||||
if (import.meta.client) { return {} }
|
||||
const headers = useNuxtApp().ssrContext?.event.node.req.headers ?? {}
|
||||
const event = useNuxtApp().ssrContext?.event
|
||||
const headers = event ? getRequestHeaders(event) : {}
|
||||
if (!include) { return headers }
|
||||
return Object.fromEntries(include.map(key => key.toLowerCase()).filter(key => headers[key]).map(key => [key, headers[key]]))
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
console.warn(`[nuxt] Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${spaLoadingTemplate}\`.`)
|
||||
}
|
||||
|
||||
// @ts-expect-error `typescriptBundlerResolution` coming in next nitro version
|
||||
const nitroConfig: NitroConfig = defu(_nitroConfig, {
|
||||
debug: nuxt.options.debug,
|
||||
rootDir: nuxt.options.rootDir,
|
||||
@ -44,9 +43,8 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
dev: nuxt.options.dev,
|
||||
buildDir: nuxt.options.buildDir,
|
||||
experimental: {
|
||||
// @ts-expect-error `typescriptBundlerResolution` coming in next nitro version
|
||||
typescriptBundlerResolution: nuxt.options.experimental.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || _nitroConfig.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler',
|
||||
asyncContext: nuxt.options.experimental.asyncContext
|
||||
asyncContext: nuxt.options.experimental.asyncContext,
|
||||
typescriptBundlerResolution: nuxt.options.experimental.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || _nitroConfig.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler'
|
||||
},
|
||||
imports: {
|
||||
autoImport: nuxt.options.imports.autoImport as boolean,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { joinURL, withQuery } from 'ufo'
|
||||
import type { NitroErrorHandler } from 'nitropack'
|
||||
import type { H3Error } from 'h3'
|
||||
import { getRequestHeaders, setResponseHeader, setResponseStatus } from 'h3'
|
||||
import { getRequestHeaders, send, setResponseHeader, setResponseStatus } from 'h3'
|
||||
import { useNitroApp, useRuntimeConfig } from '#internal/nitro'
|
||||
import { isJsonRequest, normalizeError } from '#internal/nitro/utils'
|
||||
|
||||
@ -11,7 +11,7 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
|
||||
|
||||
// Create an error object
|
||||
const errorObject = {
|
||||
url: event.node.req.url,
|
||||
url: event.path,
|
||||
statusCode,
|
||||
statusMessage,
|
||||
message,
|
||||
@ -41,12 +41,11 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
|
||||
// JSON response
|
||||
if (isJsonRequest(event)) {
|
||||
setResponseHeader(event, 'Content-Type', 'application/json')
|
||||
event.node.res.end(JSON.stringify(errorObject))
|
||||
return
|
||||
return send(event, JSON.stringify(errorObject))
|
||||
}
|
||||
|
||||
// HTML response (via SSR)
|
||||
const isErrorPage = event.node.req.url?.startsWith('/__nuxt_error')
|
||||
const isErrorPage = event.path.startsWith('/__nuxt_error')
|
||||
const res = !isErrorPage
|
||||
? await useNitroApp().localFetch(withQuery(joinURL(useRuntimeConfig().app.baseURL, '/__nuxt_error'), errorObject), {
|
||||
headers: getRequestHeaders(event) as Record<string, string>,
|
||||
@ -67,8 +66,7 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
|
||||
}
|
||||
if (event.handled) { return }
|
||||
setResponseHeader(event, 'Content-Type', 'text/html;charset=UTF-8')
|
||||
event.node.res.end(template(errorObject))
|
||||
return
|
||||
return send(event, template(errorObject))
|
||||
}
|
||||
|
||||
const html = await res.text()
|
||||
@ -79,5 +77,5 @@ export default <NitroErrorHandler> async function errorhandler (error: H3Error,
|
||||
}
|
||||
setResponseStatus(event, res.status && res.status !== 200 ? res.status : undefined, res.statusText)
|
||||
|
||||
event.node.res.end(html)
|
||||
return send(event, html)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
import type { RenderResponse } from 'nitropack'
|
||||
import type { Manifest } from 'vite'
|
||||
import type { H3Event } from 'h3'
|
||||
import { appendResponseHeader, createError, getQuery, readBody, writeEarlyHints } from 'h3'
|
||||
import { appendResponseHeader, createError, getQuery, getResponseStatus, getResponseStatusText, readBody, writeEarlyHints } from 'h3'
|
||||
import devalue from '@nuxt/devalue'
|
||||
import { stringify, uneval } from 'devalue'
|
||||
import destr from 'destr'
|
||||
@ -170,16 +170,16 @@ const islandPropCache = import.meta.prerender ? useStorage('internal:nuxt:preren
|
||||
|
||||
async function getIslandContext (event: H3Event): Promise<NuxtIslandContext> {
|
||||
// TODO: Strict validation for url
|
||||
let url = event.node.req.url || ''
|
||||
if (import.meta.prerender && event.node.req.url && await islandPropCache!.hasItem(event.node.req.url)) {
|
||||
let url = event.path || ''
|
||||
if (import.meta.prerender && event.path && await islandPropCache!.hasItem(event.path)) {
|
||||
// rehydrate props from cache so we can rerender island if cache does not have it any more
|
||||
url = await islandPropCache!.getItem(event.node.req.url) as string
|
||||
url = await islandPropCache!.getItem(event.path) as string
|
||||
}
|
||||
url = url.substring('/__nuxt_island'.length + 1) || ''
|
||||
const [componentName, hashId] = url.split('?')[0].split('_')
|
||||
|
||||
// TODO: Validate context
|
||||
const context = event.node.req.method === 'GET' ? getQuery(event) : await readBody(event)
|
||||
const context = event.method === 'GET' ? getQuery(event) : await readBody(event)
|
||||
|
||||
const ctx: NuxtIslandContext = {
|
||||
url: '/',
|
||||
@ -202,7 +202,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
const nitroApp = useNitroApp()
|
||||
|
||||
// Whether we're rendering an error page
|
||||
const ssrError = event.node.req.url?.startsWith('/__nuxt_error')
|
||||
const ssrError = event.path.startsWith('/__nuxt_error')
|
||||
? getQuery(event) as unknown as Exclude<NuxtPayload['error'], Error>
|
||||
: null
|
||||
|
||||
@ -210,7 +210,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
ssrError.statusCode = parseInt(ssrError.statusCode as any)
|
||||
}
|
||||
|
||||
if (ssrError && event.node.req.socket.readyState !== 'readOnly' /* direct request */) {
|
||||
if (ssrError && !('__unenv__' in event.node.req) /* allow internal fetch */) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Page Not Found: /__nuxt_error'
|
||||
@ -218,21 +218,23 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
}
|
||||
|
||||
// Check for island component rendering
|
||||
const islandContext = (process.env.NUXT_COMPONENT_ISLANDS && event.node.req.url?.startsWith('/__nuxt_island'))
|
||||
const islandContext = (process.env.NUXT_COMPONENT_ISLANDS && event.path.startsWith('/__nuxt_island'))
|
||||
? await getIslandContext(event)
|
||||
: undefined
|
||||
|
||||
if (import.meta.prerender && islandContext && event.node.req.url && await islandCache!.hasItem(event.node.req.url)) {
|
||||
return islandCache!.getItem(event.node.req.url) as Promise<Partial<RenderResponse>>
|
||||
if (import.meta.prerender && islandContext && event.path && await islandCache!.hasItem(event.path)) {
|
||||
return islandCache!.getItem(event.path) as Promise<Partial<RenderResponse>>
|
||||
}
|
||||
|
||||
// Request url
|
||||
let url = ssrError?.url as string || islandContext?.url || event.node.req.url!
|
||||
let url = ssrError?.url as string || islandContext?.url || event.path
|
||||
|
||||
// Whether we are rendering payload route
|
||||
const isRenderingPayload = PAYLOAD_URL_RE.test(url) && !islandContext
|
||||
if (isRenderingPayload) {
|
||||
url = url.substring(0, url.lastIndexOf('/')) || '/'
|
||||
|
||||
event._path = url
|
||||
event.node.req.url = url
|
||||
if (import.meta.prerender && await payloadCache!.hasItem(url)) {
|
||||
return payloadCache!.getItem(url) as Promise<Partial<RenderResponse>>
|
||||
@ -435,8 +437,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
|
||||
const response = {
|
||||
body: JSON.stringify(islandResponse, null, 2),
|
||||
statusCode: event.node.res.statusCode,
|
||||
statusMessage: event.node.res.statusMessage,
|
||||
statusCode: getResponseStatus(event),
|
||||
statusMessage: getResponseStatusText(event),
|
||||
headers: {
|
||||
'content-type': 'application/json;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt'
|
||||
@ -444,7 +446,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
} satisfies RenderResponse
|
||||
if (import.meta.prerender) {
|
||||
await islandCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, response)
|
||||
await islandPropCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, event.node.req.url!)
|
||||
await islandPropCache!.setItem(`/__nuxt_island/${islandContext!.name}_${islandContext!.id}`, event.path)
|
||||
}
|
||||
return response
|
||||
}
|
||||
@ -452,8 +454,8 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
// Construct HTML response
|
||||
const response = {
|
||||
body: renderHTMLDocument(htmlContext),
|
||||
statusCode: event.node.res.statusCode,
|
||||
statusMessage: event.node.res.statusMessage,
|
||||
statusCode: getResponseStatus(event),
|
||||
statusMessage: getResponseStatusText(event),
|
||||
headers: {
|
||||
'content-type': 'text/html;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt'
|
||||
@ -511,8 +513,8 @@ function renderPayloadResponse (ssrContext: NuxtSSRContext) {
|
||||
body: process.env.NUXT_JSON_PAYLOADS
|
||||
? stringify(splitPayload(ssrContext).payload, ssrContext._payloadReducers)
|
||||
: `export default ${devalue(splitPayload(ssrContext).payload)}`,
|
||||
statusCode: ssrContext.event.node.res.statusCode,
|
||||
statusMessage: ssrContext.event.node.res.statusMessage,
|
||||
statusCode: getResponseStatus(ssrContext.event),
|
||||
statusMessage: getResponseStatusText(ssrContext.event),
|
||||
headers: {
|
||||
'content-type': process.env.NUXT_JSON_PAYLOADS ? 'application/json;charset=utf-8' : 'text/javascript;charset=utf-8',
|
||||
'x-powered-by': 'Nuxt'
|
||||
|
@ -37,7 +37,7 @@
|
||||
"esbuild-loader": "4.0.1",
|
||||
"h3": "1.8.0",
|
||||
"ignore": "5.2.4",
|
||||
"nitropack": "2.5.2",
|
||||
"nitropack": "2.6.0",
|
||||
"unbuild": "latest",
|
||||
"unctx": "2.3.1",
|
||||
"vite": "4.4.9",
|
||||
|
@ -163,18 +163,17 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
})
|
||||
|
||||
const viteMiddleware = defineEventHandler(async (event) => {
|
||||
// Workaround: vite devmiddleware modifies req.url
|
||||
const originalURL = event.node.req.url!
|
||||
|
||||
const viteRoutes = viteServer.middlewares.stack.map(m => m.route).filter(r => r.length > 1)
|
||||
if (!originalURL.startsWith(clientConfig.base!) && !viteRoutes.some(route => originalURL.startsWith(route))) {
|
||||
if (!event.path.startsWith(clientConfig.base!) && !viteRoutes.some(route => event.path.startsWith(route))) {
|
||||
// @ts-expect-error _skip_transform is a private property
|
||||
event.node.req._skip_transform = true
|
||||
}
|
||||
|
||||
// Workaround: vite devmiddleware modifies req.url
|
||||
const _originalPath = event.node.req.url
|
||||
await new Promise((resolve, reject) => {
|
||||
viteServer.middlewares.handle(event.node.req, event.node.res, (err: Error) => {
|
||||
event.node.req.url = originalURL
|
||||
event.node.req.url = _originalPath
|
||||
return err ? reject(err) : resolve(null)
|
||||
})
|
||||
})
|
||||
|
@ -141,7 +141,7 @@ function createViteNodeApp (ctx: ViteBuildContext, invalidates: Set<string> = ne
|
||||
}
|
||||
|
||||
return eventHandler(async (event) => {
|
||||
const moduleId = decodeURI(event.node.req.url!).substring(1)
|
||||
const moduleId = decodeURI(event.path).substring(1)
|
||||
if (moduleId === '/') {
|
||||
throw createError({ statusCode: 400 })
|
||||
}
|
||||
|
582
pnpm-lock.yaml
582
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
for (const outputDir of ['.output', '.output-inline']) {
|
||||
it('default client bundle size', async () => {
|
||||
const clientStats = await analyzeSizes('**/*.js', join(rootDir, outputDir, 'public'))
|
||||
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"95.7k"')
|
||||
expect.soft(roundToKilobytes(clientStats.totalBytes)).toMatchInlineSnapshot('"96.4k"')
|
||||
expect(clientStats.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
||||
[
|
||||
"_nuxt/entry.js",
|
||||
@ -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('"64.6k"')
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"305k"')
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2348k"')
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"1822k"')
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -55,33 +55,12 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
"@vue/runtime-dom",
|
||||
"@vue/server-renderer",
|
||||
"@vue/shared",
|
||||
"cookie-es",
|
||||
"debug",
|
||||
"defu",
|
||||
"destr",
|
||||
"devalue",
|
||||
"estree-walker",
|
||||
"h3",
|
||||
"has-flag",
|
||||
"hookable",
|
||||
"http-graceful-shutdown",
|
||||
"iron-webcrypto",
|
||||
"klona",
|
||||
"ms",
|
||||
"node-fetch-native",
|
||||
"ofetch",
|
||||
"ohash",
|
||||
"pathe",
|
||||
"radix3",
|
||||
"scule",
|
||||
"source-map-js",
|
||||
"supports-color",
|
||||
"ufo",
|
||||
"uncrypto",
|
||||
"unctx",
|
||||
"unenv",
|
||||
"unhead",
|
||||
"unstorage",
|
||||
"vue",
|
||||
"vue-bundle-renderer",
|
||||
]
|
||||
@ -92,10 +71,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('"370k"')
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"611k"')
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"613k"')
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"70.9k"')
|
||||
|
||||
const packages = modules.files
|
||||
.filter(m => m.endsWith('package.json'))
|
||||
@ -106,31 +85,9 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
"@unhead/dom",
|
||||
"@unhead/shared",
|
||||
"@unhead/ssr",
|
||||
"cookie-es",
|
||||
"debug",
|
||||
"defu",
|
||||
"destr",
|
||||
"devalue",
|
||||
"h3",
|
||||
"has-flag",
|
||||
"hookable",
|
||||
"http-graceful-shutdown",
|
||||
"iron-webcrypto",
|
||||
"klona",
|
||||
"ms",
|
||||
"node-fetch-native",
|
||||
"ofetch",
|
||||
"ohash",
|
||||
"pathe",
|
||||
"radix3",
|
||||
"scule",
|
||||
"supports-color",
|
||||
"ufo",
|
||||
"uncrypto",
|
||||
"unctx",
|
||||
"unenv",
|
||||
"unhead",
|
||||
"unstorage",
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user