feat!: migrate to nitropack (#3956)

Co-authored-by: Daniel Roe <daniel@roe.dev>
This commit is contained in:
pooya parsa 2022-04-07 13:28:04 +02:00 committed by GitHub
parent 7d1ff39077
commit 11626eea4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 752 additions and 4372 deletions

View File

@ -95,10 +95,10 @@ export default defineNuxtPlugin((nuxtApp) => {
### API routes ### API routes
Within the API routes, you can access runtime config by directly importing from virtual `#config`. Within the API routes, you can access runtime config by directly importing from virtual `#nitro`.
```ts ```ts
import config from '#config' import { useRuntimeConfig } from '#nitro'
export default async () => { export default async () => {
const result = await $fetch('https://my.api.com/test', { const result = await $fetch('https://my.api.com/test', {

View File

@ -20,7 +20,7 @@ How to deploy Nuxt to Azure Static Web Apps or Azure Functions.
```ts [nuxt.config.js|ts] ```ts [nuxt.config.js|ts]
export default { export default {
nitro: { nitro: {
preset: 'azure_functions' preset: 'azure-functions'
} }
} }
``` ```

View File

@ -21,7 +21,7 @@ Make sure another preset isn't set in `nuxt.config`.
export default { export default {
nitro: { nitro: {
// this is the default preset so you can also just omit it entirely // this is the default preset so you can also just omit it entirely
// preset: 'server' // preset: 'node-server'
} }
} }
``` ```

View File

@ -33,7 +33,7 @@ export default {
You can also define a preset in a separate file (or publish as a separate npm package). You can also define a preset in a separate file (or publish as a separate npm package).
```ts [my-preset/index.ts] ```ts [my-preset/index.ts]
import type { NitroPreset } from '@nuxt/nitro' import type { NitroPreset } from 'nitropack'
const myPreset: NitroPreset = { const myPreset: NitroPreset = {
// Your custom configuration // Your custom configuration

View File

@ -13,7 +13,7 @@ You can use the [Nuxt config](/guide/directory-structure/nuxt.config) to explici
```ts [nuxt.config.js|ts] ```ts [nuxt.config.js|ts]
export default { export default {
nitro: { nitro: {
preset: 'lambda' preset: 'aws-lambda'
} }
} }
``` ```

View File

@ -20,7 +20,7 @@ You can use the [Nuxt config](/guide/directory-structure/nuxt.config) to explici
```js [nuxt.config.js|ts] ```js [nuxt.config.js|ts]
export default { export default {
nitro: { nitro: {
preset: 'server' preset: 'node-server'
} }
} }
``` ```
@ -61,7 +61,7 @@ You can enable the `nitro.timing` option to have the logs about the chunk loadin
```js [nuxt.config.js|ts] ```js [nuxt.config.js|ts]
export default { export default {
nitro: { nitro: {
preset: 'server', preset: 'node-server',
timing: true timing: true
} }
} }

View File

@ -24,7 +24,7 @@ You can use the [Nuxt config](/guide/directory-structure/nuxt.config) to explici
```js [nuxt.config.js|ts] ```js [nuxt.config.js|ts]
export default { export default {
nitro: { nitro: {
preset: 'worker' 'browser-worker'
} }
} }
``` ```

View File

@ -7,7 +7,7 @@ head.titleTemplate: ''
If you wish to reference environment variables within your Nuxt 3 app, you will need to use runtime config. If you wish to reference environment variables within your Nuxt 3 app, you will need to use runtime config.
When referencing these variables within your components, you will have to use the `useRuntimeConfig` composable in your setup method (or Nuxt plugin). In the `server/` portion of your app, you can import directly from `#config`. When referencing these variables within your components, you will have to use the `useRuntimeConfig` composable in your setup method (or Nuxt plugin). In the `server/` portion of your app, you can import `useRuntimeConfig` directly from `#nitro`.
[Read more about runtime config](/guide/features/runtime-config). [Read more about runtime config](/guide/features/runtime-config).
@ -41,7 +41,7 @@ export default defineNuxtConfig({
``` ```
```ts [server/api/hello.ts] ```ts [server/api/hello.ts]
import config from '#config' import { useRuntimeConfig } from '#nitro';
export default (req, res) => { export default (req, res) => {
// you can now access config.BASE_URL // you can now access config.BASE_URL

View File

@ -22,10 +22,10 @@
"dev:build": "yarn run nuxi build playground", "dev:build": "yarn run nuxi build playground",
"release": "yarn && yarn lint && FORCE_COLOR=1 lerna publish -m \"chore: release\" && yarn stub", "release": "yarn && yarn lint && FORCE_COLOR=1 lerna publish -m \"chore: release\" && yarn stub",
"stub": "lerna run prepack -- --stub", "stub": "lerna run prepack -- --stub",
"test:fixtures": "yarn nuxi prepare test/fixtures/basic && JITI_ESM_RESOLVE=1 vitest --dir test", "test:fixtures": "yarn nuxi prepare test/fixtures/basic && JITI_ESM_RESOLVE=1 vitest run --dir test",
"test:fixtures:webpack": "TEST_WITH_WEBPACK=1 yarn test:fixtures", "test:fixtures:webpack": "TEST_WITH_WEBPACK=1 yarn test:fixtures",
"test:types": "yarn run nuxi prepare test/fixtures/basic && cd test/fixtures/basic && npx vue-tsc --noEmit", "test:types": "yarn run nuxi prepare test/fixtures/basic && cd test/fixtures/basic && npx vue-tsc --noEmit",
"test:unit": "JITI_ESM_RESOLVE=1 yarn vitest --dir packages", "test:unit": "JITI_ESM_RESOLVE=1 yarn vitest run --dir packages",
"version": "yarn && git add yarn.lock" "version": "yarn && git add yarn.lock"
}, },
"resolutions": { "resolutions": {

View File

@ -22,7 +22,6 @@
"@babel/plugin-proposal-optional-chaining": "^7.16.7", "@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@babel/plugin-transform-typescript": "^7.16.8", "@babel/plugin-transform-typescript": "^7.16.8",
"@nuxt/kit": "3.0.0", "@nuxt/kit": "3.0.0",
"@nuxt/nitro": "3.0.0",
"@nuxt/postcss8": "^1.1.3", "@nuxt/postcss8": "^1.1.3",
"@nuxt/schema": "3.0.0", "@nuxt/schema": "3.0.0",
"@vitejs/plugin-legacy": "^1.8.0", "@vitejs/plugin-legacy": "^1.8.0",
@ -38,12 +37,13 @@
"externality": "^0.2.1", "externality": "^0.2.1",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",
"globby": "^13.1.1", "globby": "^13.1.1",
"h3": "^0.4.2", "h3": "^0.7.1",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"knitwork": "^0.1.1", "knitwork": "^0.1.1",
"magic-string": "^0.26.1", "magic-string": "^0.26.1",
"mlly": "^0.5.1", "mlly": "^0.5.1",
"murmurhash-es": "^0.1.1", "murmurhash-es": "^0.1.1",
"nitropack": "^0.1.0",
"node-fetch": "^3.2.3", "node-fetch": "^3.2.3",
"nuxi": "3.0.0", "nuxi": "3.0.0",
"ohash": "^0.1.0", "ohash": "^0.1.0",
@ -63,6 +63,7 @@
"untyped": "^0.4.4", "untyped": "^0.4.4",
"vite": "^2.9.1", "vite": "^2.9.1",
"vite-plugin-vue2": "^1.9.3", "vite-plugin-vue2": "^1.9.3",
"vue-bundle-renderer": "^0.3.5",
"vue-template-compiler": "^2.6.14" "vue-template-compiler": "^2.6.14"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,4 +1,4 @@
import { useNuxt, addTemplate, resolveAlias, addWebpackPlugin, addVitePlugin } from '@nuxt/kit' import { useNuxt, addTemplate, resolveAlias, addWebpackPlugin, addVitePlugin, addPlugin } from '@nuxt/kit'
import { NuxtModule } from '@nuxt/schema' import { NuxtModule } from '@nuxt/schema'
import { resolve } from 'pathe' import { resolve } from 'pathe'
import { componentsTypeTemplate } from '../../nuxt3/src/components/templates' import { componentsTypeTemplate } from '../../nuxt3/src/components/templates'
@ -94,4 +94,9 @@ export function setupAppBridge (_options: any) {
}) })
} }
}) })
addPlugin({
src: resolve(distDir, 'runtime/error.plugin.server.mjs'),
mode: 'server'
})
} }

View File

@ -49,7 +49,9 @@ export default defineNuxtModule({
}] }]
if (opts.nitro) { if (opts.nitro) {
await setupNitroBridge() nuxt.hook('modules:done', async () => {
await setupNitroBridge()
})
} }
if (opts.app) { if (opts.app) {
await setupAppBridge(opts.app) await setupAppBridge(opts.app)

View File

@ -1,19 +1,23 @@
import { promises as fsp } from 'fs' import { promises as fsp, existsSync } from 'fs'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import fse from 'fs-extra' import fsExtra from 'fs-extra'
import { addPluginTemplate, useNuxt } from '@nuxt/kit' import { addPluginTemplate, resolvePath, useNuxt } from '@nuxt/kit'
import { joinURL, stringifyQuery, withoutTrailingSlash } from 'ufo' import { joinURL, stringifyQuery, withoutTrailingSlash } from 'ufo'
import { resolve, join } from 'pathe' import { resolve, join } from 'pathe'
import { build, generate, prepare, getNitroContext, NitroContext, createDevServer, wpfs, resolveMiddleware, scanMiddleware, writeTypes } from '@nuxt/nitro' import { createNitro, createDevServer, build, writeTypes, prepare, copyPublicAssets, prerender } from 'nitropack'
import { dynamicEventHandler, toEventHandler } from 'h3'
import type { Nitro, NitroEventHandler, NitroDevEventHandler, NitroConfig } from 'nitropack'
import { Nuxt } from '@nuxt/schema'
import defu from 'defu'
import { AsyncLoadingPlugin } from './async-loading' import { AsyncLoadingPlugin } from './async-loading'
import { distDir } from './dirs' import { distDir } from './dirs'
import { isDirectory, readDirRecursively } from './vite/utils/fs' import { isDirectory, readDirRecursively } from './vite/utils/fs'
export function setupNitroBridge () { export async function setupNitroBridge () {
const nuxt = useNuxt() const nuxt = useNuxt()
// Ensure we're not just building with 'static' target // Ensure we're not just building with 'static' target
if (!nuxt.options.dev && nuxt.options.target === 'static' && !nuxt.options._prepare && !nuxt.options._export && !nuxt.options._legacyGenerate) { if (!nuxt.options.dev && nuxt.options.target === 'static' && !nuxt.options._prepare && !(nuxt.options as any)._export && !nuxt.options._legacyGenerate) {
throw new Error('[nitro] Please use `nuxt generate` for static target') throw new Error('[nitro] Please use `nuxt generate` for static target')
} }
@ -21,6 +25,7 @@ export function setupNitroBridge () {
nuxt.options.app.buildAssetsDir = nuxt.options.app.buildAssetsDir || nuxt.options.app.assetsPath nuxt.options.app.buildAssetsDir = nuxt.options.app.buildAssetsDir || nuxt.options.app.assetsPath
nuxt.options.app.assetsPath = nuxt.options.app.buildAssetsDir nuxt.options.app.assetsPath = nuxt.options.app.buildAssetsDir
nuxt.options.app.baseURL = nuxt.options.app.baseURL || (nuxt.options.app as any).basePath nuxt.options.app.baseURL = nuxt.options.app.baseURL || (nuxt.options.app as any).basePath
nuxt.options.app.cdnURL = nuxt.options.app.cdnURL || ''
// Nitro expects app config on `config.app` rather than `config._app` // Nitro expects app config on `config.app` rather than `config._app`
nuxt.options.publicRuntimeConfig.app = nuxt.options.publicRuntimeConfig.app || {} nuxt.options.publicRuntimeConfig.app = nuxt.options.publicRuntimeConfig.app || {}
Object.assign(nuxt.options.publicRuntimeConfig.app, nuxt.options.publicRuntimeConfig._app) Object.assign(nuxt.options.publicRuntimeConfig.app, nuxt.options.publicRuntimeConfig._app)
@ -40,28 +45,89 @@ export function setupNitroBridge () {
} }
} }
// Create contexts // Resolve config
const nitroOptions = (nuxt.options as any).nitro || {} const _nitroConfig = (nuxt.options as any).nitro || {} as NitroConfig
const nitroContext = getNitroContext(nuxt.options, nitroOptions) const nitroConfig: NitroConfig = defu(_nitroConfig, <NitroConfig>{
const nitroDevContext = getNitroContext(nuxt.options, { ...nitroOptions, preset: 'dev' }) rootDir: resolve(nuxt.options.rootDir),
srcDir: resolve(nuxt.options.srcDir, 'server'),
dev: nuxt.options.dev,
preset: nuxt.options.dev ? 'nitro-dev' : undefined,
buildDir: resolve(nuxt.options.buildDir),
scanDirs: nuxt.options._layers.map(layer => join(layer.config.srcDir, 'server')),
renderer: resolve(distDir, 'runtime/nitro/renderer'),
nodeModulesDirs: nuxt.options.modulesDir,
handlers: [],
devHandlers: [],
runtimeConfig: {
// Private
...nuxt.options.publicRuntimeConfig,
...nuxt.options.privateRuntimeConfig,
// Public
public: nuxt.options.publicRuntimeConfig,
// Nitro
nitro: {
envPrefix: 'NUXT_'
}
},
typescript: {
generateTsConfig: false
},
publicAssets: [
{
baseURL: nuxt.options.app.buildAssetsDir,
dir: resolve(nuxt.options.buildDir, 'dist/client')
},
...nuxt.options._layers
.map(layer => join(layer.config.srcDir, 'public'))
.filter(dir => existsSync(dir))
.map(dir => ({ dir }))
],
prerender: {
crawlLinks: nuxt.options.generate.crawler,
routes: nuxt.options.generate.routes
},
output: {
dir: nuxt.options.dev ? join(nuxt.options.buildDir, 'nitro') : resolve(nuxt.options.rootDir, '.output')
},
externals: {
inline: nuxt.options.dev ? [] : [nuxt.options.buildDir]
},
alias: {
// Vue 2 mocks
encoding: 'unenv/runtime/mock/proxy',
he: 'unenv/runtime/mock/proxy',
resolve: 'unenv/runtime/mock/proxy',
'source-map': 'unenv/runtime/mock/proxy',
'lodash.template': 'unenv/runtime/mock/proxy',
'serialize-javascript': 'unenv/runtime/mock/proxy',
// Normalize Nuxt directories // Renderer
for (const context of [nitroContext, nitroDevContext]) { '#vue-renderer': resolve(distDir, 'runtime/nitro/vue2'),
context._nuxt.rootDir = resolve(context._nuxt.rootDir) '#vue2-server-renderer': 'vue-server-renderer/' + (nuxt.options.dev ? 'build.dev.js' : 'build.prod.js'),
context._nuxt.srcDir = resolve(context._nuxt.srcDir)
context._nuxt.buildDir = resolve(context._nuxt.buildDir) // Error renderer
context._nuxt.generateDir = resolve(context._nuxt.generateDir) '#nitro/error': resolve(distDir, 'runtime/nitro/error'),
}
// Paths
'#paths': resolve(distDir, 'runtime/nitro/paths')
}
})
// Extend nitro config with hook
await nuxt.callHook('nitro:config', nitroConfig)
// Initiate nitro
const nitro = await createNitro(nitroConfig)
// Expose nitro to modules
await nuxt.callHook('nitro:init', nitro)
// Shared vfs storage
nitro.vfs = nuxt.vfs = nitro.vfs || nuxt.vfs || {}
// Connect hooks // Connect hooks
nuxt.addHooks(nitroContext.nuxtHooks) nuxt.hook('close', () => nitro.hooks.callHook('close'))
nuxt.hook('close', () => nitroContext._internal.hooks.callHook('close')) nitro.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
nitroContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
nitroContext._internal.hooks.hook('nitro:generate', ctx => nuxt.callHook('nitro:generate', ctx))
nuxt.addHooks(nitroDevContext.nuxtHooks)
nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close'))
nitroDevContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
// Use custom document template if provided // Use custom document template if provided
if (nuxt.options.appTemplatePath) { if (nuxt.options.appTemplatePath) {
@ -82,7 +148,7 @@ export function setupNitroBridge () {
publicFiles = readDirRecursively(publicDir).map(r => r.replace(publicDir, '')) publicFiles = readDirRecursively(publicDir).map(r => r.replace(publicDir, ''))
for (const file of publicFiles) { for (const file of publicFiles) {
try { try {
fse.rmSync(join(clientDist, file)) fsExtra.rmSync(join(clientDist, file))
} catch {} } catch {}
} }
} }
@ -93,26 +159,17 @@ export function setupNitroBridge () {
const nestedAssetsPath = withoutTrailingSlash(join(clientDist, nuxt.options.app.buildAssetsDir)) const nestedAssetsPath = withoutTrailingSlash(join(clientDist, nuxt.options.app.buildAssetsDir))
if (await isDirectory(nestedAssetsPath)) { if (await isDirectory(nestedAssetsPath)) {
await fse.copy(nestedAssetsPath, clientDist, { recursive: true }) await fsExtra.copy(nestedAssetsPath, clientDist, { recursive: true })
await fse.remove(nestedAssetsPath) await fsExtra.remove(nestedAssetsPath)
} }
} }
} }
nuxt.hook('nitro:generate', updateViteBase) nuxt.hook('nitro:generate', updateViteBase)
nuxt.hook('generate:before', updateViteBase) nuxt.hook('generate:before', updateViteBase)
// Expose process.env.NITRO_PRESET
nuxt.options.env.NITRO_PRESET = nitroContext.preset
// .ts is supported for serverMiddleware // .ts is supported for serverMiddleware
nuxt.options.extensions.push('ts') nuxt.options.extensions.push('ts')
// Replace nuxt server
if (nuxt.server) {
nuxt.server.__closed = true
nuxt.server = createNuxt2DevServer(nitroDevContext)
}
// Disable server sourceMap, esbuild will generate for it. // Disable server sourceMap, esbuild will generate for it.
nuxt.hook('webpack:config', (webpackConfigs) => { nuxt.hook('webpack:config', (webpackConfigs) => {
const serverConfig = webpackConfigs.find(config => config.name === 'server') const serverConfig = webpackConfigs.find(config => config.name === 'server')
@ -134,8 +191,8 @@ export function setupNitroBridge () {
// Nitro client plugin // Nitro client plugin
addPluginTemplate({ addPluginTemplate({
filename: 'nitro.client.mjs', filename: 'nitro-bridge.client.mjs',
src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.mjs') src: resolve(distDir, 'runtime/nitro-bridge.client.mjs')
}) })
// Nitro server plugin (for vue-meta) // Nitro server plugin (for vue-meta)
@ -167,21 +224,27 @@ export function setupNitroBridge () {
} }
}) })
// Wait for all modules to be ready // Setup handlers
nuxt.hook('modules:done', async () => { const devMidlewareHandler = dynamicEventHandler()
// Extend nitro with modules nitro.options.devHandlers.unshift({ handler: devMidlewareHandler })
await nuxt.callHook('nitro:context', nitroContext) const { handlers, devHandlers } = await resolveHandlers(nuxt)
await nuxt.callHook('nitro:context', nitroDevContext) nitro.options.handlers.push(...handlers)
nitro.options.devHandlers.push(...devHandlers)
// Resolve middleware nitro.options.handlers.unshift({
const { middleware, legacyMiddleware } = await resolveMiddleware(nuxt) route: '/_nitro',
if (nuxt.server) { lazy: true,
nuxt.server.setLegacyMiddleware(legacyMiddleware) handler: resolve(distDir, 'runtime/nitro/renderer')
}
nitroContext.middleware.push(...middleware)
nitroDevContext.middleware.push(...middleware)
}) })
// Create dev server
if (nuxt.server) {
nuxt.server.__closed = true
nuxt.server = createNuxt2DevServer(nitro)
nuxt.hook('build:resources', () => {
nuxt.server.reload()
})
}
// Add typed route responses // Add typed route responses
nuxt.hook('prepare:types', (opts) => { nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/nitro.d.ts') }) opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/nitro.d.ts') })
@ -189,58 +252,70 @@ export function setupNitroBridge () {
// nuxt prepare // nuxt prepare
nuxt.hook('build:done', async () => { nuxt.hook('build:done', async () => {
nitroDevContext.scannedMiddleware = await scanMiddleware(nitroDevContext._nuxt.serverDir) await writeTypes(nitro)
await writeTypes(nitroDevContext)
}) })
// nuxt build/dev // nuxt build/dev
// @ts-ignore // @ts-ignore
nuxt.options.build._minifyServer = false nuxt.options.build._minifyServer = false
nuxt.options.build.standalone = false nuxt.options.build.standalone = false
const waitUntilCompile = new Promise<void>(resolve => nitro.hooks.hook('nitro:compiled', () => resolve()))
nuxt.hook('build:done', async () => { nuxt.hook('build:done', async () => {
if (nuxt.options._prepare) { return } if (nuxt.options._prepare) { return }
if (nuxt.options.dev) { if (nuxt.options.dev) {
await build(nitroDevContext) await build(nitro)
} else if (!nitroContext._nuxt.isStatic) { await waitUntilCompile
await prepare(nitroContext) // nitro.hooks.callHook('nitro:dev:reload')
await generate(nitroContext) } else {
await build(nitroContext) await prepare(nitro)
await copyPublicAssets(nitro)
if (nuxt.options.target === 'static') {
await prerender(nitro)
}
await build(nitro)
if (nuxt.options._generate) {
await prerender(nitro)
}
} }
}) })
// nuxt dev // nuxt dev
if (nuxt.options.dev) { if (nuxt.options.dev) {
nitroDevContext._internal.hooks.hook('nitro:compiled', () => { nuxt.server.watch() }) nuxt.hook('build:compile', ({ compiler }) => {
nuxt.hook('build:compile', ({ compiler }) => { compiler.outputFileSystem = wpfs }) compiler.outputFileSystem = { ...fsExtra, join } as any
nuxt.hook('server:devMiddleware', (m) => { nuxt.server.setDevMiddleware(m) }) })
nuxt.hook('server:devMiddleware', (m) => { devMidlewareHandler.set(toEventHandler(m)) })
} }
// nuxt generate // nuxt generate
nuxt.options.generate.dir = nitroContext.output.publicDir nuxt.options.generate.dir = nitro.options.output.publicDir
nuxt.options.generate.manifest = false nuxt.options.generate.manifest = false
nuxt.hook('generate:cache:ignore', (ignore: string[]) => { nuxt.hook('generate:cache:ignore', (ignore: string[]) => {
ignore.push(nitroContext.output.dir) ignore.push(nitro.options.output.dir)
ignore.push(nitroContext.output.serverDir) ignore.push(nitro.options.output.serverDir)
if (nitroContext.output.publicDir) { if (nitro.options.output.publicDir) {
ignore.push(nitroContext.output.publicDir) ignore.push(nitro.options.output.publicDir)
} }
ignore.push(...nitroContext.ignore)
}) })
nuxt.hook('generate:before', async () => { nuxt.hook('generate:before', async () => {
await prepare(nitroContext) console.log('generate:before')
await prepare(nitro)
}) })
nuxt.hook('generate:extendRoutes', async () => { nuxt.hook('generate:extendRoutes', async () => {
await build(nitroDevContext) console.log('generate:extendRoutes')
await build(nitro)
await nuxt.server.reload() await nuxt.server.reload()
}) })
nuxt.hook('generate:done', async () => { nuxt.hook('generate:done', async () => {
console.log('generate:done')
await nuxt.server.close() await nuxt.server.close()
await build(nitroContext) await build(nitro)
}) })
} }
function createNuxt2DevServer (nitroContext: NitroContext) { function createNuxt2DevServer (nitro: Nitro) {
const server = createDevServer(nitroContext) const server = createDevServer(nitro)
const listeners = [] const listeners = []
async function listen (port) { async function listen (port) {
@ -277,3 +352,30 @@ function createNuxt2DevServer (nitroContext: NitroContext) {
ready () { } ready () { }
} }
} }
async function resolveHandlers (nuxt: Nuxt) {
const handlers: NitroEventHandler[] = []
const devHandlers: NitroDevEventHandler[] = []
for (let m of nuxt.options.serverMiddleware) {
if (typeof m === 'string' || typeof m === 'function' /* legacy middleware */) { m = { handler: m } }
const route = m.path || m.route || '/'
const handler = m.handler || m.handle
if (typeof handler !== 'string' || typeof route !== 'string') {
devHandlers.push({ route, handler })
} else {
delete m.handler
delete m.path
handlers.push({
...m,
route,
handler: await resolvePath(handler)
})
}
}
return {
handlers,
devHandlers
}
}

View File

@ -0,0 +1,5 @@
export default (ctx) => {
if (ctx.ssrContext.error) {
ctx.error(ctx.ssrContext.error)
}
}

View File

@ -1 +1 @@
../../../nuxt3/src/head/runtime/ ../../../nuxt3/src/head/runtime

View File

@ -0,0 +1 @@
../../../nuxt3/src/core/runtime/nitro

View File

@ -11,7 +11,7 @@ import { ViteBuildContext, ViteOptions } from './types'
export async function buildClient (ctx: ViteBuildContext) { export async function buildClient (ctx: ViteBuildContext) {
const alias = { const alias = {
'#_config': resolve(ctx.nuxt.options.buildDir, 'config.client.mjs') '#nitro': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs')
} }
for (const p of ctx.builder.plugins) { for (const p of ctx.builder.plugins) {
alias[p.name] = p.mode === 'server' alias[p.name] = p.mode === 'server'

View File

@ -58,8 +58,7 @@ export async function buildServer (ctx: ViteBuildContext) {
ssr: ctx.nuxt.options.ssr ?? true, ssr: ctx.nuxt.options.ssr ?? true,
ssrManifest: true, ssrManifest: true,
rollupOptions: { rollupOptions: {
// Private nitro alias: packages/nitro/src/rollup/config.ts#L234 external: ['#nitro'],
external: ['#_config'],
input: resolve(ctx.nuxt.options.buildDir, 'server.js'), input: resolve(ctx.nuxt.options.buildDir, 'server.js'),
output: { output: {
entryFileNames: 'server.mjs', entryFileNames: 'server.mjs',

View File

@ -1,4 +1,4 @@
import type {} from '@nuxt/nitro' /// <reference types="nitropack" />
import type { NuxtConfig as _NuxtConfig } from '@nuxt/schema' import type { NuxtConfig as _NuxtConfig } from '@nuxt/schema'
import type { MetaInfo } from 'vue-meta' import type { MetaInfo } from 'vue-meta'
import type { PluginOptions as ScriptSetupPluginOptions } from 'unplugin-vue2-script-setup/dist' import type { PluginOptions as ScriptSetupPluginOptions } from 'unplugin-vue2-script-setup/dist'

View File

@ -1,3 +0,0 @@
# Nitro
**Notice:** This package is being deprecated. Read more: <https://github.com/nuxt/framework/issues/3161>

View File

@ -1,24 +0,0 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
declaration: true,
entries: [
'src/index',
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' },
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'cjs', declaration: false }
],
dependencies: [
'@cloudflare/kv-asset-handler',
'@netlify/functions',
'@nuxt/devalue',
'connect',
'destr',
'ohmyfetch',
'ora',
'vue-bundle-renderer',
'vue-server-renderer'
],
externals: [
'@nuxt/schema'
]
})

View File

@ -1,14 +0,0 @@
declare module '#build/dist/server/client.manifest.mjs' {
type ClientManifest = any // TODO: export from vue-bundle-renderer
const clientManifest: ClientManifest
export default clientManifest
}
declare module '#build/dist/server/server.mjs' {
const _default: any
export default _default
}
declare module '#nitro-renderer' {
export const renderToString: Function
}

View File

@ -1,90 +0,0 @@
{
"name": "@nuxt/nitro",
"version": "3.0.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.mjs",
"types": "./types/index.d.ts",
"files": [
"dist",
"types"
],
"scripts": {
"prepack": "unbuild"
},
"dependencies": {
"@cloudflare/kv-asset-handler": "^0.2.0",
"@netlify/functions": "^1.0.0",
"@nuxt/devalue": "^2.0.0",
"@nuxt/kit": "3.0.0",
"@nuxt/ui-templates": "npm:@nuxt/ui-templates-edge@latest",
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^21.0.3",
"@rollup/plugin-inject": "^4.0.4",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.1.3",
"@rollup/plugin-replace": "^4.0.0",
"@rollup/plugin-virtual": "^2.1.0",
"@rollup/plugin-wasm": "^5.1.2",
"@rollup/pluginutils": "^4.2.0",
"@types/jsdom": "^16.2.14",
"@vercel/nft": "^0.18.0",
"archiver": "^5.3.0",
"chalk": "^5.0.1",
"chokidar": "^3.5.3",
"connect": "^3.7.0",
"consola": "^2.15.3",
"defu": "^6.0.0",
"destr": "^1.1.0",
"dot-prop": "^7.2.0",
"esbuild": "^0.14.34",
"etag": "^1.8.1",
"fs-extra": "^10.0.1",
"globby": "^13.1.1",
"gzip-size": "^7.0.0",
"h3": "^0.4.2",
"hasha": "^5.2.2",
"hookable": "^5.1.1",
"http-proxy": "^1.18.1",
"is-primitive": "^3.0.1",
"jiti": "^1.13.0",
"knitwork": "^0.1.1",
"listhen": "^0.2.6",
"mime": "^3.0.0",
"mlly": "^0.5.1",
"node-fetch": "^3.2.3",
"ohmyfetch": "^0.4.15",
"ora": "^6.1.0",
"pathe": "^0.2.0",
"perfect-debounce": "^0.1.3",
"pkg-types": "^0.3.2",
"pretty-bytes": "^6.0.0",
"rollup": "^2.70.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-visualizer": "^5.6.0",
"serve-placeholder": "^1.2.4",
"serve-static": "^1.15.0",
"std-env": "^3.0.1",
"table": "^6.8.0",
"ufo": "^0.8.3",
"unenv": "^0.4.5",
"unstorage": "^0.3.3",
"vue-bundle-renderer": "^0.3.5",
"vue-server-renderer": "^2.6.14"
},
"devDependencies": {
"@nuxt/schema": "3.0.0",
"@types/aws-lambda": "^8.10.93",
"@types/etag": "^1.8.1",
"@types/fs-extra": "^9.0.13",
"@types/http-proxy": "^1.17.8",
"@types/mime": "^2.0.3",
"@types/node-fetch": "^3.0.2",
"@types/serve-static": "^1.13.10",
"unbuild": "latest",
"vue": "3.2.31"
},
"engines": {
"node": "^14.16.0 || ^16.11.0 || ^17.0.0"
}
}

View File

@ -1,196 +0,0 @@
import { relative, resolve, join } from 'pathe'
import { logger } from '@nuxt/kit'
import * as rollup from 'rollup'
import fse from 'fs-extra'
import { genDynamicImport } from 'knitwork'
import { printFSTree } from './utils/tree'
import { getRollupConfig } from './rollup/config'
import { hl, prettyPath, serializeTemplate, writeFile, isDirectory, replaceAll } from './utils'
import { NitroContext } from './context'
import { scanMiddleware } from './server/middleware'
export async function prepare (nitroContext: NitroContext) {
logger.info(`Nitro preset is ${hl(nitroContext.preset)}`)
await cleanupDir(nitroContext.output.dir)
if (!nitroContext.output.publicDir.startsWith(nitroContext.output.dir)) {
await cleanupDir(nitroContext.output.publicDir)
}
if (!nitroContext.output.serverDir.startsWith(nitroContext.output.dir)) {
await cleanupDir(nitroContext.output.serverDir)
}
}
async function cleanupDir (dir: string) {
logger.info('Cleaning up', prettyPath(dir))
await fse.emptyDir(dir)
}
export async function generate (nitroContext: NitroContext) {
logger.start('Generating public...')
await nitroContext._internal.hooks.callHook('nitro:generate', nitroContext)
const publicDir = nitroContext._nuxt.publicDir
if (await isDirectory(publicDir)) {
await fse.copy(publicDir, nitroContext.output.publicDir)
}
const clientDist = resolve(nitroContext._nuxt.buildDir, 'dist/client')
if (await isDirectory(clientDist)) {
const buildAssetsDir = join(nitroContext.output.publicDir, nitroContext._nuxt.buildAssetsDir)
await fse.copy(clientDist, buildAssetsDir)
}
logger.success('Generated public ' + prettyPath(nitroContext.output.publicDir))
}
export async function build (nitroContext: NitroContext) {
// Compile html template
const htmlSrc = resolve(nitroContext._nuxt.buildDir, `views/${{ 2: 'app', 3: 'document' }[2]}.template.html`)
const htmlTemplate = { src: htmlSrc, contents: '', dst: '' }
htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.mjs').replace('app.template.mjs', 'document.template.mjs')
htmlTemplate.contents = nitroContext.vfs[htmlTemplate.src] || await fse.readFile(htmlTemplate.src, 'utf-8')
await nitroContext._internal.hooks.callHook('nitro:document', htmlTemplate)
const compiled = 'export default ' + serializeTemplate(htmlTemplate.contents)
await writeFile(htmlTemplate.dst, compiled)
nitroContext.rollupConfig = getRollupConfig(nitroContext)
await nitroContext._internal.hooks.callHook('nitro:rollup:before', nitroContext)
return nitroContext._nuxt.dev ? _watch(nitroContext) : _build(nitroContext)
}
export async function writeTypes (nitroContext: NitroContext) {
const routeTypes: Record<string, string[]> = {}
const middleware = [
...nitroContext.scannedMiddleware,
...nitroContext.middleware
]
for (const mw of middleware) {
if (typeof mw.handle !== 'string') { continue }
const relativePath = relative(join(nitroContext._nuxt.buildDir, 'types'), mw.handle).replace(/\.[a-z]+$/, '')
routeTypes[mw.route] = routeTypes[mw.route] || []
routeTypes[mw.route].push(`Awaited<ReturnType<typeof ${genDynamicImport(relativePath, { wrapper: false })}.default>>`)
}
const lines = [
'// Generated by nitro',
'declare module \'@nuxt/nitro\' {',
' type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T',
' interface InternalApi {',
...Object.entries(routeTypes).map(([path, types]) => ` '${path}': ${types.join(' | ')}`),
' }',
'}',
// Makes this a module for augmentation purposes
'export {}'
]
await writeFile(join(nitroContext._nuxt.buildDir, 'types/nitro.d.ts'), lines.join('\n'))
}
async function _build (nitroContext: NitroContext) {
const serverDirs = nitroContext._layers.map(layer => layer.serverDir)
nitroContext.scannedMiddleware = (
await Promise.all(serverDirs.map(async dir => await scanMiddleware(dir)))
).flat()
await writeTypes(nitroContext)
logger.start('Building server...')
const build = await rollup.rollup(nitroContext.rollupConfig).catch((error) => {
logger.error('Rollup error: ' + error.message)
throw error
})
logger.start('Writing server bundle...')
await build.write(nitroContext.rollupConfig.output)
const rewriteBuildPaths = (input: unknown, to: string) =>
typeof input === 'string' ? replaceAll(input, nitroContext.output.dir, to) : undefined
// Write build info
const nitroConfigPath = resolve(nitroContext.output.dir, 'nitro.json')
const buildInfo = {
date: new Date(),
preset: nitroContext.preset,
commands: {
preview: rewriteBuildPaths(nitroContext.commands.preview, '.'),
deploy: rewriteBuildPaths(nitroContext.commands.deploy, '.')
}
}
await writeFile(nitroConfigPath, JSON.stringify(buildInfo, null, 2))
logger.success('Server built')
await printFSTree(nitroContext.output.serverDir)
await nitroContext._internal.hooks.callHook('nitro:compiled', nitroContext)
// Show deploy and preview hints
const rOutDir = relative(process.cwd(), nitroContext.output.dir)
if (nitroContext.commands.preview) {
// consola.info(`You can preview this build using \`${rewriteBuildPaths(nitroContext.commands.preview, rOutDir)}\``)
logger.info('You can preview this build using `nuxi preview`')
}
if (nitroContext.commands.deploy) {
logger.info(`You can deploy this build using \`${rewriteBuildPaths(nitroContext.commands.deploy, rOutDir)}\``)
}
return {
entry: resolve(nitroContext.rollupConfig.output.dir, nitroContext.rollupConfig.output.entryFileNames as string)
}
}
function startRollupWatcher (nitroContext: NitroContext) {
const watcher = rollup.watch(nitroContext.rollupConfig)
let start: number
watcher.on('event', (event) => {
switch (event.code) {
// The watcher is (re)starting
case 'START':
return
// Building an individual bundle
case 'BUNDLE_START':
start = Date.now()
return
// Finished building all bundles
case 'END':
nitroContext._internal.hooks.callHook('nitro:compiled', nitroContext)
logger.success('Nitro built', start ? `in ${Date.now() - start} ms` : '')
return
// Encountered an error while bundling
case 'ERROR':
logger.error('Rollup error: ' + event.error)
// consola.error(event.error)
}
})
return watcher
}
async function _watch (nitroContext: NitroContext) {
let watcher = startRollupWatcher(nitroContext)
const serverDirs = nitroContext._layers.map(layer => layer.serverDir)
nitroContext.scannedMiddleware = (
await Promise.all(serverDirs.map(async dir => await scanMiddleware(dir,
(middleware, event) => {
nitroContext.scannedMiddleware = middleware
if (['add', 'addDir'].includes(event)) {
watcher.close()
writeTypes(nitroContext).catch(console.error)
watcher = startRollupWatcher(nitroContext)
}
}
)))
).flat()
await writeTypes(nitroContext)
}

View File

@ -1,216 +0,0 @@
/* eslint-disable no-use-before-define */
import { resolve } from 'pathe'
import defu from 'defu'
import { createHooks, Hookable, NestedHooks } from 'hookable'
import type { Preset } from 'unenv'
import type { NuxtHooks, NuxtOptions } from '@nuxt/schema'
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'
import { tryImport, resolvePath, detectTarget, extendPreset, evalTemplate } from './utils'
import * as PRESETS from './presets'
import type { NodeExternalsOptions } from './rollup/plugins/externals'
import type { StorageOptions } from './rollup/plugins/storage'
import type { AssetOptions } from './rollup/plugins/assets'
import type { ServerMiddleware } from './server/middleware'
import type { RollupConfig } from './rollup/config'
import type { Options as EsbuildOptions } from './rollup/plugins/esbuild'
import { runtimeDir } from './dirs'
export interface NitroHooks {
'nitro:document': (htmlTemplate: { src: string, contents: string, dst: string }) => void
'nitro:rollup:before': (context: NitroContext) => void | Promise<void>
'nitro:compiled': (context: NitroContext) => void
'nitro:generate': (context: NitroContext) => void | Promise<void>
'close': () => void
}
export interface NitroContext {
alias: Record<string, string>
timing: boolean
inlineDynamicImports: boolean
minify: boolean
sourceMap: boolean
externals: boolean | NodeExternalsOptions
analyze: false | PluginVisualizerOptions
entry: string
node: boolean
preset: string
rollupConfig?: RollupConfig
esbuild?: {
options?: EsbuildOptions
}
experiments?: {
wasm?: boolean
}
commands: {
preview: string | ((config: NitroContext) => string)
deploy: string | ((config: NitroContext) => string)
},
moduleSideEffects: string[]
renderer: string
serveStatic: boolean
middleware: ServerMiddleware[]
scannedMiddleware: ServerMiddleware[]
hooks: NestedHooks<NitroHooks>
nuxtHooks: NestedHooks<NuxtHooks>
ignore: string[]
env: Preset
vfs: Record<string, string>
output: {
dir: string
serverDir: string
publicDir: string
}
storage: StorageOptions,
assets: AssetOptions,
_nuxt: {
majorVersion: number
dev: boolean
ssr: boolean
rootDir: string
srcDir: string
buildDir: string
generateDir: string
publicDir: string
serverDir: string
baseURL: string
buildAssetsDir: string
isStatic: boolean
fullStatic: boolean
staticAssets: any
modulesDir: string[]
runtimeConfig: { public: any, private: any }
}
_internal: {
runtimeDir: string
hooks: Hookable<NitroHooks>
},
_layers: Array<{
serverDir: string
}>
}
type DeepPartial<T> = T extends Record<string, any> ? { [P in keyof T]?: DeepPartial<T[P]> | T[P] } : T
export interface NitroInput extends DeepPartial<NitroContext> {}
export type NitroPreset = NitroInput | ((input: NitroInput) => NitroInput)
export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): NitroContext {
const defaults: NitroContext = {
alias: {},
timing: undefined,
inlineDynamicImports: undefined,
minify: undefined,
sourceMap: undefined,
externals: undefined,
analyze: nuxtOptions.build.analyze as any,
entry: undefined,
node: undefined,
preset: undefined,
rollupConfig: undefined,
experiments: {},
moduleSideEffects: ['unenv/runtime/polyfill/'],
renderer: undefined,
serveStatic: undefined,
commands: {
preview: undefined,
deploy: undefined
},
middleware: [],
scannedMiddleware: [],
ignore: [],
env: {},
vfs: {},
hooks: {},
nuxtHooks: {},
output: {
dir: '{{ _nuxt.rootDir }}/.output',
serverDir: '{{ output.dir }}/server',
publicDir: '{{ output.dir }}/public'
},
storage: { mounts: { } },
assets: {
inline: !nuxtOptions.dev,
dirs: {}
},
_nuxt: {
majorVersion: nuxtOptions._majorVersion || 2,
dev: nuxtOptions.dev,
ssr: nuxtOptions.ssr,
rootDir: nuxtOptions.rootDir,
srcDir: nuxtOptions.srcDir,
buildDir: nuxtOptions.buildDir,
generateDir: nuxtOptions.generate.dir,
publicDir: resolve(nuxtOptions.srcDir, nuxtOptions.dir.public || nuxtOptions.dir.static),
serverDir: resolve(nuxtOptions.srcDir, (nuxtOptions.dir as any).server || 'server'),
baseURL: nuxtOptions.app.baseURL || '/',
buildAssetsDir: nuxtOptions.app.buildAssetsDir,
isStatic: nuxtOptions.target === 'static' && !nuxtOptions.dev,
fullStatic: nuxtOptions.target === 'static' && !nuxtOptions._legacyGenerate,
staticAssets: nuxtOptions.generate.staticAssets,
modulesDir: nuxtOptions.modulesDir,
runtimeConfig: {
public: nuxtOptions.publicRuntimeConfig,
private: nuxtOptions.privateRuntimeConfig
}
},
_internal: {
runtimeDir,
hooks: createHooks<NitroHooks>()
},
_layers: nuxtOptions._layers.map(layer => ({
serverDir: resolve(layer.config.srcDir, (layer.config.dir as any)?.server || 'server')
}))
}
defaults.preset = input.preset || process.env.NITRO_PRESET || detectTarget() || 'server'
// eslint-disable-next-line import/namespace
let presetDefaults = PRESETS[defaults.preset] || tryImport(nuxtOptions.rootDir, defaults.preset)
if (!presetDefaults) {
throw new Error('Cannot resolve preset: ' + defaults.preset)
}
presetDefaults = presetDefaults.default || presetDefaults
const _presetInput = defu(input, defaults)
const _preset = (extendPreset(presetDefaults /* base */, input) as Function)(_presetInput)
const nitroContext: NitroContext = defu(_preset, defaults) as any
nitroContext.output.dir = resolvePath(nitroContext, nitroContext.output.dir)
nitroContext.output.publicDir = resolvePath(nitroContext, nitroContext.output.publicDir)
nitroContext.output.serverDir = resolvePath(nitroContext, nitroContext.output.serverDir)
if (nitroContext.commands.preview) {
nitroContext.commands.preview = evalTemplate(nitroContext, nitroContext.commands.preview)
}
if (nitroContext.commands.deploy) {
nitroContext.commands.deploy = evalTemplate(nitroContext, nitroContext.commands.deploy)
}
nitroContext._internal.hooks.addHooks(nitroContext.hooks)
// Dev-only storage
if (nitroContext._nuxt.dev) {
const fsMounts = {
root: resolve(nitroContext._nuxt.rootDir),
src: resolve(nitroContext._nuxt.srcDir),
build: resolve(nitroContext._nuxt.buildDir),
cache: resolve(nitroContext._nuxt.rootDir, '.nuxt/nitro/cache')
}
for (const p in fsMounts) {
nitroContext.storage.mounts[p] = nitroContext.storage.mounts[p] || {
driver: 'fs',
driverOptions: { base: fsMounts[p] }
}
}
}
// Assets
nitroContext.assets.dirs.server = {
dir: resolve(nitroContext._nuxt.srcDir, 'server/assets'), meta: true
}
// console.log(nitroContext)
// process.exit(1)
return nitroContext
}

View File

@ -1,6 +0,0 @@
import { fileURLToPath } from 'url'
import { dirname, resolve } from 'pathe'
export const distDir = dirname(fileURLToPath(import.meta.url))
export const pkgDir = resolve(distDir, '..')
export const runtimeDir = resolve(distDir, 'runtime')

View File

@ -1,5 +0,0 @@
export * from './build'
export * from './context'
export * from './server/middleware'
export * from './server/dev'
export { wpfs } from './utils/wpfs'

View File

@ -1,106 +0,0 @@
import fse from 'fs-extra'
import { globby } from 'globby'
import { join, resolve } from 'pathe'
import { writeFile } from '../utils'
import { NitroPreset, NitroContext } from '../context'
export const azure: NitroPreset = {
entry: '{{ _internal.runtimeDir }}/entries/azure',
externals: true,
output: {
serverDir: '{{ output.dir }}/server/functions'
},
commands: {
preview: 'npx @azure/static-web-apps-cli start {{ output.publicDir }} --api-location {{ output.serverDir }}/..'
},
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
await writeRoutes(ctx)
}
}
}
async function writeRoutes ({ output }: NitroContext) {
const host = {
version: '2.0'
}
const config = {
routes: [],
navigationFallback: {
rewrite: '/api/server'
}
}
const indexPath = resolve(output.publicDir, 'index.html')
const indexFileExists = fse.existsSync(indexPath)
if (!indexFileExists) {
config.routes.unshift(
{
route: '/index.html',
redirect: '/'
},
{
route: '/',
rewrite: '/api/server'
}
)
}
const folderFiles = await globby([
join(output.publicDir, 'index.html'),
join(output.publicDir, '**/index.html')
])
const prefix = output.publicDir.length
const suffix = '/index.html'.length
folderFiles.forEach(file =>
config.routes.unshift({
route: file.slice(prefix, -suffix) || '/',
rewrite: file.slice(prefix)
})
)
const otherFiles = await globby([join(output.publicDir, '**/*.html'), join(output.publicDir, '*.html')])
otherFiles.forEach((file) => {
if (file.endsWith('index.html')) {
return
}
const route = file.slice(prefix, '.html'.length)
const existingRouteIndex = config.routes.findIndex(_route => _route.route === route)
if (existingRouteIndex > -1) {
config.routes.splice(existingRouteIndex, 1)
}
config.routes.unshift(
{
route,
rewrite: file.slice(prefix)
}
)
})
const functionDefinition = {
entryPoint: 'handle',
bindings: [
{
authLevel: 'anonymous',
type: 'httpTrigger',
direction: 'in',
name: 'req',
route: '{*url}',
methods: ['delete', 'get', 'head', 'options', 'patch', 'post', 'put']
},
{
type: 'http',
direction: 'out',
name: 'res'
}
]
}
await writeFile(resolve(output.serverDir, 'function.json'), JSON.stringify(functionDefinition))
await writeFile(resolve(output.serverDir, '../host.json'), JSON.stringify(host))
await writeFile(resolve(output.publicDir, 'staticwebapp.config.json'), JSON.stringify(config))
if (!indexFileExists) {
await writeFile(indexPath, '')
}
}

View File

@ -1,73 +0,0 @@
import { createWriteStream } from 'fs'
import archiver from 'archiver'
import { join, resolve } from 'pathe'
import { writeFile } from '../utils'
import { NitroPreset, NitroContext } from '../context'
// eslint-disable-next-line
export const azure_functions: NitroPreset = {
serveStatic: true,
entry: '{{ _internal.runtimeDir }}/entries/azure_functions',
externals: true,
commands: {
deploy: 'az functionapp deployment source config-zip -g <resource-group> -n <app-name> --src {{ output.dir }}/deploy.zip'
},
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
await writeRoutes(ctx)
}
}
}
function zipDirectory (dir: string, outfile: string): Promise<undefined> {
const archive = archiver('zip', { zlib: { level: 9 } })
const stream = createWriteStream(outfile)
return new Promise((resolve, reject) => {
archive
.directory(dir, false)
.on('error', (err: Error) => reject(err))
.pipe(stream)
stream.on('close', () => resolve(undefined))
archive.finalize()
})
}
async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) {
const host = {
version: '2.0',
extensions: { http: { routePrefix: '' } }
}
const functionDefinition = {
entryPoint: 'handle',
bindings: [
{
authLevel: 'anonymous',
type: 'httpTrigger',
direction: 'in',
name: 'req',
route: '{*url}',
methods: [
'delete',
'get',
'head',
'options',
'patch',
'post',
'put'
]
},
{
type: 'http',
direction: 'out',
name: 'res'
}
]
}
await writeFile(resolve(serverDir, 'function.json'), JSON.stringify(functionDefinition))
await writeFile(resolve(dir, 'host.json'), JSON.stringify(host))
await zipDirectory(dir, join(dir, 'deploy.zip'))
}

View File

@ -1,84 +0,0 @@
import { existsSync, promises as fsp } from 'fs'
import { resolve } from 'pathe'
import { logger } from '@nuxt/kit'
import { joinURL } from 'ufo'
import { genString } from 'knitwork'
import { extendPreset, prettyPath } from '../utils'
import { NitroPreset, NitroContext, NitroInput } from '../context'
import { worker } from './worker'
export const browser: NitroPreset = extendPreset(worker, (input: NitroInput) => {
// TODO: Join base at runtime
const baseURL = input._nuxt.baseURL
const script = `<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register(${genString(joinURL(baseURL, 'sw.js'))});
});
}
</script>`
// TEMP FIX
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="prefetch" href="${joinURL(baseURL, 'sw.js')}">
<link rel="prefetch" href="${joinURL(baseURL, '_server/index.mjs')}">
<script>
async function register () {
const registration = await navigator.serviceWorker.register(${genString(joinURL(baseURL, 'sw.js'))})
await navigator.serviceWorker.ready
registration.active.addEventListener('statechange', (event) => {
if (event.target.state === 'activated') {
window.location.reload()
}
})
}
if (location.hostname !== 'localhost' && location.protocol === 'http:') {
location.replace(location.href.replace('http://', 'https://'))
} else {
register()
}
</script>
</head>
<body>
Loading...
</body>
</html>`
return <NitroInput> {
entry: '{{ _internal.runtimeDir }}/entries/service-worker',
output: {
serverDir: '{{ output.dir }}/public/_server'
},
nuxtHooks: {
'generate:page' (page) {
page.html = page.html.replace('</body>', script + '</body>')
}
},
hooks: {
'nitro:document' (tmpl) {
tmpl.contents = tmpl.contents.replace('</body>', script + '</body>')
},
async 'nitro:compiled' ({ output }: NitroContext) {
await fsp.writeFile(resolve(output.publicDir, 'sw.js'), `self.importScripts(${genString(joinURL(baseURL, '_server/index.mjs'))});`, 'utf8')
// Temp fix
if (!existsSync(resolve(output.publicDir, 'index.html'))) {
await fsp.writeFile(resolve(output.publicDir, 'index.html'), html, 'utf8')
}
if (!existsSync(resolve(output.publicDir, '200.html'))) {
await fsp.writeFile(resolve(output.publicDir, '200.html'), html, 'utf8')
}
if (!existsSync(resolve(output.publicDir, '404.html'))) {
await fsp.writeFile(resolve(output.publicDir, '404.html'), html, 'utf8')
}
logger.info('Ready to deploy to static hosting:', prettyPath(output.publicDir as string))
}
}
}
})

View File

@ -1,13 +0,0 @@
import consola from 'consola'
import { extendPreset, prettyPath } from '../utils'
import { NitroPreset, NitroContext } from '../context'
import { node } from './node'
export const cli: NitroPreset = extendPreset(node, {
entry: '{{ _internal.runtimeDir }}/entries/cli',
hooks: {
'nitro:compiled' ({ output }: NitroContext) {
consola.info('Run with `node ' + prettyPath(output.serverDir) + ' [route]`')
}
}
})

View File

@ -1,21 +0,0 @@
import { resolve } from 'pathe'
import { extendPreset, writeFile } from '../utils'
import { NitroContext, NitroPreset } from '../context'
import { worker } from './worker'
export const cloudflare: NitroPreset = extendPreset(worker, {
entry: '{{ _internal.runtimeDir }}/entries/cloudflare',
ignore: [
'wrangler.toml'
],
commands: {
preview: 'npx miniflare {{ output.serverDir }}/index.mjs --site {{ output.publicDir }}',
deploy: 'cd {{ output.serverDir }} && npx wrangler publish'
},
hooks: {
async 'nitro:compiled' ({ output, _nuxt }: NitroContext) {
await writeFile(resolve(output.dir, 'package.json'), JSON.stringify({ private: true, main: './server/index.mjs' }, null, 2))
await writeFile(resolve(output.dir, 'package-lock.json'), JSON.stringify({ lockfileVersion: 1 }, null, 2))
}
}
})

View File

@ -1,13 +0,0 @@
import { extendPreset } from '../utils'
import { NitroPreset } from '../context'
import { node } from './node'
export const dev: NitroPreset = extendPreset(node, {
entry: '{{ _internal.runtimeDir }}/entries/dev',
output: {
serverDir: '{{ _nuxt.buildDir }}/nitro'
},
externals: { trace: false },
inlineDynamicImports: true, // externals plugin limitation
sourceMap: true
})

View File

@ -1,90 +0,0 @@
import { createRequire } from 'module'
import { join, relative, resolve } from 'pathe'
import fse from 'fs-extra'
import { globby } from 'globby'
import { readPackageJSON } from 'pkg-types'
import { writeFile } from '../utils'
import { NitroPreset, NitroContext } from '../context'
export const firebase: NitroPreset = {
entry: '{{ _internal.runtimeDir }}/entries/firebase',
externals: true,
commands: {
deploy: 'npx firebase deploy'
},
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
await writeRoutes(ctx)
}
}
}
async function writeRoutes ({ output: { publicDir, serverDir }, _nuxt: { rootDir, modulesDir } }: NitroContext) {
if (!fse.existsSync(join(rootDir, 'firebase.json'))) {
const firebase = {
functions: {
source: relative(rootDir, serverDir)
},
hosting: [
{
site: '<your_project_id>',
public: relative(rootDir, publicDir),
cleanUrls: true,
rewrites: [
{
source: '**',
function: 'server'
}
]
}
]
}
await writeFile(resolve(rootDir, 'firebase.json'), JSON.stringify(firebase))
}
const _require = createRequire(import.meta.url)
const jsons = await globby(`${serverDir}/node_modules/**/package.json`)
const prefixLength = `${serverDir}/node_modules/`.length
const suffixLength = '/package.json'.length
const dependencies = jsons.reduce((obj, packageJson) => {
const dirname = packageJson.slice(prefixLength, -suffixLength)
if (!dirname.includes('node_modules')) {
obj[dirname] = _require(packageJson).version
}
return obj
}, {} as Record<string, string>)
let nodeVersion = '14'
try {
const currentNodeVersion = fse.readJSONSync(join(rootDir, 'package.json')).engines.node
if (['16', '14'].includes(currentNodeVersion)) {
nodeVersion = currentNodeVersion
}
} catch {}
const getPackageVersion = async (id) => {
const pkg = await readPackageJSON(id, { url: modulesDir })
return pkg.version
}
await writeFile(
resolve(serverDir, 'package.json'),
JSON.stringify(
{
private: true,
type: 'module',
main: './index.mjs',
dependencies,
devDependencies: {
'firebase-functions-test': 'latest',
'firebase-admin': await getPackageVersion('firebase-admin'),
'firebase-functions': await getPackageVersion('firebase-functions')
},
engines: { node: nodeVersion }
},
null,
2
)
)
}

View File

@ -1,13 +0,0 @@
export * from './azure_functions'
export * from './azure'
export * from './browser'
export * from './cloudflare'
export * from './firebase'
export * from './lambda'
export * from './netlify'
export * from './node'
export * from './dev'
export * from './server'
export * from './cli'
export * from './vercel'
export * from './worker'

View File

@ -1,7 +0,0 @@
import { NitroPreset } from '../context'
export const lambda: NitroPreset = {
entry: '{{ _internal.runtimeDir }}/entries/lambda',
externals: true
}

View File

@ -1,41 +0,0 @@
import { existsSync, promises as fsp } from 'fs'
import { join } from 'pathe'
import consola from 'consola'
import { extendPreset } from '../utils'
import { NitroContext, NitroPreset } from '../context'
import { lambda } from './lambda'
export const netlify: NitroPreset = extendPreset(lambda, {
output: {
dir: '{{ _nuxt.rootDir }}/.netlify/functions-internal',
publicDir: '{{ _nuxt.rootDir }}/dist'
},
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
const redirectsPath = join(ctx.output.publicDir, '_redirects')
let contents = '/* /.netlify/functions/server 200'
if (existsSync(redirectsPath)) {
const currentRedirects = await fsp.readFile(redirectsPath, 'utf-8')
if (currentRedirects.match(/^\/\* /m)) {
consola.info('Not adding Nitro fallback to `_redirects` (as an existing fallback was found).')
return
}
consola.info('Adding Nitro fallback to `_redirects` to handle all unmatched routes.')
contents = currentRedirects + '\n' + contents
}
await fsp.writeFile(redirectsPath, contents)
},
'nitro:rollup:before' (ctx: NitroContext) {
ctx.rollupConfig.output.entryFileNames = 'server.ts'
}
},
ignore: [
'netlify.toml',
'_redirects'
]
})
// eslint-disable-next-line
export const netlify_builder: NitroPreset = extendPreset(netlify, {
entry: '{{ _internal.runtimeDir }}/entries/netlify_builder'
})

View File

@ -1,6 +0,0 @@
import { NitroPreset } from '../context'
export const node: NitroPreset = {
entry: '{{ _internal.runtimeDir }}/entries/node',
externals: true
}

View File

@ -1,11 +0,0 @@
import { extendPreset } from '../utils'
import { NitroPreset } from '../context'
import { node } from './node'
export const server: NitroPreset = extendPreset(node, {
entry: '{{ _internal.runtimeDir }}/entries/server',
serveStatic: true,
commands: {
preview: 'node {{ output.serverDir }}/index.mjs'
}
})

View File

@ -1,49 +0,0 @@
import { resolve } from 'pathe'
import { extendPreset, writeFile } from '../utils'
import { NitroPreset, NitroContext } from '../context'
import { node } from './node'
export const vercel: NitroPreset = extendPreset(node, {
entry: '{{ _internal.runtimeDir }}/entries/vercel',
output: {
dir: '{{ _nuxt.rootDir }}/.vercel_build_output',
serverDir: '{{ output.dir }}/functions/node/server',
publicDir: '{{ output.dir }}/static'
},
ignore: [
'vercel.json'
],
hooks: {
async 'nitro:compiled' (ctx: NitroContext) {
await writeRoutes(ctx)
}
}
})
async function writeRoutes ({ output }: NitroContext) {
const routes = [
{
src: '/sw.js',
headers: {
'cache-control': 'public, max-age=0, must-revalidate'
},
continue: true
},
{
src: '/_nuxt/(.*)',
headers: {
'cache-control': 'public,max-age=31536000,immutable'
},
continue: true
},
{
handle: 'filesystem'
},
{
src: '(.*)',
dest: '/.vercel/functions/server/index'
}
]
await writeFile(resolve(output.dir, 'config/routes.json'), JSON.stringify(routes, null, 2))
}

View File

@ -1,9 +0,0 @@
import { NitroPreset } from '../context'
export const worker: NitroPreset = {
entry: null, // Abstract
node: false,
minify: true,
externals: false,
inlineDynamicImports: true // iffe does not support code-splitting
}

View File

@ -1,347 +0,0 @@
import { pathToFileURL } from 'url'
import { createRequire } from 'module'
import { dirname, join, relative, resolve } from 'pathe'
import type { InputOptions, OutputOptions } from 'rollup'
import defu from 'defu'
import { terser } from 'rollup-plugin-terser'
import commonjs from '@rollup/plugin-commonjs'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import alias from '@rollup/plugin-alias'
import json from '@rollup/plugin-json'
import replace from '@rollup/plugin-replace'
import virtual from '@rollup/plugin-virtual'
import wasmPlugin from '@rollup/plugin-wasm'
import inject from '@rollup/plugin-inject'
import { visualizer } from 'rollup-plugin-visualizer'
import * as unenv from 'unenv'
import devalue from '@nuxt/devalue'
import type { Preset } from 'unenv'
import { sanitizeFilePath } from 'mlly'
import { genImport } from 'knitwork'
import { NitroContext } from '../context'
import { resolvePath } from '../utils'
import { pkgDir } from '../dirs'
import { dynamicRequire } from './plugins/dynamic-require'
import { externals, NodeExternalsOptions } from './plugins/externals'
import { timing } from './plugins/timing'
// import { autoMock } from './plugins/automock'
import { staticAssets, dirnames } from './plugins/static'
import { assets } from './plugins/assets'
import { middleware } from './plugins/middleware'
import { esbuild } from './plugins/esbuild'
import { raw } from './plugins/raw'
import { storage } from './plugins/storage'
export type RollupConfig = InputOptions & { output: OutputOptions }
export const getRollupConfig = (nitroContext: NitroContext) => {
const extensions: string[] = ['.ts', '.mjs', '.js', '.json', '.node']
const nodePreset = nitroContext.node === false ? unenv.nodeless : unenv.node
const builtinPreset: Preset = {
alias: {
// General
debug: 'unenv/runtime/npm/debug',
consola: 'unenv/runtime/npm/consola',
// Vue 2
encoding: 'unenv/runtime/mock/proxy',
he: 'unenv/runtime/mock/proxy',
resolve: 'unenv/runtime/mock/proxy',
'source-map': 'unenv/runtime/mock/proxy',
'lodash.template': 'unenv/runtime/mock/proxy',
'serialize-javascript': 'unenv/runtime/mock/proxy',
// Vue 3
'estree-walker': 'unenv/runtime/mock/proxy',
'@babel/parser': 'unenv/runtime/mock/proxy',
'@vue/compiler-core': 'unenv/runtime/mock/proxy',
'@vue/compiler-dom': 'unenv/runtime/mock/proxy',
'@vue/compiler-ssr': 'unenv/runtime/mock/proxy',
...nitroContext.alias
}
}
const env = unenv.env(nodePreset, builtinPreset, nitroContext.env)
if (nitroContext.sourceMap) {
env.polyfill.push('source-map-support/register.js')
}
// TODO: #590
const _require = createRequire(import.meta.url)
if (nitroContext._nuxt.majorVersion === 3) {
env.alias['vue/server-renderer'] = 'vue/server-renderer'
env.alias['vue/compiler-sfc'] = 'vue/compiler-sfc'
env.alias.vue = _require.resolve(`vue/dist/vue.cjs${nitroContext._nuxt.dev ? '' : '.prod'}.js`)
}
const buildServerDir = join(nitroContext._nuxt.buildDir, 'dist/server')
const runtimeAppDir = join(nitroContext._internal.runtimeDir, 'app')
const rollupConfig: RollupConfig = {
input: resolvePath(nitroContext, nitroContext.entry),
output: {
dir: nitroContext.output.serverDir,
entryFileNames: 'index.mjs',
chunkFileNames (chunkInfo) {
let prefix = ''
const modules = Object.keys(chunkInfo.modules)
const lastModule = modules[modules.length - 1]
if (lastModule.startsWith(buildServerDir)) {
prefix = join('app', relative(buildServerDir, dirname(lastModule)))
} else if (lastModule.startsWith(runtimeAppDir)) {
prefix = 'app'
} else if (lastModule.startsWith(nitroContext._nuxt.buildDir)) {
prefix = 'nuxt'
} else if (lastModule.startsWith(nitroContext._internal.runtimeDir)) {
prefix = 'nitro'
} else if (nitroContext.middleware.find(m => lastModule.startsWith(m.handle as string))) {
prefix = 'middleware'
} else if (lastModule.includes('assets')) {
prefix = 'assets'
}
return join('chunks', prefix, '[name].mjs')
},
inlineDynamicImports: nitroContext.inlineDynamicImports,
format: 'esm',
exports: 'auto',
intro: '',
outro: '',
preferConst: true,
sanitizeFileName: sanitizeFilePath,
sourcemap: !!nitroContext.sourceMap,
sourcemapExcludeSources: true,
sourcemapPathTransform (relativePath, sourcemapPath) {
return resolve(dirname(sourcemapPath), relativePath)
}
},
external: env.external,
// https://github.com/rollup/rollup/pull/4021#issuecomment-809985618
// https://github.com/nuxt/framework/issues/160
makeAbsoluteExternalsRelative: false,
plugins: [],
onwarn (warning, rollupWarn) {
if (
!['CIRCULAR_DEPENDENCY', 'EVAL'].includes(warning.code) &&
!warning.message.includes('Unsupported source map comment')
) {
rollupWarn(warning)
}
},
treeshake: {
moduleSideEffects (id) {
return nitroContext.moduleSideEffects.some(match => id.startsWith(match))
}
}
}
if (nitroContext.timing) {
rollupConfig.plugins.push(timing())
}
// Raw asset loader
rollupConfig.plugins.push(raw())
// WASM import support
if (nitroContext.experiments.wasm) {
rollupConfig.plugins.push(wasmPlugin())
}
// https://github.com/rollup/plugins/tree/master/packages/replace
rollupConfig.plugins.push(replace({
sourceMap: !!nitroContext.sourceMap,
preventAssignment: true,
values: {
'process.env.NODE_ENV': nitroContext._nuxt.dev ? '"development"' : '"production"',
'typeof window': '"undefined"',
...Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`])),
'process.server': 'true',
'process.client': 'false',
'process.dev': String(nitroContext._nuxt.dev),
'process.env.NUXT_NO_SSR': JSON.stringify(!nitroContext._nuxt.ssr),
'process.env.NUXT_STATIC_BASE': JSON.stringify(nitroContext._nuxt.staticAssets.base),
'process.env.NUXT_STATIC_VERSION': JSON.stringify(nitroContext._nuxt.staticAssets.version),
'process.env.NUXT_FULL_STATIC': nitroContext._nuxt.fullStatic as unknown as string,
'process.env.NITRO_PRESET': JSON.stringify(nitroContext.preset),
'process.env.RUNTIME_CONFIG': devalue(nitroContext._nuxt.runtimeConfig),
'process.env.DEBUG': JSON.stringify(nitroContext._nuxt.dev),
// Needed for vue 2 server build
'commonjsGlobal.process.env.VUE_ENV': '"server"',
'global["process"].env.VUE_ENV': '"server"'
}
}))
// ESBuild
rollupConfig.plugins.push(esbuild({
target: 'es2019',
sourceMap: !!nitroContext.sourceMap,
...nitroContext.esbuild?.options
}))
// Dynamic Require Support
rollupConfig.plugins.push(dynamicRequire({
dir: resolve(nitroContext._nuxt.buildDir, 'dist/server'),
inline: nitroContext.node === false || nitroContext.inlineDynamicImports,
ignore: [
'client.manifest.mjs',
'server.js',
'server.cjs',
'server.mjs',
'server.manifest.mjs'
]
}))
// Assets
rollupConfig.plugins.push(assets(nitroContext.assets))
// Static
// TODO: use assets plugin
if (nitroContext.serveStatic) {
rollupConfig.plugins.push(dirnames())
rollupConfig.plugins.push(staticAssets(nitroContext))
}
// Storage
rollupConfig.plugins.push(storage(nitroContext.storage))
// Middleware
rollupConfig.plugins.push(middleware(() => {
const _middleware = [
...nitroContext.scannedMiddleware,
...nitroContext.middleware
]
if (nitroContext.serveStatic) {
_middleware.unshift({ route: '/', handle: '#nitro/server/static' })
}
return _middleware
}))
// Polyfill
rollupConfig.plugins.push(virtual({
'#polyfill': env.polyfill.map(p => genImport(p)).join('\n')
}))
// https://github.com/rollup/plugins/tree/master/packages/alias
const renderer = nitroContext.renderer || (nitroContext._nuxt.majorVersion === 3 ? 'vue3' : 'vue2')
const vue2ServerRenderer = 'vue-server-renderer/' + (nitroContext._nuxt.dev ? 'build.dev.js' : 'build.prod.js')
rollupConfig.plugins.push(alias({
entries: {
'#nitro': nitroContext._internal.runtimeDir,
'#nitro-renderer': resolve(nitroContext._internal.runtimeDir, 'app', renderer),
'#paths': resolve(nitroContext._internal.runtimeDir, 'app/paths'),
'#config': resolve(nitroContext._internal.runtimeDir, 'app/config'),
'#_config': resolve(nitroContext._internal.runtimeDir, 'app/config'),
'#nitro-vue-renderer': vue2ServerRenderer,
// Only file and data URLs are supported by the default ESM loader on Windows (#427)
'#build': nitroContext._nuxt.dev && process.platform === 'win32'
? pathToFileURL(nitroContext._nuxt.buildDir).href
: nitroContext._nuxt.buildDir,
'~': nitroContext._nuxt.srcDir,
'@/': nitroContext._nuxt.srcDir,
'~~': nitroContext._nuxt.rootDir,
'@@/': nitroContext._nuxt.rootDir,
...env.alias
}
}))
const moduleDirectories = [
resolve(nitroContext._nuxt.rootDir, 'node_modules'),
...nitroContext._nuxt.modulesDir,
resolve(pkgDir, '../node_modules'),
'node_modules'
]
// Externals Plugin
if (nitroContext.externals) {
rollupConfig.plugins.push(externals(defu(nitroContext.externals as NodeExternalsOptions, {
outDir: nitroContext.output.serverDir,
moduleDirectories,
external: [
...(nitroContext._nuxt.dev ? [nitroContext._nuxt.buildDir] : [])
],
inline: [
'#',
'~',
'@/',
'~~',
'@@/',
'virtual:',
nitroContext._internal.runtimeDir,
nitroContext._nuxt.srcDir,
nitroContext._nuxt.rootDir,
nitroContext._nuxt.serverDir,
...nitroContext.middleware.map(m => m.handle).filter(i => typeof i === 'string') as string[],
...(nitroContext._nuxt.dev ? [] : ['vue', '@vue/', '@nuxt/'])
],
traceOptions: {
base: '/',
processCwd: nitroContext._nuxt.rootDir,
exportsOnly: true
}
})))
}
// https://github.com/rollup/plugins/tree/master/packages/node-resolve
rollupConfig.plugins.push(nodeResolve({
extensions,
preferBuiltins: true,
rootDir: nitroContext._nuxt.rootDir,
moduleDirectories,
// 'module' is intentionally not supported because of externals
mainFields: ['main'],
exportConditions: [
'default',
'module',
'node',
'import'
]
}))
// Automatically mock unresolved externals
// rollupConfig.plugins.push(autoMock())
// https://github.com/rollup/plugins/tree/master/packages/commonjs
rollupConfig.plugins.push(commonjs({
sourceMap: !!nitroContext.sourceMap,
esmExternals: id => !id.startsWith('unenv/'),
requireReturnsDefault: 'auto'
}))
// https://github.com/rollup/plugins/tree/master/packages/json
rollupConfig.plugins.push(json())
// https://github.com/rollup/plugins/tree/master/packages/inject
rollupConfig.plugins.push(inject({
// TODO: https://github.com/rollup/plugins/pull/1066
// @ts-ignore
sourceMap: !!nitroContext.sourceMap,
...env.inject
}))
// https://github.com/TrySound/rollup-plugin-terser
// https://github.com/terser/terser#minify-nitroContext
if (nitroContext.minify) {
rollupConfig.plugins.push(terser({
mangle: {
keep_fnames: true,
keep_classnames: true
},
format: {
comments: false
}
}))
}
if (nitroContext.analyze) {
// https://github.com/btd/rollup-plugin-visualizer
rollupConfig.plugins.push(visualizer({
...nitroContext.analyze,
filename: nitroContext.analyze.filename.replace('{name}', 'nitro'),
title: 'Nitro Server bundle stats'
}))
}
return rollupConfig
}

View File

@ -1,112 +0,0 @@
import { promises as fsp } from 'fs'
import type { Plugin } from 'rollup'
import createEtag from 'etag'
import mime from 'mime'
import { resolve } from 'pathe'
import { globby } from 'globby'
import { genDynamicImport, genObjectFromRawEntries } from 'knitwork'
import virtual from './virtual'
export interface AssetOptions {
inline: boolean
dirs: {
[assetdir: string]: {
dir: string
meta?: boolean
}
}
}
interface Asset {
fsPath: string
meta: {
type?: string
etag?: string
mtime?: string
}
}
export function assets (opts: AssetOptions): Plugin {
if (!opts.inline) {
// Development: Use filesystem
return virtual({ '#assets': getAssetsDev(opts.dirs) })
}
// Production: Bundle assets
return virtual({
'#assets': {
async load () {
// Scan all assets
const assets: Record<string, Asset> = {}
for (const assetdir in opts.dirs) {
const dirOpts = opts.dirs[assetdir]
const files = await globby('**/*.*', { cwd: dirOpts.dir, absolute: false })
for (const _id of files) {
const fsPath = resolve(dirOpts.dir, _id)
const id = assetdir + '/' + _id
assets[id] = { fsPath, meta: {} }
if (dirOpts.meta) {
// @ts-ignore TODO: Use mime@2 types
let type = mime.getType(id) || 'text/plain'
if (type.startsWith('text')) { type += '; charset=utf-8' }
const etag = createEtag(await fsp.readFile(fsPath))
const mtime = await fsp.stat(fsPath).then(s => s.mtime.toJSON())
assets[id].meta = { type, etag, mtime }
}
}
}
return getAssetProd(assets)
}
}
})
}
function getAssetsDev (dirs) {
return `
import { createStorage } from 'unstorage'
import fsDriver from 'unstorage/drivers/fs'
const dirs = ${JSON.stringify(dirs)}
export const assets = createStorage()
for (const [dirname, dirOpts] of Object.entries(dirs)) {
assets.mount(dirname, fsDriver({ base: dirOpts.dir }))
}
`
}
function normalizeKey (key) {
return key.replace(/[/\\]/g, ':').replace(/^:|:$/g, '')
}
function getAssetProd (assets: Record<string, Asset>) {
return `
const _assets = ${genObjectFromRawEntries(
Object.entries(assets).map(([id, asset]) => [normalizeKey(id), {
import: genDynamicImport(asset.fsPath, { interopDefault: true }),
meta: asset.meta
}])
)}
${normalizeKey.toString()}
export const assets = {
getKeys() {
return Object.keys(_assets)
},
hasItem (id) {
id = normalizeKey(id)
return id in _assets
},
getItem (id) {
id = normalizeKey(id)
return _assets[id] ? _assets[id].import() : null
},
getMeta (id) {
id = normalizeKey(id)
return _assets[id] ? _assets[id].meta : {}
}
}
`
}

View File

@ -1,18 +0,0 @@
import consola from 'consola'
const internalRegex = /^\.|\?|\.[mc]?js$|.ts$|.json$/
export function autoMock () {
return {
name: 'auto-mock',
resolveId (src: string) {
if (src && !internalRegex.test(src)) {
consola.warn('Auto mock external ', src)
return {
id: 'unenv/runtime/mock/proxy'
}
}
return null
}
}
}

View File

@ -1,108 +0,0 @@
import { pathToFileURL } from 'url'
import { resolve } from 'pathe'
import { globby } from 'globby'
import type { Plugin } from 'rollup'
import { genDynamicImport, genObjectFromRawEntries, genImport } from 'knitwork'
import { serializeImportName } from '../../utils'
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, `${genDynamicImport(HELPER_DYNAMIC, { wrapper: false, interopDefault: true })}.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: serializeImportName(id),
meta: await getWebpackChunkMeta(resolve(dir, id))
})))).filter(chunk => chunk.meta)
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 => genImport(i.src, { name: '*', as: i.name })).join('\n')}
const dynamicChunks = ${genObjectFromRawEntries(chunks.map(i => [i.id, i.name]))};
export default function dynamicRequire(id) {
return Promise.resolve(dynamicChunks[id]);
};`
}
function TMPL_LAZY ({ chunks }: TemplateContext) {
return `
const dynamicChunks = ${genObjectFromRawEntries(chunks.map(i => [i.id, genDynamicImport(i.src)]))};
export default function dynamicRequire(id) {
return dynamicChunks[id]();
};`
}

View File

@ -1,138 +0,0 @@
// Based on https://github.com/egoist/rollup-plugin-esbuild (MIT)
import { extname, relative } from 'pathe'
import type { Plugin, PluginContext } from 'rollup'
import { Loader, TransformResult, transform } from 'esbuild'
import { createFilter } from '@rollup/pluginutils'
import type { FilterPattern } from '@rollup/pluginutils'
const defaultLoaders: { [ext: string]: Loader } = {
'.ts': 'ts',
'.js': 'js'
}
export type Options = {
include?: FilterPattern
exclude?: FilterPattern
sourceMap?: boolean
minify?: boolean
target?: string | string[]
jsxFactory?: string
jsxFragment?: string
define?: {
[k: string]: string
}
/**
* Use this tsconfig file instead
* Disable it by setting to `false`
*/
tsconfig?: string | false
/**
* Map extension to esbuild loader
* Note that each entry (the extension) needs to start with a dot
*/
loaders?: {
[ext: string]: Loader | false
}
}
export function esbuild (options: Options = {}): Plugin {
let target: string | string[]
const loaders = {
...defaultLoaders
}
if (options.loaders) {
for (const key of Object.keys(options.loaders)) {
const value = options.loaders[key]
if (typeof value === 'string') {
loaders[key] = value
} else if (value === false) {
delete loaders[key]
}
}
}
const extensions: string[] = Object.keys(loaders)
const INCLUDE_REGEXP = new RegExp(
`\\.(${extensions.map(ext => ext.slice(1)).join('|')})$`
)
const EXCLUDE_REGEXP = /node_modules/
const filter = createFilter(
options.include || INCLUDE_REGEXP,
options.exclude || EXCLUDE_REGEXP
)
return {
name: 'esbuild',
async transform (code, id) {
if (!filter(id)) {
return null
}
const ext = extname(id)
const loader = loaders[ext]
if (!loader) {
return null
}
target = options.target || 'node12'
const result = await transform(code, {
loader,
target,
define: options.define,
sourcemap: options.sourceMap !== false,
sourcefile: id
})
printWarnings(id, result, this)
return (
result.code && {
code: result.code,
map: result.map || null
}
)
},
async renderChunk (code) {
if (options.minify) {
const result = await transform(code, {
loader: 'js',
minify: true,
target
})
if (result.code) {
return {
code: result.code,
map: result.map || null
}
}
}
return null
}
}
}
function printWarnings (
id: string,
result: TransformResult,
plugin: PluginContext
) {
if (result.warnings) {
for (const warning of result.warnings) {
let message = '[esbuild]'
if (warning.location) {
message += ` (${relative(process.cwd(), id)}:${warning.location.line}:${warning.location.column
})`
}
message += ` ${warning.text}`
plugin.warn(message)
}
}
}

View File

@ -1,127 +0,0 @@
import { promises as fsp } from 'fs'
import { resolve, dirname } from 'pathe'
import { nodeFileTrace, NodeFileTraceOptions } from '@vercel/nft'
import type { Plugin } from 'rollup'
export interface NodeExternalsOptions {
inline?: string[]
external?: string[]
outDir?: string
trace?: boolean
traceOptions?: NodeFileTraceOptions
moduleDirectories?: string[]
/** additional packages to include in `.output/server/node_modules` */
traceInclude?: string[]
}
export function externals (opts: NodeExternalsOptions): Plugin {
const trackedExternals = new Set<string>()
return {
name: 'node-externals',
async resolveId (id, importer, options) {
// Internals
if (!id || id.startsWith('\x00') || id.includes('?') || id.startsWith('#')) {
return null
}
const originalId = id
// Normalize path on windows
if (process.platform === 'win32') {
if (id.startsWith('/')) {
// Add back C: prefix on Windows
id = resolve(id)
}
id = id.replace(/\\/g, '/')
}
// Normalize from node_modules
const _id = id.split('node_modules/').pop()
const externalPath = opts.external.find(i => _id.startsWith(i) || id.startsWith(i))
// Skip checks if is an explicit external
if (!externalPath) {
// Resolve relative paths and exceptions
// Ensure to take absolute and relative id
if (_id.startsWith('.') || opts.inline.find(i => _id.startsWith(i) || id.startsWith(i))) {
return null
}
// Bundle typescript, json and wasm (see https://github.com/nuxt/framework/discussions/692)
if (/\.(ts|wasm|json)$/.test(_id)) {
return null
}
// Check for subpaths
} else if (opts.inline.find(i => i.startsWith(externalPath) && (_id.startsWith(i) || id.startsWith(i)))) {
return null
}
// Track externals
if (opts.trace !== false) {
const resolved = await this.resolve(originalId, importer, { ...options, skipSelf: true })
if (!resolved) {
console.warn(`Could not resolve \`${originalId}\`. Have you installed it?`)
} else {
trackedExternals.add(resolved.id)
}
}
return {
id: _id,
external: true
}
},
async buildEnd () {
if (opts.trace !== false) {
for (const pkgName of opts.traceInclude || []) {
const path = await this.resolve(pkgName)
if (path?.id) {
trackedExternals.add(path.id)
}
}
const tracedFiles = await nodeFileTrace(Array.from(trackedExternals), opts.traceOptions)
.then(r => Array.from(r.fileList).map(f => resolve(opts.traceOptions.base, f)))
.then(r => r.filter(file => file.includes('node_modules')))
// // Find all unique package names
const pkgs = new Set<string>()
for (const file of tracedFiles) {
const [, baseDir, pkgName, _importPath] = /^(.+\/node_modules\/)([^@/]+|@[^/]+\/[^/]+)(\/?.*?)?$/.exec(file)
pkgs.add(resolve(baseDir, pkgName, 'package.json'))
}
for (const pkg of pkgs) {
if (!tracedFiles.includes(pkg)) {
tracedFiles.push(pkg)
}
}
const writeFile = async (file) => {
if (!await isFile(file)) { return }
const src = resolve(opts.traceOptions.base, file)
const dst = resolve(opts.outDir, 'node_modules', file.replace(/^.*?node_modules[\\/](.*)$/, '$1'))
await fsp.mkdir(dirname(dst), { recursive: true })
await fsp.copyFile(src, dst)
}
if (process.platform === 'win32') {
// Workaround for EBUSY on windows (#424)
for (const file of tracedFiles) {
await writeFile(file)
}
} else {
await Promise.all(tracedFiles.map(writeFile))
}
}
}
}
}
async function isFile (file: string) {
try {
const stat = await fsp.stat(file)
return stat.isFile()
} catch (err) {
if (err.code === 'ENOENT') { return false }
throw err
}
}

View File

@ -1,81 +0,0 @@
import hasha from 'hasha'
import { relative } from 'pathe'
import table from 'table'
import isPrimitive from 'is-primitive'
import { isDebug } from 'std-env'
import { genArrayFromRaw, genDynamicImport, genImport } from 'knitwork'
import type { ServerMiddleware } from '../../server/middleware'
import virtual from './virtual'
const unique = (arr: any[]) => Array.from(new Set(arr))
export function middleware (getMiddleware: () => ServerMiddleware[]) {
const getImportId = p => '_' + hasha(p).slice(0, 6)
let lastDump = ''
return virtual({
'#server-middleware': {
load: () => {
const middleware = getMiddleware()
if (isDebug) {
const dumped = dumpMiddleware(middleware)
if (dumped !== lastDump) {
lastDump = dumped
if (middleware.length) {
console.log(dumped)
}
}
}
// Imports take priority
const imports = unique(middleware.filter(m => m.lazy === false).map(m => m.handle))
// Lazy imports should fill in the gaps
const lazyImports = unique(middleware.filter(m => m.lazy !== false && !imports.includes(m.handle)).map(m => m.handle))
return `
${imports.map(handle => `${genImport(handle, getImportId(handle))};`).join('\n')}
${lazyImports.map(handle => `const ${getImportId(handle)} = ${genDynamicImport(handle)};`).join('\n')}
const middleware = ${genArrayFromRaw(middleware.map(m => ({
route: JSON.stringify(m.route),
handle: getImportId(m.handle),
lazy: m.lazy || true,
promisify: m.promisify !== undefined ? m.promisify : true
})))};
export default middleware
`
}
}
})
}
function dumpMiddleware (middleware: ServerMiddleware[]) {
const data = middleware.map(({ route, handle, ...props }) => {
return [
(route && route !== '/') ? route : '*',
relative(process.cwd(), handle as string),
dumpObject(props)
]
})
return table.table([
['Route', 'Handle', 'Options'],
...data
], {
singleLine: true,
border: table.getBorderCharacters('norc')
})
}
function dumpObject (obj: any) {
const items = []
for (const key in obj) {
const val = obj[key]
items.push(`${key}: ${isPrimitive(val) ? val : JSON.stringify(val)}`)
}
return items.join(', ')
}

View File

@ -1,23 +0,0 @@
import { extname } from 'pathe'
import type { Plugin } from 'rollup'
export interface RawOptions {
extensions?: string[]
}
export function raw (opts: RawOptions = {}): Plugin {
const extensions = new Set(['.md', '.mdx', '.yml', '.txt', '.css', '.htm', '.html']
.concat(opts.extensions || []))
return {
name: 'raw',
transform (code, id) {
if (id[0] !== '\0' && extensions.has(extname(id))) {
return {
code: `// ${id}\nexport default ${JSON.stringify(code)}`,
map: null
}
}
}
}
}

View File

@ -1,62 +0,0 @@
import { readFileSync, statSync } from 'fs'
import createEtag from 'etag'
import mime from 'mime'
import { relative, resolve } from 'pathe'
import virtual from '@rollup/plugin-virtual'
import { globbySync } from 'globby'
import type { Plugin } from 'rollup'
import type { NitroContext } from '../../context'
export function staticAssets (context: NitroContext) {
const assets: Record<string, { type: string, etag: string, mtime: string, path: string }> = {}
const files = globbySync('**/*.*', { cwd: context.output.publicDir, absolute: false })
for (const id of files) {
let type = mime.getType(id) || 'text/plain'
if (type.startsWith('text')) { type += '; charset=utf-8' }
const fullPath = resolve(context.output.publicDir, id)
const etag = createEtag(readFileSync(fullPath))
const stat = statSync(fullPath)
assets['/' + decodeURIComponent(id)] = {
type,
etag,
mtime: stat.mtime.toJSON(),
path: relative(context.output.serverDir, fullPath)
}
}
return virtual({
'#static-assets': `export default ${JSON.stringify(assets, null, 2)};`,
'#static': `
import { promises } from 'fs'
import { resolve } from 'pathe'
import { dirname } from 'pathe'
import { fileURLToPath } from 'url'
import assets from '#static-assets'
const mainDir = dirname(fileURLToPath(globalThis.entryURL))
export function readAsset (id) {
return promises.readFile(resolve(mainDir, getAsset(id).path))
}
export function getAsset (id) {
return assets[id]
}
`
})
}
export function dirnames (): Plugin {
return {
name: 'dirnames',
renderChunk (code, chunk) {
return {
code: (chunk.isEntry ? 'globalThis.entryURL = import.meta.url;' : '') + code,
map: null
}
}
}
}

View File

@ -1,48 +0,0 @@
import virtual from '@rollup/plugin-virtual'
import { genImport, genString } from 'knitwork'
import { serializeImportName } from '../../utils'
export interface StorageOptions {
mounts: {
[path: string]: {
driver: 'fs' | 'http' | 'memory',
driverOptions?: Record<string, any>
}
}
}
const drivers = {
fs: 'unstorage/drivers/fs',
http: 'unstorage/drivers/http',
memory: 'unstorage/drivers/memory'
}
export function storage (opts: StorageOptions) {
const mounts: { path: string, driver: string, opts: object }[] = []
for (const path in opts.mounts) {
const mount = opts.mounts[path]
mounts.push({
path,
driver: drivers[mount.driver] || mount.driver,
opts: mount.driverOptions || {}
})
}
const driverImports = Array.from(new Set(mounts.map(m => m.driver)))
return virtual({
'#storage': `
import { createStorage } from 'unstorage'
import { assets } from '#assets'
${driverImports.map(i => genImport(i, serializeImportName(i))).join('\n')}
export const storage = createStorage({})
storage.mount('/assets', assets)
${mounts.map(m => `storage.mount(${genString(m.path)}, ${serializeImportName(m.driver)}(${JSON.stringify(m.opts)}))`).join('\n')}
`
})
}

View File

@ -1,44 +0,0 @@
import { extname } from 'pathe'
import type { Plugin, RenderedChunk } from 'rollup'
export interface Options { }
const TIMING = 'globalThis.__timing__'
const iife = code => `(function() { ${code.trim()} })();`.replace(/\n/g, '')
const HELPER = iife(`
const start = () => Date.now();
const end = s => Date.now() - s;
const _s = {};
const metrics = [];
const logStart = id => { _s[id] = Date.now(); };
const logEnd = id => { const t = end(_s[id]); delete _s[id]; metrics.push([id, t]); console.debug('>', id + ' (' + t + 'ms)'); };
${TIMING} = { start, end, metrics, logStart, logEnd };
`)
const HELPERIMPORT = "import './timing.js';"
export function timing (_opts: Options = {}): Plugin {
return {
name: 'timing',
generateBundle () {
this.emitFile({
type: 'asset',
fileName: 'timing.js',
source: HELPER
})
},
renderChunk (code, chunk: RenderedChunk) {
let name = chunk.fileName || ''
name = name.replace(extname(name), '')
const logName = name === 'index' ? 'Nitro Start' : ('Load ' + name)
return {
code: (chunk.isEntry ? HELPERIMPORT : '') + `${TIMING}.logStart('${logName}');` + code + `;${TIMING}.logEnd('${logName}');`,
map: null
}
}
}
}

View File

@ -1,58 +0,0 @@
import { resolve, dirname } from 'pathe'
import type { Plugin } from 'rollup'
// Based on https://github.com/rollup/plugins/blob/master/packages/virtual/src/index.ts
type VirtualModule = string | { load: () => string | Promise<string> }
export interface RollupVirtualOptions {
[id: string]: VirtualModule;
}
const PREFIX = '\0virtual:'
export default function virtual (modules: RollupVirtualOptions): Plugin {
const _modules = new Map<string, VirtualModule>()
for (const [id, mod] of Object.entries(modules)) {
_modules.set(id, mod)
_modules.set(resolve(id), mod)
}
return {
name: 'virtual',
resolveId (id, importer) {
if (id in modules) { return PREFIX + id }
if (importer) {
const importerNoPrefix = importer.startsWith(PREFIX)
? importer.slice(PREFIX.length)
: importer
const resolved = resolve(dirname(importerNoPrefix), id)
if (_modules.has(resolved)) { return PREFIX + resolved }
}
return null
},
async load (id) {
if (!id.startsWith(PREFIX)) { return null }
const idNoPrefix = id.slice(PREFIX.length)
if (!_modules.has(idNoPrefix)) { return null }
let m = _modules.get(idNoPrefix)
if (typeof m !== 'string' && typeof m.load === 'function') {
m = await m.load()
}
// console.log('[virtual]', idNoPrefix, '\n', m)
return {
code: m as string,
map: null
}
}
}
}

View File

@ -1,37 +0,0 @@
import destr from 'destr'
import defu from 'defu'
// Bundled runtime config (injected by nitro)
const _runtimeConfig = process.env.RUNTIME_CONFIG as any
// Allow override from process.env and deserialize
for (const type of ['private', 'public']) {
for (const key in _runtimeConfig[type]) {
_runtimeConfig[type][key] = destr(process.env[key] || _runtimeConfig[type][key])
}
}
// Load dynamic app configuration
const appConfig = _runtimeConfig.public.app
appConfig.baseURL = process.env.NUXT_APP_BASE_URL || appConfig.baseURL
appConfig.cdnURL = process.env.NUXT_APP_CDN_URL || appConfig.cdnURL
appConfig.buildAssetsDir = process.env.NUXT_APP_BUILD_ASSETS_DIR || appConfig.buildAssetsDir
// Named exports
export const privateConfig = deepFreeze(defu(_runtimeConfig.private, _runtimeConfig.public))
export const publicConfig = deepFreeze(_runtimeConfig.public)
// Default export (usable for server)
export default privateConfig
// 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

@ -1,19 +0,0 @@
import { joinURL } from 'ufo'
import config from '#config'
export function baseURL () {
return config.app.baseURL
}
export function buildAssetsDir () {
return config.app.buildAssetsDir
}
export function buildAssetsURL (...path: string[]) {
return joinURL(publicAssetsURL(), config.app.buildAssetsDir, ...path)
}
export function publicAssetsURL (...path: string[]) {
const publicBase = config.app.cdnURL || config.app.baseURL
return path.length ? joinURL(publicBase, ...path) : publicBase
}

View File

@ -1,12 +0,0 @@
import _renderToString from 'vue-server-renderer/basic'
export function renderToString (component, context) {
return new Promise((resolve, reject) => {
_renderToString(component, context, (err, result) => {
if (err) {
return reject(err)
}
return resolve(result)
})
})
}

View File

@ -1,28 +0,0 @@
import '#polyfill'
import { parseURL } from 'ufo'
import { localCall } from '../server'
export async function handle (context, req) {
let url: string
if (req.headers['x-ms-original-url']) {
// This URL has been proxied as there was no static file matching it.
url = parseURL(req.headers['x-ms-original-url']).pathname
} else {
// Because Azure SWA handles /api/* calls differently they
// never hit the proxy and we have to reconstitute the URL.
url = '/api/' + (req.params.url || '')
}
const { body, status, statusText, headers } = await localCall({
url,
headers: req.headers,
method: req.method,
body: req.body
})
context.res = {
status,
headers,
body: body ? body.toString() : statusText
}
}

View File

@ -1,19 +0,0 @@
import '#polyfill'
import { localCall } from '../server'
export async function handle (context, req) {
const url = '/' + (req.params.url || '')
const { body, status, statusText, headers } = await localCall({
url,
headers: req.headers,
method: req.method,
body: req.body
})
context.res = {
status,
headers,
body: body ? body.toString() : statusText
}
}

View File

@ -1,24 +0,0 @@
import '#polyfill'
import { localCall } from '../server'
async function cli () {
const url = process.argv[2] || '/'
const debug = (label, ...args) => console.debug(`> ${label}:`, ...args)
const r = await localCall({ url })
debug('URL', url)
debug('StatusCode', r.status)
debug('StatusMessage', r.statusText)
// @ts-ignore
for (const header of r.headers.entries()) {
debug(header[0], header[1])
}
console.log('\n', r.body.toString())
}
if (require.main === module) {
cli().catch((err) => {
console.error(err)
process.exit(1)
})
}

View File

@ -1,57 +0,0 @@
import '#polyfill'
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'
import { withoutBase } from 'ufo'
import { localCall } from '../server'
import { requestHasBody, useRequestBody } from '../server/utils'
import { buildAssetsURL, baseURL } from '#paths'
addEventListener('fetch', (event: any) => {
event.respondWith(handleEvent(event))
})
async function handleEvent (event) {
try {
return await getAssetFromKV(event, { cacheControl: assetsCacheControl, mapRequestToAsset: baseURLModifier })
} catch (_err) {
// Ignore
}
const url = new URL(event.request.url)
let body
if (requestHasBody(event.request)) {
body = await useRequestBody(event.request)
}
const r = await localCall({
event,
url: url.pathname + url.search,
host: url.hostname,
protocol: url.protocol,
headers: event.request.headers,
method: event.request.method,
redirect: event.request.redirect,
body
})
return new Response(r.body, {
// @ts-ignore
headers: r.headers,
status: r.status,
statusText: r.statusText
})
}
function assetsCacheControl (request) {
if (request.url.startsWith(buildAssetsURL())) {
return {
browserTTL: 31536000,
edgeTTL: 31536000
}
}
return {}
}
const baseURLModifier = (request: Request) => {
const url = withoutBase(request.url, baseURL())
return mapRequestToAsset(new Request(url, request))
}

View File

@ -1,36 +0,0 @@
import '#polyfill'
import { Server } from 'http'
import { tmpdir } from 'os'
import { join } from 'path'
import { mkdirSync } from 'fs'
import { threadId, parentPort } from 'worker_threads'
import { isWindows, provider } from 'std-env'
import { handle } from '../server'
const server = new Server(handle)
function getAddress () {
// https://github.com/nuxt/framework/issues/1636
if (provider === 'stackblitz' || process.env.NITRO_NO_UNIX_SOCKET) {
return '0'
}
const socketName = `worker-${process.pid}-${threadId}.sock`
if (isWindows) {
return join('\\\\.\\pipe\\nitro', socketName)
} else {
const socketDir = join(tmpdir(), 'nitro')
mkdirSync(socketDir, { recursive: true })
return join(socketDir, socketName)
}
}
const listenAddress = getAddress()
server.listen(listenAddress, () => {
const _address = server.address()
parentPort.postMessage({
event: 'listen',
address: typeof _address === 'string'
? { socketPath: _address }
: `http://localhost:${_address.port}`
})
})

View File

@ -1,7 +0,0 @@
import '#polyfill'
// @ts-ignore
import functions from 'firebase-functions'
import { handle } from '../server'
export const server = functions.https.onRequest(handle)

View File

@ -1,38 +0,0 @@
import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, APIGatewayProxyEventV2, APIGatewayProxyResult, APIGatewayProxyResultV2, Context } from 'aws-lambda'
import '#polyfill'
import { withQuery } from 'ufo'
import type { HeadersObject } from 'unenv/runtime/_internal/types'
import { localCall } from '../server'
export const handler = async function handler (event: APIGatewayProxyEvent | APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResult | APIGatewayProxyResultV2> {
const url = withQuery((event as APIGatewayProxyEvent).path || (event as APIGatewayProxyEventV2).rawPath, event.queryStringParameters)
const method = (event as APIGatewayProxyEvent).httpMethod || (event as APIGatewayProxyEventV2).requestContext?.http?.method || 'get'
if ('cookies' in event) {
event.headers.cookie = event.cookies.join(',')
}
const r = await localCall({
event,
url,
context,
headers: normalizeIncomingHeaders(event.headers),
method,
query: event.queryStringParameters,
body: event.body // TODO: handle event.isBase64Encoded
})
return {
statusCode: r.status,
headers: normalizeOutgoingHeaders(r.headers),
body: r.body.toString()
}
}
function normalizeIncomingHeaders (headers: APIGatewayProxyEventHeaders) {
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]))
}
function normalizeOutgoingHeaders (headers: HeadersObject) {
return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]))
}

View File

@ -1,5 +0,0 @@
import { builder } from '@netlify/functions'
// @ts-ignore
import { handler as _handler } from '#nitro/entries/lambda'
export const handler = builder(_handler)

View File

@ -1,2 +0,0 @@
import '#polyfill'
export * from '../server'

View File

@ -1,26 +0,0 @@
import '#polyfill'
import { Server as HttpServer } from 'http'
import { Server as HttpsServer } from 'https'
import destr from 'destr'
import { handle } from '../server'
import { baseURL } from '#paths'
const cert = process.env.NITRO_SSL_CERT
const key = process.env.NITRO_SSL_KEY
const server = cert && key ? new HttpsServer({ key, cert }, handle) : new HttpServer(handle)
const port = (destr(process.env.NUXT_PORT || process.env.PORT) || 3000) as number
const hostname = process.env.NUXT_HOST || process.env.HOST || 'localhost'
// @ts-ignore
server.listen(port, hostname, (err) => {
if (err) {
console.error(err)
process.exit(1)
}
const protocol = cert && key ? 'https' : 'http'
console.log(`Listening on ${protocol}://${hostname}:${port}${baseURL()}`)
})
export default {}

View File

@ -1,48 +0,0 @@
// @ts-nocheck
import '#polyfill'
import { localCall } from '../server'
import { requestHasBody, useRequestBody } from '../server/utils'
const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION
addEventListener('fetch', (event: any) => {
const url = new URL(event.request.url)
if (url.pathname.includes('.') && !url.pathname.startsWith(STATIC_ASSETS_BASE) && !url.pathname.startsWith('/api')) {
return
}
event.respondWith(handleEvent(url, event))
})
async function handleEvent (url, event) {
let body
if (requestHasBody(event.request)) {
body = await useRequestBody(event.request)
}
const r = await localCall({
event,
url: url.pathname + url.search,
host: url.hostname,
protocol: url.protocol,
headers: event.request.headers,
method: event.request.method,
redirect: event.request.redirect,
body
})
return new Response(r.body, {
headers: r.headers,
status: r.status,
statusText: r.statusText
})
}
self.addEventListener('install', () => {
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})

View File

@ -1,4 +0,0 @@
import '#polyfill'
import { handle } from '../server'
export default handle

View File

@ -1,64 +0,0 @@
// import ansiHTML from 'ansi-html'
import type { IncomingMessage, ServerResponse } from 'http'
import { withQuery } from 'ufo'
import { $fetch } from '.'
const cwd = process.cwd()
const hasReqHeader = (req, header, includes) => req.headers[header] && req.headers[header].toLowerCase().includes(includes)
const isDev = process.env.NODE_ENV === 'development'
export async function handleError (error, req: IncomingMessage, res: ServerResponse) {
const isJsonRequest = hasReqHeader(req, 'accept', 'application/json') || hasReqHeader(req, 'user-agent', 'curl/') || hasReqHeader(req, 'user-agent', 'httpie/')
const stack = (error.stack || '')
.split('\n')
.splice(1)
.filter(line => line.includes('at '))
.map((line) => {
const text = line
.replace(cwd + '/', './')
.replace('webpack:/', '')
.replace('.vue', '.js') // TODO: Support sourcemap
.trim()
return {
text,
internal: (line.includes('node_modules') && !line.includes('.cache')) ||
line.includes('internal') ||
line.includes('new Promise')
}
})
const is404 = error.statusCode === 404
const errorObject = {
url: req.url,
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage ?? is404 ? 'Page Not Found' : 'Internal Server Error',
message: error.message || error.toString(),
description: isDev && !is404
? `<pre>${stack.map(i => `<span class="stack${i.internal ? ' internal' : ''}">${i.text}</span>`).join('\n')}</pre>`
: ''
}
res.statusCode = errorObject.statusCode
res.statusMessage = errorObject.statusMessage
// Console output
if (!is404) {
console.error(error.message + '\n' + stack.map(l => ' ' + l.text).join(' \n'))
}
// JSON response
if (isJsonRequest) {
res.setHeader('Content-Type', 'application/json')
return res.end(JSON.stringify(errorObject))
}
// HTML response
const url = withQuery('/_nitro/__error', errorObject)
const html = await $fetch(url).catch(() => errorObject.statusMessage)
res.setHeader('Content-Type', 'text/html;charset=UTF-8')
res.end(html)
}

View File

@ -1,30 +0,0 @@
import { createApp, lazyHandle, useBase } from 'h3'
import { createFetch, Headers } from 'ohmyfetch'
import destr from 'destr'
import { createCall, createFetch as createLocalFetch } from 'unenv/runtime/fetch/index'
import { baseURL } from '../app/paths'
import { timingMiddleware } from './timing'
import { handleError } from './error'
// @ts-ignore
import serverMiddleware from '#server-middleware'
const app = createApp({
debug: destr(process.env.DEBUG),
onError: handleError
})
const renderMiddleware = lazyHandle(() => import('../app/render').then(e => e.renderMiddleware))
app.use('/_nitro', renderMiddleware)
app.use(timingMiddleware)
app.use(serverMiddleware)
app.use(renderMiddleware)
export const stack = app.stack
export const handle = useBase(baseURL(), app)
export const localCall = createCall(handle)
export const localFetch = createLocalFetch(localCall, globalThis.fetch)
export const $fetch = createFetch({ fetch: localFetch, Headers })
globalThis.$fetch = $fetch as any

View File

@ -1,73 +0,0 @@
import { createError } from 'h3'
import { withoutTrailingSlash, withLeadingSlash, parseURL } from 'ufo'
// @ts-ignore
import { getAsset, readAsset } from '#static'
import { buildAssetsDir } from '#paths'
const METHODS = ['HEAD', 'GET']
const TWO_DAYS = 2 * 60 * 60 * 24
const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION
export default async function serveStatic (req, res) {
if (!METHODS.includes(req.method)) {
return
}
let id = decodeURIComponent(withLeadingSlash(withoutTrailingSlash(parseURL(req.url).pathname)))
let asset: string
for (const _id of [id, id + '/index.html']) {
const _asset = getAsset(_id)
if (_asset) {
asset = _asset
id = _id
break
}
}
const isBuildAsset = id.startsWith(buildAssetsDir())
if (!asset) {
if (isBuildAsset && !id.startsWith(STATIC_ASSETS_BASE)) {
throw createError({
statusMessage: 'Cannot find static asset ' + id,
statusCode: 404
})
}
return
}
const ifNotMatch = req.headers['if-none-match'] === asset.etag
if (ifNotMatch) {
res.statusCode = 304
return res.end('Not Modified (etag)')
}
const ifModifiedSinceH = req.headers['if-modified-since']
if (ifModifiedSinceH && asset.mtime) {
if (new Date(ifModifiedSinceH) >= new Date(asset.mtime)) {
res.statusCode = 304
return res.end('Not Modified (mtime)')
}
}
if (asset.type) {
res.setHeader('Content-Type', asset.type)
}
if (asset.etag) {
res.setHeader('ETag', asset.etag)
}
if (asset.mtime) {
res.setHeader('Last-Modified', asset.mtime)
}
if (isBuildAsset) {
res.setHeader('Cache-Control', `max-age=${TWO_DAYS}, immutable`)
}
const contents = await readAsset(id)
return res.end(contents)
}

View File

@ -1,22 +0,0 @@
export const globalTiming = globalThis.__timing__ || {
start: () => 0,
end: () => 0,
metrics: []
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
export function timingMiddleware (_req, res, next) {
const start = globalTiming.start()
const _end = res.end
res.end = (data, encoding, callback) => {
const metrics = [['Generate', globalTiming.end(start)], ...globalTiming.metrics]
const serverTiming = metrics.map(m => `-;dur=${m[1]};desc="${encodeURIComponent(m[0])}"`).join(', ')
if (!res.headersSent) {
res.setHeader('Server-Timing', serverTiming)
}
_end.call(res, data, encoding, callback)
}
next()
}

View File

@ -1,26 +0,0 @@
const METHOD_WITH_BODY_RE = /post|put|patch/i
const TEXT_MIME_RE = /application\/text|text\/html/
const JSON_MIME_RE = /application\/json/
export function requestHasBody (request: globalThis.Request) : boolean {
return METHOD_WITH_BODY_RE.test(request.method)
}
export async function useRequestBody (request: globalThis.Request): Promise<any> {
const contentType = request.headers.get('content-type') || ''
if (contentType.includes('form')) {
const formData = await request.formData()
const body = Object.create(null)
for (const entry of formData.entries()) {
body[entry[0]] = entry[1]
}
return body
} else if (JSON_MIME_RE.test(contentType)) {
return request.json()
} else if (TEXT_MIME_RE.test(contentType)) {
return request.text()
} else {
const blob = await request.blob()
return URL.createObjectURL(blob)
}
}

View File

@ -1,11 +0,0 @@
declare global {
namespace NodeJS {
interface Global {
__timing__: any
$config: any
}
}
}
// export required to turn this into a module for TS augmentation purposes
export { }

View File

@ -1,182 +0,0 @@
import { Worker } from 'worker_threads'
import { IncomingMessage, ServerResponse } from 'http'
import { existsSync, promises as fsp } from 'fs'
import { loading as loadingTemplate } from '@nuxt/ui-templates'
import chokidar, { FSWatcher } from 'chokidar'
import { debounce } from 'perfect-debounce'
import { promisifyHandle, createApp, Middleware, useBase } from 'h3'
import httpProxy from 'http-proxy'
import { listen, Listener, ListenOptions } from 'listhen'
import servePlaceholder from 'serve-placeholder'
import serveStatic from 'serve-static'
import { resolve } from 'pathe'
import connect from 'connect'
import { joinURL } from 'ufo'
import type { NitroContext } from '../context'
import { handleVfs } from './vfs'
export interface NitroWorker {
worker: Worker,
address: string
}
function initWorker (filename): Promise<NitroWorker> {
return new Promise((resolve, reject) => {
const worker = new Worker(filename)
worker.once('exit', (code) => {
if (code) {
reject(new Error('[worker] exited with code: ' + code))
}
})
worker.on('error', (err) => {
console.error('[worker]', err)
err.message = '[worker] ' + err.message
reject(err)
})
worker.on('message', (event) => {
if (event && event.address) {
resolve({
worker,
address: event.address
} as NitroWorker)
}
})
})
}
async function killWorker (worker?: NitroWorker) {
if (!worker) {
return
}
await worker.worker?.terminate()
worker.worker = null
if (worker.address && existsSync(worker.address)) {
await fsp.rm(worker.address).catch(() => {})
}
}
export function createDevServer (nitroContext: NitroContext) {
// Worker
const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.mjs')
let currentWorker: NitroWorker
async function reload () {
// Create a new worker
const newWorker = await initWorker(workerEntry)
// Kill old worker in background
killWorker(currentWorker).catch(err => console.error(err))
// Replace new worker as current
currentWorker = newWorker
}
// App
const app = createApp()
// _nuxt and static
const buildAssetsURL = joinURL(nitroContext._nuxt.baseURL, nitroContext._nuxt.buildAssetsDir)
app.use(buildAssetsURL, serveStatic(resolve(nitroContext._nuxt.buildDir, 'dist/client')))
app.use(nitroContext._nuxt.baseURL, serveStatic(resolve(nitroContext._nuxt.publicDir)))
// debugging endpoint to view vfs
app.use('/_vfs', useBase('/_vfs', handleVfs(nitroContext)))
// Dynamic Middlwware
const legacyMiddleware = createDynamicMiddleware()
const devMiddleware = createDynamicMiddleware()
app.use(legacyMiddleware.middleware)
app.use(devMiddleware.middleware)
// serve placeholder 404 assets instead of hitting SSR
app.use(buildAssetsURL, servePlaceholder())
// SSR Proxy
const proxy = httpProxy.createProxy()
const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => {
proxy.web(req, res, { target: currentWorker.address }, (error: unknown) => {
console.error('[proxy]', error)
})
})
app.use((req, res) => {
if (currentWorker?.address) {
// Workaround to pass legacy req.spa to proxy
// @ts-ignore
if (req.spa) {
req.headers['x-nuxt-no-ssr'] = 'true'
}
return proxyHandle(req, res)
} else {
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.end(loadingTemplate({}))
}
})
// Listen
let listeners: Listener[] = []
const _listen = async (port: ListenOptions['port'], opts?: Partial<ListenOptions>) => {
const listener = await listen(app, { port, ...opts })
listeners.push(listener)
return listener
}
// Watch for dist and reload worker
const pattern = '**/*.{js,json,cjs,mjs}'
const events = ['add', 'change']
let watcher: FSWatcher
function watch () {
if (watcher) { return }
const dReload = debounce(() => reload().catch(console.warn))
watcher = chokidar.watch([
resolve(nitroContext.output.serverDir, pattern),
resolve(nitroContext._nuxt.buildDir, 'dist/server', pattern)
]).on('all', event => events.includes(event) && dReload())
}
// Close handler
async function close () {
if (watcher) {
await watcher.close()
}
await killWorker(currentWorker)
await Promise.all(listeners.map(l => l.close()))
listeners = []
}
nitroContext._internal.hooks.hook('close', close)
return {
reload,
listen: _listen,
app,
close,
watch,
setLegacyMiddleware: legacyMiddleware.set,
setDevMiddleware: devMiddleware.set
}
}
interface DynamicMiddleware {
set: (input: Middleware) => void
middleware: Middleware
}
function createDynamicMiddleware (): DynamicMiddleware {
let middleware: Middleware
return {
set: (input) => {
if (!Array.isArray(input)) {
middleware = input
return
}
const app = connect()
for (const m of input) {
app.use(m.path || m.route || '/', m.handler || m.handle!)
}
middleware = app
},
middleware: (req, res, next) =>
middleware ? middleware(req, res, next) : next()
}
}

View File

@ -1,96 +0,0 @@
import { resolve, join, extname } from 'pathe'
import { joinURL } from 'ufo'
import { globby } from 'globby'
import { watch } from 'chokidar'
import { resolvePath } from '@nuxt/kit'
import type { Nuxt } from '@nuxt/schema'
import type { Middleware } from 'h3'
export interface ServerMiddleware {
route: string
/**
* @deprecated use route
*/
path?: string
handle?: Middleware | string
/**
* @deprecated use handle
*/
handler?: Middleware | string
lazy?: boolean // Default is true
promisify?: boolean // Default is true
}
function filesToMiddleware (files: string[], baseDir: string, baseURL: string, overrides?: Partial<ServerMiddleware>): ServerMiddleware[] {
return files.map((file) => {
const route = joinURL(
baseURL,
file
.slice(0, file.length - extname(file).length)
.replace(/\/index$/, '')
)
const handle = resolve(baseDir, file)
return {
route,
handle
}
})
.sort((a, b) => b.route.localeCompare(a.route))
.map(m => ({ ...m, ...overrides }))
}
export function scanMiddleware (serverDir: string, onChange?: (results: ServerMiddleware[], event: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', file: string) => void): Promise<ServerMiddleware[]> {
const pattern = '**/*.{ts,mjs,js,cjs}'
const globalDir = resolve(serverDir, 'middleware')
const apiDir = resolve(serverDir, 'api')
const scan = async () => {
const globalFiles = await globby(pattern, { cwd: globalDir, dot: true })
const apiFiles = await globby(pattern, { cwd: apiDir, dot: true })
return [
...filesToMiddleware(globalFiles, globalDir, '/', { route: '/' }),
...filesToMiddleware(apiFiles, apiDir, '/api', { lazy: true })
]
}
if (typeof onChange === 'function') {
const watcher = watch([
join(globalDir, pattern),
join(apiDir, pattern)
], { ignoreInitial: true })
watcher.on('all', async (event, file) => {
onChange(await scan(), event, file)
})
}
return scan()
}
export async function resolveMiddleware (nuxt: Nuxt) {
const middleware: ServerMiddleware[] = []
const legacyMiddleware: ServerMiddleware[] = []
for (let m of nuxt.options.serverMiddleware) {
if (typeof m === 'string' || typeof m === 'function' /* legacy middleware */) { m = { handler: m } }
const route = m.path || m.route || '/'
const handle = m.handler || m.handle
if (typeof handle !== 'string' || typeof route !== 'string') {
legacyMiddleware.push(m)
} else {
delete m.handler
delete m.path
middleware.push({
...m,
handle: await resolvePath(handle),
route
})
}
}
return {
middleware,
legacyMiddleware
}
}

View File

@ -1,54 +0,0 @@
import { createError, Handle } from 'h3'
import { NitroContext } from '..'
export function handleVfs (ctx: NitroContext): Handle {
return (req) => {
if (req.url === '/') {
const items = Object.keys(ctx.vfs)
.filter(i => !i.startsWith('#'))
.map(key => `<li><a href="/_vfs/${encodeURIComponent(key)}">${key.replace(ctx._nuxt.rootDir, '')}</a></li>`)
.join('\n')
return `<!doctype html><html><body><ul>${items}</ul></body></html>`
}
const param = decodeURIComponent(req.url.slice(1))
if (param in ctx.vfs) {
return editorTemplate({
readOnly: true,
language: param.endsWith('html') ? 'html' : 'javascript',
theme: 'vs-dark',
value: ctx.vfs[param]
})
}
return createError({ message: 'File not found', statusCode: 404 })
}
}
const monacoVersion = '0.30.0'
const monacoUrl = `https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/${monacoVersion}/min`
const vsUrl = `${monacoUrl}/vs`
const editorTemplate = (options: Record<string, any>) => `
<!doctype html>
<html>
<head>
<link rel="stylesheet" data-name="vs/editor/editor.main" href="${vsUrl}/editor/editor.main.min.css">
</head>
<body style="margin: 0">
<div id="editor" style="height:100vh"></div>
<script src="${vsUrl}/loader.min.js"></script>
<script>
require.config({ paths: { vs: '${vsUrl}' } })
const proxy = URL.createObjectURL(new Blob([\`
self.MonacoEnvironment = { baseUrl: '${monacoUrl}' }
importScripts('${vsUrl}/base/worker/workerMain.min.js')
\`], { type: 'text/javascript' }))
window.MonacoEnvironment = { getWorkerUrl: () => proxy }
require(['vs/editor/editor.main'], function () {
monaco.editor.create(document.getElementById('editor'), ${JSON.stringify(options)})
})
</script>
</body>
</html>
`

View File

@ -1,154 +0,0 @@
import { createRequire } from 'module'
import { relative, dirname, resolve } from 'pathe'
import fse from 'fs-extra'
import jiti from 'jiti'
import defu from 'defu'
import { mergeHooks } from 'hookable'
import consola from 'consola'
import chalk from 'chalk'
import { getProperty } from 'dot-prop'
import type { NitroPreset, NitroInput } from '../context'
export function hl (str: string) {
return chalk.cyan(str)
}
export function prettyPath (p: string, highlight = true) {
p = relative(process.cwd(), p)
return highlight ? hl(p) : p
}
export function compileTemplate (contents: string) {
return (params: Record<string, any>) => contents.replace(/{{ ?([\w.]+) ?}}/g, (_, match) => {
const val = getProperty(params, match)
if (!val) {
consola.warn(`cannot resolve template param '${match}' in ${contents.slice(0, 20)}`)
}
return val as string || `${match}`
})
}
export function serializeTemplate (contents: string) {
// eslint-disable-next-line no-template-curly-in-string
return `(params) => \`${contents.replace(/{{ (\w+) }}/g, '${params.$1}')}\``
}
export function jitiImport (dir: string, path: string) {
return jiti(dir, { interopDefault: true })(path)
}
export function tryImport (dir: string, path: string) {
try {
return jitiImport(dir, path)
} catch (_err) { }
}
export async function writeFile (file: string, contents: string, log = false) {
await fse.mkdirp(dirname(file))
await fse.writeFile(file, contents, 'utf-8')
if (log) {
consola.info('Generated', prettyPath(file))
}
}
export function evalTemplate (ctx, input: string | ((ctx) => string)): string {
if (typeof input === 'function') {
input = input(ctx)
}
if (typeof input !== 'string') {
throw new TypeError('Invalid template: ' + input)
}
return compileTemplate(input)(ctx)
}
export function resolvePath (nitroContext: NitroInput, input: string | ((nitroContext: NitroInput) => string), resolveBase: string = ''): string {
return resolve(resolveBase, evalTemplate(nitroContext, input))
}
export function replaceAll (input: string, from: string, to: string) {
return input.replace(new RegExp(from, 'g'), to)
}
export function detectTarget () {
if (process.env.NETLIFY || process.env.NETLIFY_LOCAL) {
return 'netlify'
}
if (process.env.NOW_BUILDER) {
return 'vercel'
}
if (process.env.INPUT_AZURE_STATIC_WEB_APPS_API_TOKEN) {
return 'azure'
}
}
export async function isDirectory (path: string) {
try {
return (await fse.stat(path)).isDirectory()
} catch (_err) {
return false
}
}
export function extendPreset (base: NitroPreset, preset: NitroPreset): NitroPreset {
return (config: NitroInput) => {
if (typeof preset === 'function') {
preset = preset(config)
}
if (typeof base === 'function') {
base = base(config)
}
return defu({
hooks: mergeHooks(base.hooks, preset.hooks)
}, preset, base)
}
}
const _getDependenciesMode = {
dev: ['devDependencies'],
prod: ['dependencies'],
all: ['devDependencies', 'dependencies']
}
const _require = createRequire(import.meta.url)
export function getDependencies (dir: string, mode: keyof typeof _getDependenciesMode = 'all') {
const fields = _getDependenciesMode[mode]
const pkg = _require(resolve(dir, 'package.json'))
const dependencies = []
for (const field of fields) {
if (pkg[field]) {
for (const name in pkg[field]) {
dependencies.push(name)
}
}
}
return dependencies
}
// TODO: Refactor to scule (https://github.com/unjs/scule/issues/6)
export function serializeImportName (id: string) {
return '_' + id.replace(/[^a-zA-Z0-9_$]/g, '_')
}
export function readPackageJson (
packageName: string,
_require: NodeRequire = createRequire(import.meta.url)
) {
try {
return _require(`${packageName}/package.json`)
} catch (error) {
if (error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
const pkgModulePaths = /^(.*\/node_modules\/).*$/.exec(_require.resolve(packageName))
for (const pkgModulePath of pkgModulePaths) {
const path = resolve(pkgModulePath, packageName, 'package.json')
if (fse.existsSync(path)) {
return fse.readJSONSync(path)
}
continue
}
throw error
}
throw error
}
}

View File

@ -1,50 +0,0 @@
import { promises as fsp } from 'fs'
import { resolve, dirname, relative } from 'pathe'
import { globby } from 'globby'
import prettyBytes from 'pretty-bytes'
import { gzipSize } from 'gzip-size'
import chalk from 'chalk'
import { isTest } from 'std-env'
export async function printFSTree (dir: string) {
if (isTest) {
return
}
const files = await globby('**/*.*', { cwd: dir })
const items = (await Promise.all(files.map(async (file) => {
const path = resolve(dir, file)
const src = await fsp.readFile(path)
const size = src.byteLength
const gzip = await gzipSize(src)
return { file, path, size, gzip }
}))).sort((a, b) => b.path.localeCompare(a.path))
let totalSize = 0
let totalGzip = 0
let totalNodeModulesSize = 0
let totalNodeModulesGzip = 0
items.forEach((item, index) => {
let dir = dirname(item.file)
if (dir === '.') { dir = '' }
const rpath = relative(process.cwd(), item.path)
const treeChar = index === items.length - 1 ? '└─' : '├─'
const isNodeModules = item.file.includes('node_modules')
if (isNodeModules) {
totalNodeModulesSize += item.size
totalNodeModulesGzip += item.gzip
return
}
process.stdout.write(chalk.gray(` ${treeChar} ${rpath} (${prettyBytes(item.size)}) (${prettyBytes(item.gzip)} gzip)\n`))
totalSize += item.size
totalGzip += item.gzip
})
process.stdout.write(`${chalk.cyan('Σ Total size:')} ${prettyBytes(totalSize + totalNodeModulesSize)} (${prettyBytes(totalGzip + totalNodeModulesGzip)} gzip)\n`)
}

View File

@ -1,7 +0,0 @@
import { join } from 'pathe'
import fsExtra from 'fs-extra'
export const wpfs = {
...fsExtra,
join
} as any

View File

@ -1,41 +0,0 @@
import type { FetchRequest, FetchOptions, FetchResponse } from 'ohmyfetch'
// An interface to extend in a local project
export declare interface InternalApi { }
export declare type ValueOf<C> = C extends Record<any, any> ? C[keyof C] : never
export declare type MatchedRoutes<Route extends string> = ValueOf<{
// exact match, prefix match or root middleware
[key in keyof InternalApi]: Route extends key | `${key}/${string}` | '/' ? key : never
}>
export declare type MiddlewareOf<Route extends string> = Exclude<InternalApi[MatchedRoutes<Route>], Error | void>
export declare type TypedInternalResponse<Route, Default> =
Default extends string | boolean | number | null | void | object
// Allow user overrides
? Default
: Route extends string
? MiddlewareOf<Route> extends never
// Bail if only types are Error or void (for example, from middleware)
? Default
: MiddlewareOf<Route>
: Default
export declare interface $Fetch<DefaultT = unknown, DefaultR extends FetchRequest = FetchRequest> {
<T = DefaultT, R extends FetchRequest = DefaultR> (request: R, opts?: FetchOptions): Promise<TypedInternalResponse<R, T>>
raw<T = DefaultT, R extends FetchRequest = DefaultR> (request: R, opts?: FetchOptions): Promise<FetchResponse<TypedInternalResponse<R, T>>>
}
declare global {
// eslint-disable-next-line no-var
var $fetch: $Fetch
namespace NodeJS {
interface Global {
$fetch: $Fetch
}
}
}
export { }

View File

@ -1,11 +0,0 @@
import './shims'
declare module '@nuxt/schema' {
import type { NitroInput } from '../dist'
interface NuxtConfig {
nitro?: NitroInput
}
}
export * from './fetch'
export * from '../dist'

View File

@ -1,31 +0,0 @@
declare module '#storage' {
import type { Storage } from 'unstorage'
export const storage: Storage
}
declare module '#assets' {
export interface AssetMeta { type?: string, etag?: string, mtime?: string }
export const assets: {
getKeys(): Promise<string[]>
hasItem(id: string): Promise<boolean>
getItem<T = any> (id: string): Promise<T>
getMeta(id: string): Promise<AssetMeta>
}
}
declare module '#config' {
import type { PublicRuntimeConfig, PrivateRuntimeConfig } from '@nuxt/schema'
export const privateConfig: PrivateRuntimeConfig
export const publicConfig: PublicRuntimeConfig
const runtimeConfig: PrivateRuntimeConfig & PublicRuntimeConfig
export default runtimeConfig
}
declare module '#paths' {
export const baseURL: () => string
export const buildAssetsDir: () => string
export const buildAssetsURL: (...path: string[]) => string
export const publicAssetsURL: (...path: string[]) => string
}

View File

@ -19,7 +19,6 @@
"devDependencies": { "devDependencies": {
"@nuxt/kit": "3.0.0", "@nuxt/kit": "3.0.0",
"@nuxt/schema": "3.0.0", "@nuxt/schema": "3.0.0",
"@nuxt/ui-templates": "npm:@nuxt/ui-templates-edge@latest",
"@types/clear": "^0", "@types/clear": "^0",
"@types/mri": "^1.1.1", "@types/mri": "^1.1.1",
"@types/rimraf": "^3", "@types/rimraf": "^3",

View File

@ -1,7 +1,7 @@
import { promises as fsp } from 'fs' import { promises as fsp } from 'fs'
import { join, resolve } from 'pathe' import { join, resolve } from 'pathe'
import { createApp, lazyHandle } from 'h3' import { createApp, lazyHandle } from 'h3'
import { createServer } from '../utils/server' import { listen } from 'listhen'
import { writeTypes } from '../utils/prepare' import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit' import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs' import { clearDir } from '../utils/fs'
@ -35,7 +35,6 @@ export default defineNuxtCommand({
await buildNuxt(nuxt) await buildNuxt(nuxt)
const app = createApp() const app = createApp()
const server = createServer(app)
const serveFile = (filePath: string) => lazyHandle(async () => { const serveFile = (filePath: string) => lazyHandle(async () => {
const contents = await fsp.readFile(filePath, 'utf-8') const contents = await fsp.readFile(filePath, 'utf-8')
@ -65,6 +64,6 @@ export default defineNuxtCommand({
</ul> </ul>
</html>`) </html>`)
await server.listen() await listen(app)
} }
}) })

View File

@ -8,7 +8,7 @@ import { defineNuxtCommand } from './index'
export default defineNuxtCommand({ export default defineNuxtCommand({
meta: { meta: {
name: 'build', name: 'build',
usage: 'npx nuxi build [rootDir]', usage: 'npx nuxi build [--prerender] [rootDir]',
description: 'Build nuxt for production deployment' description: 'Build nuxt for production deployment'
}, },
async invoke (args) { async invoke (args) {
@ -17,7 +17,12 @@ export default defineNuxtCommand({
const { loadNuxt, buildNuxt } = await loadKit(rootDir) const { loadNuxt, buildNuxt } = await loadKit(rootDir)
const nuxt = await loadNuxt({ rootDir }) const nuxt = await loadNuxt({
rootDir,
overrides: {
_generate: args.prerender
}
})
await clearDir(nuxt.options.buildDir) await clearDir(nuxt.options.buildDir)

View File

@ -4,11 +4,10 @@ import { debounce } from 'perfect-debounce'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import consola from 'consola' import consola from 'consola'
import { withTrailingSlash } from 'ufo' import { withTrailingSlash } from 'ufo'
import { createServer, createLoadingHandler } from '../utils/server'
import { showBanner } from '../utils/banner' import { showBanner } from '../utils/banner'
import { writeTypes } from '../utils/prepare' import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit' import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs' import { importModule } from '../utils/cjs'
import { defineNuxtCommand } from './index' import { defineNuxtCommand } from './index'
export default defineNuxtCommand({ export default defineNuxtCommand({
@ -19,8 +18,21 @@ export default defineNuxtCommand({
}, },
async invoke (args) { async invoke (args) {
process.env.NODE_ENV = process.env.NODE_ENV || 'development' process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const server = createServer()
const listener = await server.listen({ const { listen } = await import('listhen')
let currentHandler
let loadingMessage = 'Nuxt is starting...'
const loadingHandler = async (_req, res) => {
const { loading: loadingTemplate } = await importModule('@nuxt/ui-templates')
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.statusCode = 503 // Service Unavailable
res.end(loadingTemplate({ loading: loadingMessage }))
}
const serverHandler = (req, res) => {
return currentHandler ? currentHandler(req, res) : loadingHandler(req, res)
}
const listener = await listen(serverHandler, {
clipboard: args.clipboard, clipboard: args.clipboard,
open: args.open || args.o, open: args.open || args.o,
port: args.port || args.p || process.env.NUXT_PORT, port: args.port || args.p || process.env.NUXT_PORT,
@ -39,31 +51,29 @@ export default defineNuxtCommand({
let currentNuxt: Nuxt let currentNuxt: Nuxt
const load = async (isRestart: boolean, reason?: string) => { const load = async (isRestart: boolean, reason?: string) => {
try { try {
const message = `${reason ? reason + '. ' : ''}${isRestart ? 'Restarting' : 'Starting'} nuxt...` loadingMessage = `${reason ? reason + '. ' : ''}${isRestart ? 'Restarting' : 'Starting'} nuxt...`
server.setApp(createLoadingHandler(message)) currentHandler = null
if (isRestart) { if (isRestart) {
consola.info(message) consola.info(loadingMessage)
} }
if (currentNuxt) { if (currentNuxt) {
await currentNuxt.close() await currentNuxt.close()
} }
currentNuxt = await loadNuxt({ rootDir, dev: true, ready: false }) currentNuxt = await loadNuxt({ rootDir, dev: true, ready: false })
await clearDir(currentNuxt.options.buildDir)
await currentNuxt.ready() await currentNuxt.ready()
await Promise.all([ await Promise.all([
writeTypes(currentNuxt).catch(console.error), writeTypes(currentNuxt).catch(console.error),
buildNuxt(currentNuxt) buildNuxt(currentNuxt)
]) ])
server.setApp(currentNuxt.server.app) currentHandler = currentNuxt.server.app
if (isRestart && args.clear !== false) { if (isRestart && args.clear !== false) {
showBanner() showBanner()
listener.showURL() listener.showURL()
} }
} catch (err) { } catch (err) {
consola.error(`Cannot ${isRestart ? 'restart' : 'start'} nuxt: `, err) consola.error(`Cannot ${isRestart ? 'restart' : 'start'} nuxt: `, err)
server.setApp(createLoadingHandler( currentHandler = null
'Error while loading nuxt. Please check console and fix errors.' loadingMessage = 'Error while loading nuxt. Please check console and fix errors.'
))
} }
} }

View File

@ -1,32 +1,14 @@
import { execa } from 'execa' import buildCommand from './build'
import { resolve } from 'pathe'
import { isNuxt3 } from '@nuxt/kit'
import { loadKit } from '../utils/kit'
import { writeTypes } from '../utils/prepare'
import { defineNuxtCommand } from './index' import { defineNuxtCommand } from './index'
export default defineNuxtCommand({ export default defineNuxtCommand({
meta: { meta: {
name: 'generate', name: 'generate',
usage: 'npx nuxi generate [rootDir]', usage: 'npx nuxi generate [rootDir]',
description: '' description: 'Build Nuxt and prerender static routes'
}, },
async invoke (args) { async invoke (args) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production' args.prerender = true
const rootDir = resolve(args._[0] || '.') await buildCommand.invoke(args)
const { loadNuxt } = await loadKit(rootDir)
const nuxt = await loadNuxt({ rootDir, config: { _export: true } })
if (isNuxt3(nuxt)) {
throw new Error('`nuxt generate` is not supported in Nuxt 3. Please follow this RFC: https://git.io/JKfvx')
} else {
// Generate types and close nuxt instance
await writeTypes(nuxt)
await nuxt.close()
// Forwards argv to `nuxt generate`
await execa('npx', ['nuxt', ...process.argv.slice(2)], { stdio: 'inherit' })
}
} }
}) })

View File

@ -33,11 +33,7 @@ export const writeTypes = async (nuxt: Nuxt) => {
const aliases = { const aliases = {
...nuxt.options.alias, ...nuxt.options.alias,
'#build': nuxt.options.buildDir, '#build': nuxt.options.buildDir
// The `@nuxt/nitro` types will be overwritten by packages/nitro/types/shims.d.ts
'#config': '@nuxt/nitro',
'#storage': '@nuxt/nitro',
'#assets': '@nuxt/nitro'
} }
// Exclude bridge alias types to support Volar // Exclude bridge alias types to support Volar

View File

@ -1,33 +0,0 @@
import type { RequestListener } from 'http'
import type { ListenOptions } from 'listhen'
import { loading } from '@nuxt/ui-templates'
export function createServer (defaultApp?) {
const listener = createDynamicFunction(defaultApp || createLoadingHandler('Loading...'))
async function listen (opts?: Partial<ListenOptions>) {
const { listen } = await import('listhen')
return listen(listener.call, opts)
}
return {
setApp: (app: RequestListener) => listener.set(app),
listen
}
}
export function createLoadingHandler (message: string): RequestListener {
return (_req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.statusCode = 503 /* Service Unavailable */
res.end(loading({ loading: message }))
}
}
function createDynamicFunction<T extends (...args: any[]) => any> (initialValue: T) {
let fn = initialValue
return {
set: (newFn: T) => { fn = newFn },
call: ((...args: Parameters<T>) => fn(...args)) as T
}
}

View File

@ -9,6 +9,7 @@ export default defineBuildConfig({
{ input: 'src/app/', outDir: 'dist/app/' }, { input: 'src/app/', outDir: 'dist/app/' },
// Runtime dirs // Runtime dirs
...[ ...[
'core',
'head', 'head',
'pages' 'pages'
].map(name => ({ input: `src/${name}/runtime/`, outDir: `dist/${name}/runtime`, format: 'esm' } as BuildEntry)) ].map(name => ({ input: `src/${name}/runtime/`, outDir: `dist/${name}/runtime`, format: 'esm' } as BuildEntry))

View File

@ -31,7 +31,6 @@
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "3.0.0", "@nuxt/kit": "3.0.0",
"@nuxt/nitro": "3.0.0",
"@nuxt/schema": "3.0.0", "@nuxt/schema": "3.0.0",
"@nuxt/ui-templates": "npm:@nuxt/ui-templates-edge@latest", "@nuxt/ui-templates": "npm:@nuxt/ui-templates-edge@latest",
"@nuxt/vite-builder": "3.0.0", "@nuxt/vite-builder": "3.0.0",
@ -45,13 +44,13 @@
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",
"globby": "^13.1.1", "globby": "^13.1.1",
"h3": "^0.4.2", "h3": "^0.7.1",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"hookable": "^5.1.1", "hookable": "^5.1.1",
"knitwork": "^0.1.1", "knitwork": "^0.1.1",
"magic-string": "^0.26.1", "magic-string": "^0.26.1",
"mlly": "^0.5.1", "mlly": "^0.5.1",
"nitropack": "npm:nitropack-edge@latest", "nitropack": "^0.1.0",
"nuxi": "3.0.0", "nuxi": "3.0.0",
"ohash": "^0.1.0", "ohash": "^0.1.0",
"ohmyfetch": "^0.4.15", "ohmyfetch": "^0.4.15",
@ -60,10 +59,12 @@
"scule": "^0.2.1", "scule": "^0.2.1",
"ufo": "^0.8.3", "ufo": "^0.8.3",
"unctx": "^1.1.4", "unctx": "^1.1.4",
"unenv": "^0.4.3",
"unimport": "^0.1.4", "unimport": "^0.1.4",
"unplugin": "^0.6.1", "unplugin": "^0.6.1",
"untyped": "^0.4.4", "untyped": "^0.4.4",
"vue": "^3.2.31", "vue": "^3.2.31",
"vue-bundle-renderer": "^0.3.5",
"vue-router": "^4.0.14" "vue-router": "^4.0.14"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,9 +1,9 @@
import type { ServerResponse } from 'http'
import { Ref, ref, watch } from 'vue' import { Ref, ref, watch } from 'vue'
import { parse, serialize, CookieParseOptions, CookieSerializeOptions } from 'cookie-es' import { parse, serialize, CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
import { appendHeader } from 'h3' import { appendHeader } from 'h3'
import type { NuxtApp } from '@nuxt/schema' import type { CompatibilityEvent } from 'h3'
import destr from 'destr' import destr from 'destr'
import { useRequestEvent } from './ssr'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode' | 'encode'> type _CookieOptions = Omit<CookieSerializeOptions & CookieParseOptions, 'decode' | 'encode'>
@ -34,8 +34,7 @@ export function useCookie <T=string> (name: string, _opts?: CookieOptions<T>): C
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
nuxtApp.hooks.hookOnce('app:rendered', () => { nuxtApp.hooks.hookOnce('app:rendered', () => {
if (cookie.value !== initialValue) { if (cookie.value !== initialValue) {
// @ts-ignore writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, opts)
writeServerCookie(useSSRRes(nuxtApp), name, cookie.value, opts)
} }
}) })
} }
@ -43,15 +42,9 @@ export function useCookie <T=string> (name: string, _opts?: CookieOptions<T>): C
return cookie as CookieRef<T> return cookie as CookieRef<T>
} }
// @ts-ignore
function useSSRReq (nuxtApp?: NuxtApp = useNuxtApp()) { return nuxtApp.ssrContext?.req }
// @ts-ignore
function useSSRRes (nuxtApp?: NuxtApp = useNuxtApp()) { return nuxtApp.ssrContext?.res }
function readRawCookies (opts: CookieOptions = {}): Record<string, string> { function readRawCookies (opts: CookieOptions = {}): Record<string, string> {
if (process.server) { if (process.server) {
return parse(useSSRReq().headers.cookie || '', opts) return parse(useRequestEvent()?.req.headers.cookie || '', opts)
} else if (process.client) { } else if (process.client) {
return parse(document.cookie, opts) return parse(document.cookie, opts)
} }
@ -70,9 +63,9 @@ function writeClientCookie (name: string, value: any, opts: CookieSerializeOptio
} }
} }
function writeServerCookie (res: ServerResponse, name: string, value: any, opts: CookieSerializeOptions = {}) { function writeServerCookie (event: CompatibilityEvent, name: string, value: any, opts: CookieSerializeOptions = {}) {
if (res) { if (event) {
// TODO: Try to smart join with existing Set-Cookie headers // TODO: Try to smart join with existing Set-Cookie headers
appendHeader(res, 'Set-Cookie', serializeCookie(name, value, opts)) appendHeader(event, 'Set-Cookie', serializeCookie(name, value, opts))
} }
} }

View File

@ -1,5 +1,5 @@
import type { FetchOptions, FetchRequest } from 'ohmyfetch' import type { FetchOptions, FetchRequest } from 'ohmyfetch'
import type { TypedInternalResponse } from '@nuxt/nitro' import type { TypedInternalResponse } from 'nitropack'
import { hash } from 'ohash' import { hash } from 'ohash'
import { computed, isRef, Ref } from 'vue' import { computed, isRef, Ref } from 'vue'
import type { AsyncDataOptions, _Transform, KeyOfRes } from './asyncData' import type { AsyncDataOptions, _Transform, KeyOfRes } from './asyncData'

View File

@ -60,10 +60,11 @@ export const navigateTo = (to: RouteLocationRaw, options: NavigateToOptions = {}
} }
const router = useRouter() const router = useRouter()
if (process.server && useNuxtApp().ssrContext) { if (process.server && useNuxtApp().ssrContext) {
// Server-side redirection using h3 res from ssrContext const { ssrContext } = useNuxtApp()
const res = useNuxtApp().ssrContext?.res if (ssrContext && ssrContext.event) {
const redirectLocation = router.resolve(to).fullPath const redirectLocation = router.resolve(to).fullPath
return sendRedirect(res, redirectLocation) return sendRedirect(ssrContext.event, redirectLocation)
}
} }
// Client-side redirection using vue-router // Client-side redirection using vue-router
return options.replace ? router.replace(to) : router.push(to) return options.replace ? router.replace(to) : router.push(to)

View File

@ -1,11 +1,17 @@
/* eslint-disable no-redeclare */ /* eslint-disable no-redeclare */
import type { CompatibilityEvent } from 'h3'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
import { NuxtApp } from '#app/nuxt'
export function useRequestHeaders<K extends string = string> (include: K[]): Record<K, string>; export function useRequestHeaders<K extends string = string> (include: K[]): Record<K, string>;
export function useRequestHeaders (): Readonly<Record<string, string>>; export function useRequestHeaders (): Readonly<Record<string, string>>;
export function useRequestHeaders (include?) { export function useRequestHeaders (include?) {
if (process.client) { return {} } if (process.client) { return {} }
const headers: Record<string, string> = useNuxtApp().ssrContext?.req.headers ?? {} const headers: Record<string, string> = useNuxtApp().ssrContext?.event.req.headers ?? {}
if (!include) { return headers } if (!include) { return headers }
return Object.fromEntries(include.filter(key => headers[key]).map(key => [key, headers[key]])) return Object.fromEntries(include.filter(key => headers[key]).map(key => [key, headers[key]]))
} }
export function useRequestEvent (nuxtApp: NuxtApp = useNuxtApp()): CompatibilityEvent {
return nuxtApp.ssrContext?.event as CompatibilityEvent
}

View File

@ -1,6 +1,7 @@
// We set __webpack_public_path via this import with webpack builder // We set __webpack_public_path via this import with webpack builder
import '#build/paths.mjs' import '#build/paths.mjs'
import { createSSRApp, createApp, nextTick } from 'vue' import { createSSRApp, createApp, nextTick } from 'vue'
import { $fetch } from 'ohmyfetch'
import { createNuxtApp, applyPlugins, normalizePlugins, CreateOptions } from '#app' import { createNuxtApp, applyPlugins, normalizePlugins, CreateOptions } from '#app'
import '#build/css' import '#build/css'
// @ts-ignore // @ts-ignore
@ -10,6 +11,10 @@ import RootComponent from '#build/root-component.mjs'
// @ts-ignore // @ts-ignore
import AppComponent from '#build/app-component.mjs' import AppComponent from '#build/app-component.mjs'
if (!globalThis.$fetch) {
globalThis.$fetch = $fetch as any
}
let entry: Function let entry: Function
const plugins = normalizePlugins(_plugins) const plugins = normalizePlugins(_plugins)

View File

@ -34,6 +34,7 @@ export const appPreset = defineUnimportPreset({
'useLazyFetch', 'useLazyFetch',
'useCookie', 'useCookie',
'useRequestHeaders', 'useRequestHeaders',
'useRequestEvent',
'useRouter', 'useRouter',
'useRoute', 'useRoute',
'useActiveRoute', 'useActiveRoute',

View File

@ -1,95 +0,0 @@
import { resolve } from 'pathe'
import { wpfs, getNitroContext, createDevServer, resolveMiddleware, build, prepare, generate, writeTypes, scanMiddleware } from '@nuxt/nitro'
import type { Nuxt } from '@nuxt/schema'
import { ImportProtectionPlugin } from './plugins/import-protection'
export function initNitro (nuxt: Nuxt) {
// Create contexts
const nitroOptions = (nuxt.options as any).nitro || {}
const nitroContext = getNitroContext(nuxt.options, nitroOptions)
const nitroDevContext = getNitroContext(nuxt.options, { ...nitroOptions, preset: 'dev' })
nuxt.server = createDevServer(nitroDevContext)
if (nuxt.vfs) {
nitroContext.vfs = nuxt.vfs
nitroDevContext.vfs = nuxt.vfs
}
// Connect hooks
// @ts-ignore
nuxt.hooks.addHooks(nitroContext.nuxtHooks)
nuxt.hook('close', () => nitroContext._internal.hooks.callHook('close'))
nitroContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
nitroContext._internal.hooks.hook('nitro:generate', ctx => nuxt.callHook('nitro:generate', ctx))
// @ts-ignore
nuxt.hooks.addHooks(nitroDevContext.nuxtHooks)
nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close'))
nitroDevContext._internal.hooks.hook('nitro:document', template => nuxt.callHook('nitro:document', template))
// Register nuxt3 protection patterns
nitroDevContext._internal.hooks.hook('nitro:rollup:before', (ctx) => {
ctx.rollupConfig.plugins.push(ImportProtectionPlugin.rollup({
rootDir: nuxt.options.rootDir,
patterns: [
...['#app', /^#build(\/|$)/]
.map(p => [p, 'Vue app aliases are not allowed in server routes.']) as [RegExp | string, string][]
]
}))
})
// Add typed route responses
nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/nitro.d.ts') })
})
// Add nitro client plugin (to inject $fetch helper)
nuxt.hook('app:resolve', (app) => {
app.plugins.push({ src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.mjs') })
})
// Expose process.env.NITRO_PRESET
nuxt.options.env.NITRO_PRESET = nitroContext.preset
// Wait for all modules to be ready
nuxt.hook('modules:done', async () => {
// Extend nitro with modules
await nuxt.callHook('nitro:context', nitroContext)
await nuxt.callHook('nitro:context', nitroDevContext)
// Resolve middleware
const { middleware, legacyMiddleware } = await resolveMiddleware(nuxt)
nuxt.server.setLegacyMiddleware(legacyMiddleware)
nitroContext.middleware.push(...middleware)
nitroDevContext.middleware.push(...middleware)
})
// nuxt build/dev
nuxt.hook('build:done', async () => {
if (nuxt.options.dev) {
await build(nitroDevContext)
} else if (!nitroContext._nuxt.isStatic) {
await prepare(nitroContext)
await generate(nitroContext)
await build(nitroContext)
}
})
nuxt.hook('build:before', async () => {
const serverDirs = nitroDevContext._layers.map(layer => layer.serverDir)
nitroDevContext.scannedMiddleware = (
await Promise.all(serverDirs.map(async dir => await scanMiddleware(dir)))
).flat().sort((a, b) => b.route.localeCompare(a.route))
await writeTypes(nitroDevContext)
})
// nuxt dev
if (nuxt.options.dev) {
nitroDevContext._internal.hooks.hook('nitro:compiled', () => { nuxt.server.watch() })
nuxt.hook('build:compile', ({ compiler }) => { compiler.outputFileSystem = wpfs })
nuxt.hook('server:devMiddleware', (m) => { nuxt.server.setDevMiddleware(m) })
}
}

Some files were not shown because too many files have changed in this diff Show More