mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-24 22:55:13 +00:00
chore: merge nitro into monorepo
This commit is contained in:
commit
8d6b97a4ac
124
packages/nitro/src/build.ts
Normal file
124
packages/nitro/src/build.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { resolve, join } from 'upath'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { rollup, watch as rollupWatch } from 'rollup'
|
||||||
|
import { readFile, emptyDir, copy } from 'fs-extra'
|
||||||
|
import { printFSTree } from './utils/tree'
|
||||||
|
import { getRollupConfig } from './rollup/config'
|
||||||
|
import { hl, prettyPath, serializeTemplate, writeFile, isDirectory } from './utils'
|
||||||
|
import { NitroContext } from './context'
|
||||||
|
import { scanMiddleware } from './server/middleware'
|
||||||
|
|
||||||
|
export async function prepare (nitroContext: NitroContext) {
|
||||||
|
consola.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) {
|
||||||
|
consola.info('Cleaning up', prettyPath(dir))
|
||||||
|
await emptyDir(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generate (nitroContext: NitroContext) {
|
||||||
|
consola.start('Generating public...')
|
||||||
|
|
||||||
|
const clientDist = resolve(nitroContext._nuxt.buildDir, 'dist/client')
|
||||||
|
if (await isDirectory(clientDist)) {
|
||||||
|
await copy(clientDist, join(nitroContext.output.publicDir, nitroContext._nuxt.publicPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticDir = resolve(nitroContext._nuxt.srcDir, nitroContext._nuxt.staticDir)
|
||||||
|
if (await isDirectory(staticDir)) {
|
||||||
|
await copy(staticDir, nitroContext.output.publicDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
consola.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: '', compiled: '' }
|
||||||
|
htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.js').replace('app.', 'document.')
|
||||||
|
htmlTemplate.contents = await readFile(htmlTemplate.src, 'utf-8')
|
||||||
|
htmlTemplate.compiled = 'module.exports = ' + serializeTemplate(htmlTemplate.contents)
|
||||||
|
await nitroContext._internal.hooks.callHook('nitro:template:document', htmlTemplate)
|
||||||
|
await writeFile(htmlTemplate.dst, htmlTemplate.compiled)
|
||||||
|
|
||||||
|
nitroContext.rollupConfig = getRollupConfig(nitroContext)
|
||||||
|
await nitroContext._internal.hooks.callHook('nitro:rollup:before', nitroContext)
|
||||||
|
return nitroContext._nuxt.dev ? _watch(nitroContext) : _build(nitroContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _build (nitroContext: NitroContext) {
|
||||||
|
nitroContext.scannedMiddleware = await scanMiddleware(nitroContext._nuxt.serverDir)
|
||||||
|
|
||||||
|
consola.start('Building server...')
|
||||||
|
const build = await rollup(nitroContext.rollupConfig).catch((error) => {
|
||||||
|
consola.error('Rollup error: ' + error.message)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
|
||||||
|
consola.start('Writing server bundle...')
|
||||||
|
await build.write(nitroContext.rollupConfig.output)
|
||||||
|
|
||||||
|
consola.success('Server built')
|
||||||
|
await printFSTree(nitroContext.output.serverDir)
|
||||||
|
await nitroContext._internal.hooks.callHook('nitro:compiled', nitroContext)
|
||||||
|
|
||||||
|
return {
|
||||||
|
entry: resolve(nitroContext.rollupConfig.output.dir, nitroContext.rollupConfig.output.entryFileNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRollupWatcher (nitroContext: NitroContext) {
|
||||||
|
const watcher = rollupWatch(nitroContext.rollupConfig)
|
||||||
|
let start
|
||||||
|
|
||||||
|
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)
|
||||||
|
consola.success('Nitro built', start ? `in ${Date.now() - start} ms` : '')
|
||||||
|
return
|
||||||
|
|
||||||
|
// Encountered an error while bundling
|
||||||
|
case 'ERROR':
|
||||||
|
consola.error('Rollup error: ' + event.error)
|
||||||
|
// consola.error(event.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _watch (nitroContext: NitroContext) {
|
||||||
|
let watcher = startRollupWatcher(nitroContext)
|
||||||
|
|
||||||
|
nitroContext.scannedMiddleware = await scanMiddleware(nitroContext._nuxt.serverDir,
|
||||||
|
(middleware, event) => {
|
||||||
|
nitroContext.scannedMiddleware = middleware
|
||||||
|
if (['add', 'addDir'].includes(event)) {
|
||||||
|
watcher.close()
|
||||||
|
watcher = startRollupWatcher(nitroContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
152
packages/nitro/src/compat.ts
Normal file
152
packages/nitro/src/compat.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import fetch from 'node-fetch'
|
||||||
|
import { resolve } from 'upath'
|
||||||
|
import { build, generate, prepare } from './build'
|
||||||
|
import { getNitroContext, NitroContext } from './context'
|
||||||
|
import { createDevServer } from './server/dev'
|
||||||
|
import { wpfs } from './utils/wpfs'
|
||||||
|
import { resolveMiddleware } from './server/middleware'
|
||||||
|
|
||||||
|
export default function nuxt2CompatModule () {
|
||||||
|
const { nuxt } = this
|
||||||
|
|
||||||
|
// Ensure we're not just building with 'static' target
|
||||||
|
if (!nuxt.options.dev && nuxt.options.target === 'static' && !nuxt.options._export && !nuxt.options._legacyGenerate) {
|
||||||
|
throw new Error('[nitro] Please use `nuxt generate` for static target')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable loading-screen
|
||||||
|
nuxt.options.build.loadingScreen = false
|
||||||
|
nuxt.options.build.indicator = false
|
||||||
|
|
||||||
|
// Create contexts
|
||||||
|
const nitroContext = getNitroContext(nuxt.options, nuxt.options.nitro || {})
|
||||||
|
const nitroDevContext = getNitroContext(nuxt.options, { preset: 'dev' })
|
||||||
|
|
||||||
|
// Connect hooks
|
||||||
|
nuxt.addHooks(nitroContext.nuxtHooks)
|
||||||
|
nuxt.hook('close', () => nitroContext._internal.hooks.callHook('close'))
|
||||||
|
|
||||||
|
nuxt.addHooks(nitroDevContext.nuxtHooks)
|
||||||
|
nuxt.hook('close', () => nitroDevContext._internal.hooks.callHook('close'))
|
||||||
|
nitroDevContext._internal.hooks.hook('renderLoading',
|
||||||
|
(req, res) => nuxt.callHook('server:nuxt:renderLoading', req, res))
|
||||||
|
|
||||||
|
// Expose process.env.NITRO_PRESET
|
||||||
|
nuxt.options.env.NITRO_PRESET = nitroContext.preset
|
||||||
|
|
||||||
|
// .ts is supported for serverMiddleware
|
||||||
|
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.
|
||||||
|
nuxt.hook('webpack:config', (webpackConfigs) => {
|
||||||
|
const serverConfig = webpackConfigs.find(config => config.name === 'server')
|
||||||
|
serverConfig.devtool = false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Nitro client plugin
|
||||||
|
this.addPlugin({
|
||||||
|
fileName: 'nitro.client.js',
|
||||||
|
src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.js')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Resolve middleware
|
||||||
|
nuxt.hook('modules:done', () => {
|
||||||
|
const { middleware, legacyMiddleware } =
|
||||||
|
resolveMiddleware(nuxt.options.serverMiddleware, nuxt.resolver.resolvePath)
|
||||||
|
if (nuxt.server) {
|
||||||
|
nuxt.server.setLegacyMiddleware(legacyMiddleware)
|
||||||
|
}
|
||||||
|
nitroContext.middleware.push(...middleware)
|
||||||
|
nitroDevContext.middleware.push(...middleware)
|
||||||
|
})
|
||||||
|
|
||||||
|
// nuxt build/dev
|
||||||
|
nuxt.options.build._minifyServer = false
|
||||||
|
nuxt.options.build.standalone = false
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// nude 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) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// nuxt generate
|
||||||
|
nuxt.options.generate.dir = nitroContext.output.publicDir
|
||||||
|
nuxt.options.generate.manifest = false
|
||||||
|
nuxt.hook('generate:cache:ignore', (ignore: string[]) => {
|
||||||
|
ignore.push(nitroContext.output.dir)
|
||||||
|
ignore.push(nitroContext.output.serverDir)
|
||||||
|
if (nitroContext.output.publicDir) {
|
||||||
|
ignore.push(nitroContext.output.publicDir)
|
||||||
|
}
|
||||||
|
ignore.push(...nitroContext.ignore)
|
||||||
|
})
|
||||||
|
nuxt.hook('generate:before', async () => {
|
||||||
|
await prepare(nitroContext)
|
||||||
|
})
|
||||||
|
nuxt.hook('generate:extendRoutes', async () => {
|
||||||
|
await build(nitroDevContext)
|
||||||
|
await nuxt.server.reload()
|
||||||
|
})
|
||||||
|
nuxt.hook('generate:done', async () => {
|
||||||
|
await nuxt.server.close()
|
||||||
|
await build(nitroContext)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNuxt2DevServer (nitroContext: NitroContext) {
|
||||||
|
const server = createDevServer(nitroContext)
|
||||||
|
|
||||||
|
const listeners = []
|
||||||
|
async function listen (port) {
|
||||||
|
const listener = await server.listen(port, {
|
||||||
|
showURL: false,
|
||||||
|
isProd: true
|
||||||
|
})
|
||||||
|
listeners.push(listener)
|
||||||
|
return listener
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderRoute (route = '/', renderContext = {}) {
|
||||||
|
const [listener] = listeners
|
||||||
|
if (!listener) {
|
||||||
|
throw new Error('There is no server listener to call `server.renderRoute()`')
|
||||||
|
}
|
||||||
|
const html = await fetch(listener.url + route, {
|
||||||
|
headers: { 'nuxt-render-context': encodeQuery(renderContext) }
|
||||||
|
}).then(r => r.text())
|
||||||
|
|
||||||
|
return { html }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...server,
|
||||||
|
listeners,
|
||||||
|
renderRoute,
|
||||||
|
listen,
|
||||||
|
serverMiddlewarePaths () { return [] },
|
||||||
|
ready () { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeQuery (obj) {
|
||||||
|
return Object.entries(obj).map(
|
||||||
|
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}`
|
||||||
|
).join('&')
|
||||||
|
}
|
136
packages/nitro/src/context.ts
Normal file
136
packages/nitro/src/context.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { resolve } from 'upath'
|
||||||
|
import defu from 'defu'
|
||||||
|
import type { NuxtOptions } from '@nuxt/types'
|
||||||
|
import Hookable, { configHooksT } from 'hookable'
|
||||||
|
import type { Preset } from '@nuxt/un'
|
||||||
|
import { tryImport, resolvePath, detectTarget, extendPreset } from './utils'
|
||||||
|
import * as PRESETS from './presets'
|
||||||
|
import type { NodeExternalsOptions } from './rollup/plugins/externals'
|
||||||
|
import type { ServerMiddleware } from './server/middleware'
|
||||||
|
|
||||||
|
export interface NitroContext {
|
||||||
|
timing: boolean
|
||||||
|
inlineDynamicImports: boolean
|
||||||
|
minify: boolean
|
||||||
|
sourceMap: boolean
|
||||||
|
externals: boolean | NodeExternalsOptions
|
||||||
|
analyze: boolean
|
||||||
|
entry: string
|
||||||
|
node: boolean
|
||||||
|
preset: string
|
||||||
|
rollupConfig?: any
|
||||||
|
renderer: string
|
||||||
|
serveStatic: boolean
|
||||||
|
middleware: ServerMiddleware[]
|
||||||
|
scannedMiddleware: ServerMiddleware[]
|
||||||
|
hooks: configHooksT
|
||||||
|
nuxtHooks: configHooksT
|
||||||
|
ignore: string[]
|
||||||
|
env: Preset
|
||||||
|
output: {
|
||||||
|
dir: string
|
||||||
|
serverDir: string
|
||||||
|
publicDir: string
|
||||||
|
}
|
||||||
|
_nuxt: {
|
||||||
|
majorVersion: number
|
||||||
|
dev: boolean
|
||||||
|
rootDir: string
|
||||||
|
srcDir: string
|
||||||
|
buildDir: string
|
||||||
|
generateDir: string
|
||||||
|
staticDir: string
|
||||||
|
serverDir: string
|
||||||
|
routerBase: string
|
||||||
|
publicPath: string
|
||||||
|
isStatic: boolean
|
||||||
|
fullStatic: boolean
|
||||||
|
staticAssets: any
|
||||||
|
runtimeConfig: { public: any, private: any }
|
||||||
|
}
|
||||||
|
_internal: {
|
||||||
|
runtimeDir: string
|
||||||
|
hooks: Hookable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeepPartial<T> = { [P in keyof T]?: DeepPartial<T[P]> }
|
||||||
|
|
||||||
|
export interface NitroInput extends DeepPartial<NitroContext> {}
|
||||||
|
|
||||||
|
export type NitroPreset = NitroInput | ((input: NitroInput) => NitroInput)
|
||||||
|
|
||||||
|
export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): NitroContext {
|
||||||
|
const defaults: NitroContext = {
|
||||||
|
timing: undefined,
|
||||||
|
inlineDynamicImports: undefined,
|
||||||
|
minify: undefined,
|
||||||
|
sourceMap: undefined,
|
||||||
|
externals: undefined,
|
||||||
|
analyze: undefined,
|
||||||
|
entry: undefined,
|
||||||
|
node: undefined,
|
||||||
|
preset: undefined,
|
||||||
|
rollupConfig: undefined,
|
||||||
|
renderer: undefined,
|
||||||
|
serveStatic: undefined,
|
||||||
|
middleware: [],
|
||||||
|
scannedMiddleware: [],
|
||||||
|
ignore: [],
|
||||||
|
env: {},
|
||||||
|
hooks: {},
|
||||||
|
nuxtHooks: {},
|
||||||
|
output: {
|
||||||
|
dir: '{{ _nuxt.rootDir }}/.output',
|
||||||
|
serverDir: '{{ output.dir }}/server',
|
||||||
|
publicDir: '{{ output.dir }}/public'
|
||||||
|
},
|
||||||
|
_nuxt: {
|
||||||
|
majorVersion: nuxtOptions._majorVersion || 2,
|
||||||
|
dev: nuxtOptions.dev,
|
||||||
|
rootDir: nuxtOptions.rootDir,
|
||||||
|
srcDir: nuxtOptions.srcDir,
|
||||||
|
buildDir: nuxtOptions.buildDir,
|
||||||
|
generateDir: nuxtOptions.generate.dir,
|
||||||
|
staticDir: nuxtOptions.dir.static,
|
||||||
|
serverDir: resolve(nuxtOptions.srcDir, (nuxtOptions.dir as any).server || 'server'),
|
||||||
|
routerBase: nuxtOptions.router.base,
|
||||||
|
publicPath: nuxtOptions.build.publicPath,
|
||||||
|
isStatic: nuxtOptions.target === 'static' && !nuxtOptions.dev,
|
||||||
|
fullStatic: nuxtOptions.target === 'static' && !nuxtOptions._legacyGenerate,
|
||||||
|
// @ts-ignore
|
||||||
|
staticAssets: nuxtOptions.generate.staticAssets,
|
||||||
|
runtimeConfig: {
|
||||||
|
public: nuxtOptions.publicRuntimeConfig,
|
||||||
|
private: nuxtOptions.privateRuntimeConfig
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_internal: {
|
||||||
|
runtimeDir: resolve(__dirname, './runtime'),
|
||||||
|
hooks: new Hookable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults.preset = input.preset || process.env.NITRO_PRESET || detectTarget() || 'server'
|
||||||
|
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)
|
||||||
|
// @ts-ignore
|
||||||
|
const _preset = extendPreset(input, presetDefaults)(_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)
|
||||||
|
|
||||||
|
nitroContext._internal.hooks.addHooks(nitroContext.hooks)
|
||||||
|
|
||||||
|
// console.log(nitroContext)
|
||||||
|
// process.exit(1)
|
||||||
|
|
||||||
|
return nitroContext
|
||||||
|
}
|
6
packages/nitro/src/index.ts
Normal file
6
packages/nitro/src/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './build'
|
||||||
|
export * from './context'
|
||||||
|
export * from './server/middleware'
|
||||||
|
export * from './server/dev'
|
||||||
|
export * from './types'
|
||||||
|
export { wpfs } from './utils/wpfs'
|
105
packages/nitro/src/presets/azure.ts
Normal file
105
packages/nitro/src/presets/azure.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import fse from 'fs-extra'
|
||||||
|
import globby from 'globby'
|
||||||
|
import { join, resolve } from 'upath'
|
||||||
|
import { writeFile } from '../utils'
|
||||||
|
import { NitroPreset, NitroContext } from '../context'
|
||||||
|
|
||||||
|
export const azure: NitroPreset = {
|
||||||
|
entry: '{{ _internal.runtimeDir }}/entries/azure',
|
||||||
|
output: {
|
||||||
|
serverDir: '{{ output.dir }}/server/functions'
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
async 'nitro:compiled' (ctx: NitroContext) {
|
||||||
|
await writeRoutes(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeRoutes ({ output: { serverDir, publicDir } }: NitroContext) {
|
||||||
|
const host = {
|
||||||
|
version: '2.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
route: '/*',
|
||||||
|
serve: '/api/server'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const indexPath = resolve(publicDir, 'index.html')
|
||||||
|
const indexFileExists = fse.existsSync(indexPath)
|
||||||
|
if (!indexFileExists) {
|
||||||
|
routes.unshift(
|
||||||
|
{
|
||||||
|
route: '/',
|
||||||
|
serve: '/api/server'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: '/index.html',
|
||||||
|
serve: '/api/server'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderFiles = await globby([
|
||||||
|
join(publicDir, 'index.html'),
|
||||||
|
join(publicDir, '**/index.html')
|
||||||
|
])
|
||||||
|
const prefix = publicDir.length
|
||||||
|
const suffix = '/index.html'.length
|
||||||
|
folderFiles.forEach(file =>
|
||||||
|
routes.unshift({
|
||||||
|
route: file.slice(prefix, -suffix) || '/',
|
||||||
|
serve: file.slice(prefix)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const otherFiles = await globby([join(publicDir, '**/*.html'), join(publicDir, '*.html')])
|
||||||
|
otherFiles.forEach((file) => {
|
||||||
|
if (file.endsWith('index.html')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const route = file.slice(prefix, -5)
|
||||||
|
const existingRouteIndex = routes.findIndex(_route => _route.route === route)
|
||||||
|
if (existingRouteIndex > -1) {
|
||||||
|
routes.splice(existingRouteIndex, 1)
|
||||||
|
}
|
||||||
|
routes.unshift(
|
||||||
|
{
|
||||||
|
route,
|
||||||
|
serve: 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(serverDir, 'function.json'), JSON.stringify(functionDefinition))
|
||||||
|
await writeFile(resolve(serverDir, '../host.json'), JSON.stringify(host))
|
||||||
|
await writeFile(resolve(publicDir, 'routes.json'), JSON.stringify({ routes }))
|
||||||
|
if (!indexFileExists) {
|
||||||
|
await writeFile(indexPath, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
consola.success('Ready to deploy.')
|
||||||
|
}
|
74
packages/nitro/src/presets/azure_functions.ts
Normal file
74
packages/nitro/src/presets/azure_functions.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import archiver from 'archiver'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { createWriteStream } from 'fs-extra'
|
||||||
|
import { join, resolve } from 'upath'
|
||||||
|
import { prettyPath, 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',
|
||||||
|
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'))
|
||||||
|
const zipPath = prettyPath(resolve(dir, 'deploy.zip'))
|
||||||
|
|
||||||
|
consola.success(`Ready to run \`az functionapp deployment source config-zip -g <resource-group> -n <app-name> --src ${zipPath}\``)
|
||||||
|
}
|
79
packages/nitro/src/presets/browser.ts
Normal file
79
packages/nitro/src/presets/browser.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { writeFile } from 'fs-extra'
|
||||||
|
import { resolve } from 'upath'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { extendPreset, prettyPath } from '../utils'
|
||||||
|
import { NitroPreset, NitroContext, NitroInput } from '../context'
|
||||||
|
import { worker } from './worker'
|
||||||
|
|
||||||
|
export const browser: NitroPreset = extendPreset(worker, (input: NitroInput) => {
|
||||||
|
const routerBase = input._nuxt.routerBase
|
||||||
|
|
||||||
|
const script = `<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('${routerBase}sw.js');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>`
|
||||||
|
|
||||||
|
// TEMP FIX
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="prefetch" href="${routerBase}sw.js">
|
||||||
|
<link rel="prefetch" href="${routerBase}_server/index.js">
|
||||||
|
<script>
|
||||||
|
async function register () {
|
||||||
|
const registration = await navigator.serviceWorker.register('${routerBase}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: {
|
||||||
|
'vue-renderer:ssr:templateParams' (params) {
|
||||||
|
params.APP += script
|
||||||
|
},
|
||||||
|
'vue-renderer:spa:templateParams' (params) {
|
||||||
|
params.APP += script
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
'nitro:template:document' (tmpl) {
|
||||||
|
tmpl.compiled = tmpl.compiled.replace('</body>', script + '</body>')
|
||||||
|
},
|
||||||
|
async 'nitro:compiled' ({ output }: NitroContext) {
|
||||||
|
await writeFile(resolve(output.publicDir, 'sw.js'), `self.importScripts('${input._nuxt.routerBase}_server/index.js');`)
|
||||||
|
|
||||||
|
// Temp fix
|
||||||
|
await writeFile(resolve(output.publicDir, 'index.html'), html)
|
||||||
|
await writeFile(resolve(output.publicDir, '200.html'), html)
|
||||||
|
await writeFile(resolve(output.publicDir, '404.html'), html)
|
||||||
|
|
||||||
|
consola.info('Ready to deploy to static hosting:', prettyPath(output.publicDir as string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
13
packages/nitro/src/presets/cli.ts
Normal file
13
packages/nitro/src/presets/cli.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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]`')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
23
packages/nitro/src/presets/cloudflare.ts
Normal file
23
packages/nitro/src/presets/cloudflare.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { resolve } from 'upath'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { extendPreset, writeFile, prettyPath } 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'
|
||||||
|
],
|
||||||
|
hooks: {
|
||||||
|
async 'nitro:compiled' ({ output, _nuxt }: NitroContext) {
|
||||||
|
await writeFile(resolve(output.dir, 'package.json'), JSON.stringify({ private: true, main: './server/index.js' }, null, 2))
|
||||||
|
await writeFile(resolve(output.dir, 'package-lock.json'), JSON.stringify({ lockfileVersion: 1 }, null, 2))
|
||||||
|
let inDir = prettyPath(_nuxt.rootDir)
|
||||||
|
if (inDir) {
|
||||||
|
inDir = 'in ' + inDir
|
||||||
|
}
|
||||||
|
consola.success('Ready to run `wrangler publish`', inDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
13
packages/nitro/src/presets/dev.ts
Normal file
13
packages/nitro/src/presets/dev.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
|
})
|
81
packages/nitro/src/presets/firebase.ts
Normal file
81
packages/nitro/src/presets/firebase.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { join, relative, resolve } from 'upath'
|
||||||
|
import { existsSync, readJSONSync } from 'fs-extra'
|
||||||
|
import consola from 'consola'
|
||||||
|
import globby from 'globby'
|
||||||
|
|
||||||
|
import { writeFile } from '../utils'
|
||||||
|
import { NitroPreset, NitroContext } from '../context'
|
||||||
|
|
||||||
|
export const firebase: NitroPreset = {
|
||||||
|
entry: '{{ _internal.runtimeDir }}/entries/firebase',
|
||||||
|
hooks: {
|
||||||
|
async 'nitro:compiled' (ctx: NitroContext) {
|
||||||
|
await writeRoutes(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeRoutes ({ output: { publicDir, serverDir }, _nuxt: { rootDir } }: NitroContext) {
|
||||||
|
if (!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 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 = '12'
|
||||||
|
try {
|
||||||
|
const currentNodeVersion = readJSONSync(join(rootDir, 'package.json')).engines.node
|
||||||
|
if (['12', '10'].includes(currentNodeVersion)) {
|
||||||
|
nodeVersion = currentNodeVersion
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
resolve(serverDir, 'package.json'),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
private: true,
|
||||||
|
main: './index.js',
|
||||||
|
dependencies,
|
||||||
|
devDependencies: {
|
||||||
|
'firebase-functions-test': 'latest',
|
||||||
|
'firebase-admin': require('firebase-admin/package.json').version,
|
||||||
|
'firebase-functions': require('firebase-functions/package.json')
|
||||||
|
.version
|
||||||
|
},
|
||||||
|
engines: { node: nodeVersion }
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
consola.success('Ready to run `firebase deploy`')
|
||||||
|
}
|
13
packages/nitro/src/presets/index.ts
Normal file
13
packages/nitro/src/presets/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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'
|
7
packages/nitro/src/presets/lambda.ts
Normal file
7
packages/nitro/src/presets/lambda.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
import { NitroPreset } from '../context'
|
||||||
|
|
||||||
|
export const lambda: NitroPreset = {
|
||||||
|
entry: '{{ _internal.runtimeDir }}/entries/lambda',
|
||||||
|
externals: true
|
||||||
|
}
|
13
packages/nitro/src/presets/netlify.ts
Normal file
13
packages/nitro/src/presets/netlify.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { extendPreset } from '../utils'
|
||||||
|
import { NitroPreset } from '../context'
|
||||||
|
import { lambda } from './lambda'
|
||||||
|
|
||||||
|
export const netlify: NitroPreset = extendPreset(lambda, {
|
||||||
|
output: {
|
||||||
|
publicDir: '{{ _nuxt.rootDir }}/dist'
|
||||||
|
},
|
||||||
|
ignore: [
|
||||||
|
'netlify.toml',
|
||||||
|
'_redirects'
|
||||||
|
]
|
||||||
|
})
|
6
packages/nitro/src/presets/node.ts
Normal file
6
packages/nitro/src/presets/node.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { NitroPreset } from '../context'
|
||||||
|
|
||||||
|
export const node: NitroPreset = {
|
||||||
|
entry: '{{ _internal.runtimeDir }}/entries/node',
|
||||||
|
externals: true
|
||||||
|
}
|
14
packages/nitro/src/presets/server.ts
Normal file
14
packages/nitro/src/presets/server.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import { extendPreset, hl, prettyPath } from '../utils'
|
||||||
|
import { NitroPreset, NitroContext } from '../context'
|
||||||
|
import { node } from './node'
|
||||||
|
|
||||||
|
export const server: NitroPreset = extendPreset(node, {
|
||||||
|
entry: '{{ _internal.runtimeDir }}/entries/server',
|
||||||
|
serveStatic: true,
|
||||||
|
hooks: {
|
||||||
|
'nitro:compiled' ({ output }: NitroContext) {
|
||||||
|
consola.success('Ready to run', hl('node ' + prettyPath(output.serverDir)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
49
packages/nitro/src/presets/vercel.ts
Normal file
49
packages/nitro/src/presets/vercel.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { resolve } from 'upath'
|
||||||
|
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))
|
||||||
|
}
|
13
packages/nitro/src/presets/worker.ts
Normal file
13
packages/nitro/src/presets/worker.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { NitroPreset, NitroContext } from '../context'
|
||||||
|
|
||||||
|
export const worker: NitroPreset = {
|
||||||
|
entry: null, // Abstract
|
||||||
|
node: false,
|
||||||
|
minify: true,
|
||||||
|
inlineDynamicImports: true, // iffe does not support code-splitting
|
||||||
|
hooks: {
|
||||||
|
'nitro:rollup:before' ({ rollupConfig }: NitroContext) {
|
||||||
|
rollupConfig.output.format = 'iife'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
249
packages/nitro/src/rollup/config.ts
Normal file
249
packages/nitro/src/rollup/config.ts
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import { dirname, join, relative, resolve } from 'upath'
|
||||||
|
import { 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 inject from '@rollup/plugin-inject'
|
||||||
|
import analyze from 'rollup-plugin-analyzer'
|
||||||
|
import type { Preset } from '@nuxt/un'
|
||||||
|
import * as un from '@nuxt/un'
|
||||||
|
|
||||||
|
import { NitroContext } from '../context'
|
||||||
|
import { resolvePath, MODULE_DIR } from '../utils'
|
||||||
|
|
||||||
|
import { dynamicRequire } from './plugins/dynamic-require'
|
||||||
|
import { externals } from './plugins/externals'
|
||||||
|
import { timing } from './plugins/timing'
|
||||||
|
import { autoMock } from './plugins/automock'
|
||||||
|
import { staticAssets, dirnames } from './plugins/static'
|
||||||
|
import { middleware } from './plugins/middleware'
|
||||||
|
import { esbuild } from './plugins/esbuild'
|
||||||
|
|
||||||
|
export type RollupConfig = InputOptions & { output: OutputOptions }
|
||||||
|
|
||||||
|
export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||||
|
const extensions: string[] = ['.ts', '.mjs', '.js', '.json', '.node']
|
||||||
|
|
||||||
|
const nodePreset = nitroContext.node === false ? un.nodeless : un.node
|
||||||
|
|
||||||
|
const builtinPreset: Preset = {
|
||||||
|
alias: {
|
||||||
|
// General
|
||||||
|
debug: 'un/npm/debug',
|
||||||
|
depd: 'un/npm/depd',
|
||||||
|
// Vue 2
|
||||||
|
encoding: 'un/mock/proxy',
|
||||||
|
he: 'un/mock/proxy',
|
||||||
|
resolve: 'un/mock/proxy',
|
||||||
|
'source-map': 'un/mock/proxy',
|
||||||
|
'lodash.template': 'un/mock/proxy',
|
||||||
|
'serialize-javascript': 'un/mock/proxy',
|
||||||
|
// Vue 3
|
||||||
|
'@babel/parser': 'un/mock/proxy',
|
||||||
|
'@vue/compiler-core': 'un/mock/proxy',
|
||||||
|
'@vue/compiler-dom': 'un/mock/proxy',
|
||||||
|
'@vue/compiler-ssr': 'un/mock/proxy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = un.env(nodePreset, builtinPreset, nitroContext.env)
|
||||||
|
|
||||||
|
delete env.alias['node-fetch'] // FIX ME
|
||||||
|
|
||||||
|
if (nitroContext.sourceMap) {
|
||||||
|
env.polyfill.push('source-map-support/register')
|
||||||
|
}
|
||||||
|
|
||||||
|
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.js',
|
||||||
|
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 (!prefix && nitroContext.middleware.find(m => lastModule.startsWith(m.handle))) {
|
||||||
|
prefix = 'middleware'
|
||||||
|
}
|
||||||
|
return join('chunks', prefix, '[name].js')
|
||||||
|
},
|
||||||
|
inlineDynamicImports: nitroContext.inlineDynamicImports,
|
||||||
|
format: 'cjs',
|
||||||
|
exports: 'auto',
|
||||||
|
intro: '',
|
||||||
|
outro: '',
|
||||||
|
preferConst: true,
|
||||||
|
sourcemap: nitroContext.sourceMap,
|
||||||
|
sourcemapExcludeSources: true,
|
||||||
|
sourcemapPathTransform (relativePath, sourcemapPath) {
|
||||||
|
return resolve(dirname(sourcemapPath), relativePath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
external: env.external,
|
||||||
|
plugins: [],
|
||||||
|
onwarn (warning, rollupWarn) {
|
||||||
|
if (!['CIRCULAR_DEPENDENCY', 'EVAL'].includes(warning.code)) {
|
||||||
|
rollupWarn(warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nitroContext.timing) {
|
||||||
|
rollupConfig.plugins.push(timing())
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/replace
|
||||||
|
rollupConfig.plugins.push(replace({
|
||||||
|
// @ts-ignore https://github.com/rollup/plugins/pull/810
|
||||||
|
preventAssignment: true,
|
||||||
|
values: {
|
||||||
|
'process.env.NODE_ENV': nitroContext._nuxt.dev ? '"development"' : '"production"',
|
||||||
|
'typeof window': '"undefined"',
|
||||||
|
'process.env.ROUTER_BASE': JSON.stringify(nitroContext._nuxt.routerBase),
|
||||||
|
'process.env.PUBLIC_PATH': JSON.stringify(nitroContext._nuxt.publicPath),
|
||||||
|
'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': JSON.stringify(nitroContext._nuxt.runtimeConfig),
|
||||||
|
'process.env.DEBUG': JSON.stringify(nitroContext._nuxt.dev)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// ESBuild
|
||||||
|
rollupConfig.plugins.push(esbuild({
|
||||||
|
sourceMap: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Dynamic Require Support
|
||||||
|
rollupConfig.plugins.push(dynamicRequire({
|
||||||
|
dir: resolve(nitroContext._nuxt.buildDir, 'dist/server'),
|
||||||
|
inline: nitroContext.node === false || nitroContext.inlineDynamicImports,
|
||||||
|
globbyOptions: {
|
||||||
|
ignore: [
|
||||||
|
'server.js'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Static
|
||||||
|
if (nitroContext.serveStatic) {
|
||||||
|
rollupConfig.plugins.push(dirnames())
|
||||||
|
rollupConfig.plugins.push(staticAssets(nitroContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
rollupConfig.plugins.push(middleware(() => {
|
||||||
|
const _middleware = [
|
||||||
|
...nitroContext.scannedMiddleware,
|
||||||
|
...nitroContext.middleware
|
||||||
|
]
|
||||||
|
if (nitroContext.serveStatic) {
|
||||||
|
_middleware.unshift({ route: '/', handle: '~runtime/server/static' })
|
||||||
|
}
|
||||||
|
return _middleware
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Polyfill
|
||||||
|
rollupConfig.plugins.push(virtual({
|
||||||
|
'~polyfill': env.polyfill.map(p => `import '${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: {
|
||||||
|
'~runtime': nitroContext._internal.runtimeDir,
|
||||||
|
'~renderer': require.resolve(resolve(nitroContext._internal.runtimeDir, 'app', renderer)),
|
||||||
|
'~vueServerRenderer': vue2ServerRenderer,
|
||||||
|
'~build': nitroContext._nuxt.buildDir,
|
||||||
|
...env.alias
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const moduleDirectories = [
|
||||||
|
resolve(nitroContext._nuxt.rootDir, 'node_modules'),
|
||||||
|
resolve(MODULE_DIR, 'node_modules'),
|
||||||
|
resolve(MODULE_DIR, '../node_modules'),
|
||||||
|
'node_modules'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Externals Plugin
|
||||||
|
if (nitroContext.externals) {
|
||||||
|
rollupConfig.plugins.push(externals(defu(nitroContext.externals as any, {
|
||||||
|
outDir: nitroContext.output.serverDir,
|
||||||
|
moduleDirectories,
|
||||||
|
ignore: [
|
||||||
|
nitroContext._internal.runtimeDir,
|
||||||
|
...(nitroContext._nuxt.dev ? [] : [nitroContext._nuxt.buildDir]),
|
||||||
|
...nitroContext.middleware.map(m => m.handle),
|
||||||
|
nitroContext._nuxt.serverDir
|
||||||
|
],
|
||||||
|
traceOptions: {
|
||||||
|
base: nitroContext._nuxt.rootDir
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/node-resolve
|
||||||
|
rollupConfig.plugins.push(nodeResolve({
|
||||||
|
extensions,
|
||||||
|
preferBuiltins: true,
|
||||||
|
rootDir: nitroContext._nuxt.rootDir,
|
||||||
|
moduleDirectories,
|
||||||
|
mainFields: ['main'] // Force resolve CJS (@vue/runtime-core ssrUtils)
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Automatically mock unresolved externals
|
||||||
|
rollupConfig.plugins.push(autoMock())
|
||||||
|
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||||
|
rollupConfig.plugins.push(commonjs({
|
||||||
|
extensions: extensions.filter(ext => ext !== '.json')
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 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(env.inject))
|
||||||
|
|
||||||
|
if (nitroContext.analyze) {
|
||||||
|
// https://github.com/doesdev/rollup-plugin-analyzer
|
||||||
|
rollupConfig.plugins.push(analyze())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rollupConfig
|
||||||
|
}
|
13
packages/nitro/src/rollup/plugins/automock.ts
Normal file
13
packages/nitro/src/rollup/plugins/automock.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function autoMock () {
|
||||||
|
return {
|
||||||
|
name: 'auto-mock',
|
||||||
|
resolveId (src: string) {
|
||||||
|
if (src && !src.startsWith('.') && !src.includes('?') && !src.includes('.js')) {
|
||||||
|
return {
|
||||||
|
id: require.resolve('@nuxt/un/runtime/mock/proxy')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
packages/nitro/src/rollup/plugins/dynamic-require.ts
Normal file
136
packages/nitro/src/rollup/plugins/dynamic-require.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { resolve } from 'upath'
|
||||||
|
import globby, { GlobbyOptions } from 'globby'
|
||||||
|
import type { Plugin } from 'rollup'
|
||||||
|
|
||||||
|
const PLUGIN_NAME = 'dynamic-require'
|
||||||
|
const HELPER_DYNAMIC = `\0${PLUGIN_NAME}.js`
|
||||||
|
const DYNAMIC_REQUIRE_RE = /require\("\.\/" ?\+/g
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
dir: string
|
||||||
|
inline: boolean
|
||||||
|
globbyOptions: GlobbyOptions
|
||||||
|
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, globbyOptions, inline }: Options): Plugin {
|
||||||
|
return {
|
||||||
|
name: PLUGIN_NAME,
|
||||||
|
transform (code: string, _id: string) {
|
||||||
|
return {
|
||||||
|
code: code.replace(DYNAMIC_REQUIRE_RE, `require('${HELPER_DYNAMIC}')(`),
|
||||||
|
map: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolveId (id: string) {
|
||||||
|
return id === HELPER_DYNAMIC ? id : null
|
||||||
|
},
|
||||||
|
// TODO: Async chunk loading over netwrok!
|
||||||
|
// renderDynamicImport () {
|
||||||
|
// return {
|
||||||
|
// left: 'fetch(', right: ')'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
async load (_id: string) {
|
||||||
|
if (_id !== HELPER_DYNAMIC) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan chunks
|
||||||
|
const files = await globby('**/*.js', { cwd: dir, absolute: false, ...globbyOptions })
|
||||||
|
const chunks = files.map(id => ({
|
||||||
|
id,
|
||||||
|
src: resolve(dir, id).replace(/\\/g, '/'),
|
||||||
|
name: '_' + id.replace(/[^a-zA-Z0-9_]/g, '_'),
|
||||||
|
meta: getWebpackChunkMeta(resolve(dir, id))
|
||||||
|
}))
|
||||||
|
|
||||||
|
return inline ? TMPL_INLINE({ chunks }) : TMPL_LAZY({ chunks })
|
||||||
|
},
|
||||||
|
renderChunk (code) {
|
||||||
|
if (inline) {
|
||||||
|
return {
|
||||||
|
map: null,
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
map: null,
|
||||||
|
code: code.replace(
|
||||||
|
/Promise.resolve\(\).then\(function \(\) \{ return require\('([^']*)' \/\* webpackChunk \*\/\); \}\).then\(function \(n\) \{ return n.([_a-zA-Z0-9]*); \}\)/g,
|
||||||
|
"require('$1').$2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWebpackChunkMeta (src: string) {
|
||||||
|
const chunk = require(src) || {}
|
||||||
|
const { id, ids, modules } = chunk
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
ids,
|
||||||
|
moduleIds: Object.keys(modules)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function TMPL_INLINE ({ chunks }: TemplateContext) {
|
||||||
|
return `${chunks.map(i => `import ${i.name} from '${i.src}'`).join('\n')}
|
||||||
|
const dynamicChunks = {
|
||||||
|
${chunks.map(i => ` ['${i.id}']: ${i.name}`).join(',\n')}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function dynamicRequire(id) {
|
||||||
|
return dynamicChunks[id];
|
||||||
|
};`
|
||||||
|
}
|
||||||
|
|
||||||
|
function TMPL_LAZY ({ chunks }: TemplateContext) {
|
||||||
|
return `
|
||||||
|
function dynamicWebpackModule(id, getChunk) {
|
||||||
|
return function (module, exports, require) {
|
||||||
|
const r = getChunk()
|
||||||
|
if (r instanceof Promise) {
|
||||||
|
module.exports = r.then(r => {
|
||||||
|
const realModule = { exports: {}, require };
|
||||||
|
r.modules[id](realModule, realModule.exports, realModule.require);
|
||||||
|
return realModule.exports;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
r.modules[id](module, exports, require);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function webpackChunk (meta, getChunk) {
|
||||||
|
const chunk = { ...meta, modules: {} };
|
||||||
|
for (const id of meta.moduleIds) {
|
||||||
|
chunk.modules[id] = dynamicWebpackModule(id, getChunk);
|
||||||
|
};
|
||||||
|
return chunk;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dynamicChunks = {
|
||||||
|
${chunks.map(i => ` ['${i.id}']: () => webpackChunk(${JSON.stringify(i.meta)}, () => import('${i.src}' /* webpackChunk */))`).join(',\n')}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function dynamicRequire(id) {
|
||||||
|
return dynamicChunks[id]();
|
||||||
|
};`
|
||||||
|
}
|
165
packages/nitro/src/rollup/plugins/esbuild.ts
Normal file
165
packages/nitro/src/rollup/plugins/esbuild.ts
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Based on https://github.com/egoist/rollup-plugin-esbuild (MIT)
|
||||||
|
|
||||||
|
import { extname, relative } from 'path'
|
||||||
|
import { Plugin, PluginContext } from 'rollup'
|
||||||
|
import { startService, Loader, Service, TransformResult } from 'esbuild'
|
||||||
|
import { createFilter, 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
|
||||||
|
)
|
||||||
|
|
||||||
|
let service: Service | undefined
|
||||||
|
|
||||||
|
const stopService = () => {
|
||||||
|
if (service) {
|
||||||
|
service.stop()
|
||||||
|
service = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'esbuild',
|
||||||
|
|
||||||
|
async buildStart () {
|
||||||
|
if (!service) {
|
||||||
|
service = await startService()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async transform (code, id) {
|
||||||
|
if (!filter(id)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = extname(id)
|
||||||
|
const loader = loaders[ext]
|
||||||
|
|
||||||
|
if (!loader || !service) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
target = options.target || 'node12'
|
||||||
|
|
||||||
|
const result = await service.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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
buildEnd (error) {
|
||||||
|
// Stop the service early if there's error
|
||||||
|
if (error && !this.meta.watchMode) {
|
||||||
|
stopService()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async renderChunk (code) {
|
||||||
|
if (options.minify && service) {
|
||||||
|
const result = await service.transform(code, {
|
||||||
|
loader: 'js',
|
||||||
|
minify: true,
|
||||||
|
target
|
||||||
|
})
|
||||||
|
if (result.code) {
|
||||||
|
return {
|
||||||
|
code: result.code,
|
||||||
|
map: result.map || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
generateBundle () {
|
||||||
|
if (!this.meta.watchMode) {
|
||||||
|
stopService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
packages/nitro/src/rollup/plugins/externals.ts
Normal file
62
packages/nitro/src/rollup/plugins/externals.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { isAbsolute, relative } from 'path'
|
||||||
|
import type { Plugin } from 'rollup'
|
||||||
|
import { resolve, dirname } from 'upath'
|
||||||
|
import { copyFile, mkdirp } from 'fs-extra'
|
||||||
|
import { nodeFileTrace, NodeFileTraceOptions } from '@vercel/nft'
|
||||||
|
|
||||||
|
export interface NodeExternalsOptions {
|
||||||
|
ignore?: string[]
|
||||||
|
outDir?: string
|
||||||
|
trace?: boolean
|
||||||
|
traceOptions?: NodeFileTraceOptions
|
||||||
|
moduleDirectories?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function externals (opts: NodeExternalsOptions): Plugin {
|
||||||
|
const resolvedExternals = {}
|
||||||
|
return {
|
||||||
|
name: 'node-externals',
|
||||||
|
resolveId (id) {
|
||||||
|
// Internals
|
||||||
|
if (id.startsWith('\x00') || id.includes('?')) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve relative paths and exceptions
|
||||||
|
if (id.startsWith('.') || opts.ignore.find(i => id.startsWith(i))) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dir of opts.moduleDirectories) {
|
||||||
|
if (id.startsWith(dir)) {
|
||||||
|
id = id.substr(dir.length + 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
resolvedExternals[id] = require.resolve(id, { paths: opts.moduleDirectories })
|
||||||
|
} catch (_err) { }
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: isAbsolute(id) ? relative(opts.outDir, id) : id,
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async buildEnd () {
|
||||||
|
if (opts.trace !== false) {
|
||||||
|
const { fileList } = await nodeFileTrace(Object.values(resolvedExternals), opts.traceOptions)
|
||||||
|
await Promise.all(fileList.map(async (file) => {
|
||||||
|
if (!file.startsWith('node_modules')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: Minify package.json
|
||||||
|
const src = resolve(opts.traceOptions.base, file)
|
||||||
|
const dst = resolve(opts.outDir, file)
|
||||||
|
await mkdirp(dirname(dst))
|
||||||
|
await copyFile(src, dst)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
packages/nitro/src/rollup/plugins/middleware.ts
Normal file
67
packages/nitro/src/rollup/plugins/middleware.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import hasha from 'hasha'
|
||||||
|
import { relative } from 'upath'
|
||||||
|
import { table, getBorderCharacters } from 'table'
|
||||||
|
import isPrimitive from 'is-primitive'
|
||||||
|
import stdenv from 'std-env'
|
||||||
|
import type { ServerMiddleware } from '../../server/middleware'
|
||||||
|
import virtual from './virtual'
|
||||||
|
|
||||||
|
export function middleware (getMiddleware: () => ServerMiddleware[]) {
|
||||||
|
const getImportId = p => '_' + hasha(p).substr(0, 6)
|
||||||
|
|
||||||
|
let lastDump = ''
|
||||||
|
|
||||||
|
return virtual({
|
||||||
|
'~serverMiddleware': () => {
|
||||||
|
const middleware = getMiddleware()
|
||||||
|
|
||||||
|
if (!stdenv.test) {
|
||||||
|
const dumped = dumpMiddleware(middleware)
|
||||||
|
if (dumped !== lastDump) {
|
||||||
|
lastDump = dumped
|
||||||
|
if (middleware.length) {
|
||||||
|
console.log(dumped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
${middleware.filter(m => m.lazy === false).map(m => `import ${getImportId(m.handle)} from '${m.handle}';`).join('\n')}
|
||||||
|
|
||||||
|
${middleware.filter(m => m.lazy !== false).map(m => `const ${getImportId(m.handle)} = () => import('${m.handle}');`).join('\n')}
|
||||||
|
|
||||||
|
const middleware = [
|
||||||
|
${middleware.map(m => `{ route: '${m.route}', handle: ${getImportId(m.handle)}, lazy: ${m.lazy || true}, promisify: ${m.promisify !== undefined ? m.promisify : true} }`).join(',\n')}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default middleware
|
||||||
|
`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function dumpMiddleware (middleware: ServerMiddleware[]) {
|
||||||
|
const data = middleware.map(({ route, handle, ...props }) => {
|
||||||
|
return [
|
||||||
|
(route && route !== '/') ? route : '*',
|
||||||
|
relative(process.cwd(), handle),
|
||||||
|
dumpObject(props)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
return table([
|
||||||
|
['Route', 'Handle', 'Options'],
|
||||||
|
...data
|
||||||
|
], {
|
||||||
|
singleLine: true,
|
||||||
|
border: 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(', ')
|
||||||
|
}
|
58
packages/nitro/src/rollup/plugins/static.ts
Normal file
58
packages/nitro/src/rollup/plugins/static.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import createEtag from 'etag'
|
||||||
|
import { readFileSync, statSync } from 'fs-extra'
|
||||||
|
import mime from 'mime'
|
||||||
|
import { relative, resolve } from 'upath'
|
||||||
|
import virtual from '@rollup/plugin-virtual'
|
||||||
|
import globby 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 = globby.sync('**/*.*', { 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['/' + 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 'path'
|
||||||
|
import assets from '~static-assets'
|
||||||
|
|
||||||
|
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: code + (chunk.isEntry ? 'global.mainDir="undefined"!=typeof __dirname?__dirname:require.main.filename;' : ''),
|
||||||
|
map: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
packages/nitro/src/rollup/plugins/timing.ts
Normal file
40
packages/nitro/src/rollup/plugins/timing.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { extname } from 'upath'
|
||||||
|
import type { Plugin, RenderedChunk } from 'rollup'
|
||||||
|
|
||||||
|
export interface Options { }
|
||||||
|
|
||||||
|
const TIMING = 'global.__timing__'
|
||||||
|
|
||||||
|
const iife = code => `(function() { ${code.trim()} })();`.replace(/\n/g, '')
|
||||||
|
|
||||||
|
// https://gist.github.com/pi0/1476085924f8a2eb1df85929c20cb43f
|
||||||
|
const POLYFILL = `const global="undefined"!=typeof globalThis?globalThis:void 0!==o?o:"undefined"!=typeof self?self:{};
|
||||||
|
global.process = global.process || {};
|
||||||
|
const o=Date.now(),t=()=>Date.now()-o;global.process.hrtime=global.process.hrtime||(o=>{const e=Math.floor(.001*(Date.now()-t())),a=.001*t();let l=Math.floor(a)+e,n=Math.floor(a%1*1e9);return o&&(l-=o[0],n-=o[1],n<0&&(l--,n+=1e9)),[l,n]});`
|
||||||
|
|
||||||
|
const HELPER = POLYFILL + iife(`
|
||||||
|
const hrtime = global.process.hrtime;
|
||||||
|
const start = () => hrtime();
|
||||||
|
const end = s => { const d = hrtime(s); return ((d[0] * 1e9) + d[1]) / 1e6; };
|
||||||
|
|
||||||
|
const _s = {};
|
||||||
|
const metrics = [];
|
||||||
|
const logStart = id => { _s[id] = hrtime(); };
|
||||||
|
const logEnd = id => { const t = end(_s[id]); delete _s[id]; metrics.push([id, t]); console.debug('>', id + ' (' + t + 'ms)'); };
|
||||||
|
${TIMING} = { hrtime, start, end, metrics, logStart, logEnd };
|
||||||
|
`)
|
||||||
|
|
||||||
|
export function timing (_opts: Options = {}): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'timing',
|
||||||
|
renderChunk (code, chunk: RenderedChunk) {
|
||||||
|
let name = chunk.fileName || ''
|
||||||
|
name = name.replace(extname(name), '')
|
||||||
|
const logName = name === 'index' ? 'Cold Start' : ('Load ' + name)
|
||||||
|
return {
|
||||||
|
code: (chunk.isEntry ? HELPER : '') + `${TIMING}.logStart('${logName}');` + code + `;${TIMING}.logEnd('${logName}');`,
|
||||||
|
map: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
packages/nitro/src/rollup/plugins/virtual.ts
Normal file
49
packages/nitro/src/rollup/plugins/virtual.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Based on https://github.com/rollup/plugins/blob/master/packages/virtual/src/index.ts
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
import { Plugin } from 'rollup'
|
||||||
|
|
||||||
|
type UnresolvedModule = string | (() => string)
|
||||||
|
export interface RollupVirtualOptions {
|
||||||
|
[id: string]: UnresolvedModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PREFIX = '\0virtual:'
|
||||||
|
|
||||||
|
const resolveModule = (m: UnresolvedModule) => typeof m === 'function' ? m() : m
|
||||||
|
|
||||||
|
export default function virtual (modules: RollupVirtualOptions): Plugin {
|
||||||
|
const resolvedIds = new Map<string, string |(() => string)>()
|
||||||
|
|
||||||
|
Object.keys(modules).forEach((id) => {
|
||||||
|
resolvedIds.set(path.resolve(id), modules[id])
|
||||||
|
})
|
||||||
|
|
||||||
|
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 = path.resolve(path.dirname(importerNoPrefix), id)
|
||||||
|
if (resolvedIds.has(resolved)) { return PREFIX + resolved }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
load (id) {
|
||||||
|
if (!id.startsWith(PREFIX)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const idNoPrefix = id.slice(PREFIX.length)
|
||||||
|
return idNoPrefix in modules
|
||||||
|
? resolveModule(modules[idNoPrefix])
|
||||||
|
: resolveModule(resolvedIds.get(idNoPrefix))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
packages/nitro/src/runtime/app/config.ts
Normal file
19
packages/nitro/src/runtime/app/config.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import destr from 'destr'
|
||||||
|
|
||||||
|
const runtimeConfig = process.env.RUNTIME_CONFIG as any
|
||||||
|
|
||||||
|
for (const type of ['private', 'public']) {
|
||||||
|
for (const key in runtimeConfig[type]) {
|
||||||
|
runtimeConfig[type][key] = destr(process.env[key] || runtimeConfig[type][key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const $config = global.$config = {
|
||||||
|
...runtimeConfig.public,
|
||||||
|
...runtimeConfig.private
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
public: runtimeConfig.public,
|
||||||
|
private: $config
|
||||||
|
}
|
8
packages/nitro/src/runtime/app/nitro.client.js
Normal file
8
packages/nitro/src/runtime/app/nitro.client.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import _global from '@nuxt/un/runtime/global'
|
||||||
|
import { $fetch } from 'ohmyfetch'
|
||||||
|
|
||||||
|
_global.process = _global.process || {};
|
||||||
|
|
||||||
|
(function () { const o = Date.now(); const t = () => Date.now() - o; _global.process.hrtime = _global.process.hrtime || ((o) => { const e = Math.floor(0.001 * (Date.now() - t())); const a = 0.001 * t(); let l = Math.floor(a) + e; let n = Math.floor(a % 1 * 1e9); return o && (l -= o[0], n -= o[1], n < 0 && (l--, n += 1e9)), [l, n] }) })()
|
||||||
|
|
||||||
|
global.$fetch = $fetch
|
82
packages/nitro/src/runtime/app/render.ts
Normal file
82
packages/nitro/src/runtime/app/render.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { createRenderer } from 'vue-bundle-renderer'
|
||||||
|
import devalue from '@nuxt/devalue'
|
||||||
|
import config from './config'
|
||||||
|
// @ts-ignore
|
||||||
|
import { renderToString } from '~renderer'
|
||||||
|
// @ts-ignore
|
||||||
|
import createApp from '~build/dist/server/server'
|
||||||
|
// @ts-ignore
|
||||||
|
import clientManifest from '~build/dist/server/client.manifest.json'
|
||||||
|
// @ts-ignore
|
||||||
|
import htmlTemplate from '~build/views/document.template.js'
|
||||||
|
|
||||||
|
function _interopDefault (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e }
|
||||||
|
|
||||||
|
const renderer = createRenderer(_interopDefault(createApp), {
|
||||||
|
clientManifest: _interopDefault(clientManifest),
|
||||||
|
renderToString
|
||||||
|
})
|
||||||
|
|
||||||
|
const STATIC_ASSETS_BASE = process.env.NUXT_STATIC_BASE + '/' + process.env.NUXT_STATIC_VERSION
|
||||||
|
const PAYLOAD_JS = '/payload.js'
|
||||||
|
|
||||||
|
export async function renderMiddleware (req, res) {
|
||||||
|
let url = req.url
|
||||||
|
|
||||||
|
// payload.json request detection
|
||||||
|
let isPayloadReq = false
|
||||||
|
if (url.startsWith(STATIC_ASSETS_BASE) && url.endsWith(PAYLOAD_JS)) {
|
||||||
|
isPayloadReq = true
|
||||||
|
url = url.substr(STATIC_ASSETS_BASE.length, url.length - STATIC_ASSETS_BASE.length - PAYLOAD_JS.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssrContext = {
|
||||||
|
url,
|
||||||
|
runtimeConfig: {
|
||||||
|
public: config.public,
|
||||||
|
private: config.private
|
||||||
|
},
|
||||||
|
...(req.context || {})
|
||||||
|
}
|
||||||
|
const rendered = await renderer.renderToString(ssrContext)
|
||||||
|
// TODO: nuxt3 should not reuse `nuxt` property for different purpose!
|
||||||
|
const payload = ssrContext.payload /* nuxt 3 */ || ssrContext.nuxt /* nuxt 2 */
|
||||||
|
|
||||||
|
if (process.env.NUXT_FULL_STATIC) {
|
||||||
|
payload.staticAssetsBase = STATIC_ASSETS_BASE
|
||||||
|
}
|
||||||
|
|
||||||
|
let data
|
||||||
|
if (isPayloadReq) {
|
||||||
|
data = renderPayload(payload, url)
|
||||||
|
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8')
|
||||||
|
} else {
|
||||||
|
data = renderHTML(payload, rendered, ssrContext)
|
||||||
|
res.setHeader('Content-Type', 'text/html;charset=UTF-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = ssrContext.nuxt && ssrContext.nuxt.error
|
||||||
|
res.statusCode = error ? error.statusCode : 200
|
||||||
|
res.end(data, 'utf-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHTML (payload, rendered, ssrContext) {
|
||||||
|
const state = `<script>window.__NUXT__=${devalue(payload)}</script>`
|
||||||
|
const _html = rendered.html
|
||||||
|
|
||||||
|
const { htmlAttrs = '', bodyAttrs = '', headTags = '', headAttrs = '' } =
|
||||||
|
(ssrContext.head && ssrContext.head()) || {}
|
||||||
|
|
||||||
|
return htmlTemplate({
|
||||||
|
HTML_ATTRS: htmlAttrs,
|
||||||
|
HEAD_ATTRS: headAttrs,
|
||||||
|
BODY_ATTRS: bodyAttrs,
|
||||||
|
HEAD: headTags +
|
||||||
|
rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''),
|
||||||
|
APP: _html + state + rendered.renderScripts()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPayload (payload, url) {
|
||||||
|
return `__NUXT_JSONP__("${url}", ${devalue(payload)})`
|
||||||
|
}
|
12
packages/nitro/src/runtime/app/vue2.basic.ts
Normal file
12
packages/nitro/src/runtime/app/vue2.basic.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
15
packages/nitro/src/runtime/app/vue2.ts
Normal file
15
packages/nitro/src/runtime/app/vue2.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { createRenderer } from '~vueServerRenderer'
|
||||||
|
|
||||||
|
const _renderer = createRenderer({})
|
||||||
|
|
||||||
|
export function renderToString (component, context) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
_renderer.renderToString(component, context, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
return resolve(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
2
packages/nitro/src/runtime/app/vue3.ts
Normal file
2
packages/nitro/src/runtime/app/vue3.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
export { renderToString } from '@vue/server-renderer'
|
28
packages/nitro/src/runtime/entries/azure.ts
Normal file
28
packages/nitro/src/runtime/entries/azure.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { parseURL } from 'ufo'
|
||||||
|
import { localCall } from '../server'
|
||||||
|
|
||||||
|
export default 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
|
||||||
|
}
|
||||||
|
}
|
19
packages/nitro/src/runtime/entries/azure_functions.ts
Normal file
19
packages/nitro/src/runtime/entries/azure_functions.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { localCall } from '../server'
|
||||||
|
|
||||||
|
export default 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
|
||||||
|
}
|
||||||
|
}
|
24
packages/nitro/src/runtime/entries/cli.ts
Normal file
24
packages/nitro/src/runtime/entries/cli.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
47
packages/nitro/src/runtime/entries/cloudflare.ts
Normal file
47
packages/nitro/src/runtime/entries/cloudflare.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { getAssetFromKV } from '@cloudflare/kv-asset-handler'
|
||||||
|
import { localCall } from '../server'
|
||||||
|
|
||||||
|
const PUBLIC_PATH = process.env.PUBLIC_PATH // Default: /_nuxt/
|
||||||
|
|
||||||
|
addEventListener('fetch', (event: any) => {
|
||||||
|
event.respondWith(handleEvent(event))
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handleEvent (event) {
|
||||||
|
try {
|
||||||
|
return await getAssetFromKV(event, { cacheControl: assetsCacheControl })
|
||||||
|
} catch (_err) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(event.request.url)
|
||||||
|
|
||||||
|
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: event.request.body
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Response(r.body, {
|
||||||
|
// @ts-ignore
|
||||||
|
headers: r.headers,
|
||||||
|
status: r.status,
|
||||||
|
statusText: r.statusText
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function assetsCacheControl (request) {
|
||||||
|
if (request.url.includes(PUBLIC_PATH) /* TODO: Check with routerBase */) {
|
||||||
|
return {
|
||||||
|
browserTTL: 31536000,
|
||||||
|
edgeTTL: 31536000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
14
packages/nitro/src/runtime/entries/dev.ts
Normal file
14
packages/nitro/src/runtime/entries/dev.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { Server } from 'http'
|
||||||
|
import { parentPort } from 'worker_threads'
|
||||||
|
import type { AddressInfo } from 'net'
|
||||||
|
import { handle } from '../server'
|
||||||
|
|
||||||
|
const server = new Server(handle)
|
||||||
|
|
||||||
|
const netServer = server.listen(0, () => {
|
||||||
|
parentPort.postMessage({
|
||||||
|
event: 'listen',
|
||||||
|
port: (netServer.address() as AddressInfo).port
|
||||||
|
})
|
||||||
|
})
|
7
packages/nitro/src/runtime/entries/firebase.ts
Normal file
7
packages/nitro/src/runtime/entries/firebase.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
|
||||||
|
import { handle } from '../server'
|
||||||
|
|
||||||
|
const functions = require('firebase-functions')
|
||||||
|
|
||||||
|
export const server = functions.https.onRequest(handle)
|
21
packages/nitro/src/runtime/entries/lambda.ts
Normal file
21
packages/nitro/src/runtime/entries/lambda.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { withQuery } from 'ufo'
|
||||||
|
import { localCall } from '../server'
|
||||||
|
|
||||||
|
export async function handler (event, context) {
|
||||||
|
const r = await localCall({
|
||||||
|
event,
|
||||||
|
url: withQuery(event.path, event.queryStringParameters),
|
||||||
|
context,
|
||||||
|
headers: event.headers,
|
||||||
|
method: event.httpMethod,
|
||||||
|
query: event.queryStringParameters,
|
||||||
|
body: event.body // TODO: handle event.isBase64Encoded
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: r.status,
|
||||||
|
headers: r.headers,
|
||||||
|
body: r.body.toString()
|
||||||
|
}
|
||||||
|
}
|
2
packages/nitro/src/runtime/entries/node.ts
Normal file
2
packages/nitro/src/runtime/entries/node.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
export * from '../server'
|
20
packages/nitro/src/runtime/entries/server.ts
Normal file
20
packages/nitro/src/runtime/entries/server.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { Server } from 'http'
|
||||||
|
import destr from 'destr'
|
||||||
|
import { handle } from '../server'
|
||||||
|
|
||||||
|
const server = new Server(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)
|
||||||
|
}
|
||||||
|
console.log(`Listening on http://${hostname}:${port}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default {}
|
40
packages/nitro/src/runtime/entries/service-worker.ts
Normal file
40
packages/nitro/src/runtime/entries/service-worker.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import '~polyfill'
|
||||||
|
import { localCall } from '../server'
|
||||||
|
|
||||||
|
addEventListener('fetch', (event: any) => {
|
||||||
|
const url = new URL(event.request.url)
|
||||||
|
|
||||||
|
if (url.pathname.includes('.') /* is file */) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(handleEvent(url, event))
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handleEvent (url, event) {
|
||||||
|
const r = await localCall({
|
||||||
|
event,
|
||||||
|
url: url.pathname,
|
||||||
|
host: url.hostname,
|
||||||
|
protocol: url.protocol,
|
||||||
|
headers: event.request.headers,
|
||||||
|
method: event.request.method,
|
||||||
|
redirect: event.request.redirect,
|
||||||
|
body: event.request.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())
|
||||||
|
})
|
4
packages/nitro/src/runtime/entries/vercel.ts
Normal file
4
packages/nitro/src/runtime/entries/vercel.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import '~polyfill'
|
||||||
|
import { handle } from '../server'
|
||||||
|
|
||||||
|
export default handle
|
67
packages/nitro/src/runtime/server/error.ts
Normal file
67
packages/nitro/src/runtime/server/error.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// import ansiHTML from 'ansi-html'
|
||||||
|
const cwd = process.cwd()
|
||||||
|
|
||||||
|
// TODO: Handle process.env.DEBUG
|
||||||
|
export function handleError (error, req, res) {
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error(error.message + '\n' + stack.map(l => ' ' + l.text).join(' \n'))
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Nuxt Error</title>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
background: white;
|
||||||
|
color: red;
|
||||||
|
font-family: monospace;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.stack {
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
.stack.internal {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div>${req.method} ${req.url}</div><br>
|
||||||
|
<h1>${error.toString()}</h1>
|
||||||
|
<pre>${stack.map(i =>
|
||||||
|
`<span class="stack${i.internal ? ' internal' : ''}">${i.text}</span>`
|
||||||
|
).join('\n')
|
||||||
|
}</pre>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
res.statusCode = error.statusCode || 500
|
||||||
|
res.statusMessage = error.statusMessage || 'Internal Error'
|
||||||
|
res.end(html)
|
||||||
|
}
|
24
packages/nitro/src/runtime/server/index.ts
Normal file
24
packages/nitro/src/runtime/server/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import '../app/config'
|
||||||
|
import { createApp, useBase } from 'h3'
|
||||||
|
import { createFetch } from 'ohmyfetch'
|
||||||
|
import destr from 'destr'
|
||||||
|
import { createCall, createFetch as createLocalFetch } from '@nuxt/un/runtime/fetch'
|
||||||
|
import { timingMiddleware } from './timing'
|
||||||
|
import { handleError } from './error'
|
||||||
|
// @ts-ignore
|
||||||
|
import serverMiddleware from '~serverMiddleware'
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
debug: destr(process.env.DEBUG),
|
||||||
|
onError: handleError
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(timingMiddleware)
|
||||||
|
app.use(serverMiddleware)
|
||||||
|
app.use(() => import('../app/render').then(e => e.renderMiddleware), { lazy: true })
|
||||||
|
|
||||||
|
export const stack = app.stack
|
||||||
|
export const handle = useBase(process.env.ROUTER_BASE, app)
|
||||||
|
export const localCall = createCall(handle)
|
||||||
|
export const localFetch = createLocalFetch(localCall, global.fetch)
|
||||||
|
export const $fetch = global.$fetch = createFetch({ fetch: localFetch })
|
71
packages/nitro/src/runtime/server/static.ts
Normal file
71
packages/nitro/src/runtime/server/static.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { createError } from 'h3'
|
||||||
|
import { withoutTrailingSlash, withLeadingSlash, parseURL } from 'ufo'
|
||||||
|
// @ts-ignore
|
||||||
|
import { getAsset, readAsset } from '~static'
|
||||||
|
|
||||||
|
const METHODS = ['HEAD', 'GET']
|
||||||
|
const PUBLIC_PATH = process.env.PUBLIC_PATH // Default: /_nuxt/
|
||||||
|
const TWO_DAYS = 2 * 60 * 60 * 24
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export default async function serveStatic(req, res) {
|
||||||
|
if (!METHODS.includes(req.method)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = withLeadingSlash(withoutTrailingSlash(parseURL(req.url).pathname))
|
||||||
|
let asset = getAsset(id)
|
||||||
|
|
||||||
|
// Try index.html
|
||||||
|
if (!asset) {
|
||||||
|
const _id = id + '/index.html'
|
||||||
|
const _asset = getAsset(_id)
|
||||||
|
if (_asset) {
|
||||||
|
asset = _asset
|
||||||
|
id = _id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!asset) {
|
||||||
|
if (id.startsWith(PUBLIC_PATH)) {
|
||||||
|
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 (id.startsWith(PUBLIC_PATH)) {
|
||||||
|
res.setHeader('Cache-Control', `max-age=${TWO_DAYS}, immutable`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = await readAsset(id)
|
||||||
|
return res.end(contents)
|
||||||
|
}
|
22
packages/nitro/src/runtime/server/timing.ts
Normal file
22
packages/nitro/src/runtime/server/timing.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export const globalTiming = global.__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()
|
||||||
|
}
|
6
packages/nitro/src/runtime/types.d.ts
vendored
Normal file
6
packages/nitro/src/runtime/types.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
declare module NodeJS {
|
||||||
|
interface Global {
|
||||||
|
__timing__: any
|
||||||
|
$config: any
|
||||||
|
}
|
||||||
|
}
|
143
packages/nitro/src/server/dev.ts
Normal file
143
packages/nitro/src/server/dev.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { Worker } from 'worker_threads'
|
||||||
|
import { createApp } from 'h3'
|
||||||
|
import { resolve } from 'upath'
|
||||||
|
import debounce from 'debounce'
|
||||||
|
import chokidar from 'chokidar'
|
||||||
|
import { listen, Listener } from 'listhen'
|
||||||
|
import serveStatic from 'serve-static'
|
||||||
|
import servePlaceholder from 'serve-placeholder'
|
||||||
|
import { createProxy } from 'http-proxy'
|
||||||
|
import { stat } from 'fs-extra'
|
||||||
|
import type { NitroContext } from '../context'
|
||||||
|
|
||||||
|
export function createDevServer (nitroContext: NitroContext) {
|
||||||
|
// Worker
|
||||||
|
const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.js')
|
||||||
|
let pendingWorker: Worker
|
||||||
|
let activeWorker: Worker
|
||||||
|
let workerAddress: string
|
||||||
|
async function reload () {
|
||||||
|
if (pendingWorker) {
|
||||||
|
await pendingWorker.terminate()
|
||||||
|
workerAddress = null
|
||||||
|
pendingWorker = null
|
||||||
|
}
|
||||||
|
if (!(await stat(workerEntry)).isFile) {
|
||||||
|
throw new Error('Entry not found: ' + workerEntry)
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const worker = pendingWorker = new Worker(workerEntry)
|
||||||
|
worker.once('exit', (code) => {
|
||||||
|
if (code) {
|
||||||
|
reject(new Error('[worker] exited with code: ' + code))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
worker.on('error', (err) => {
|
||||||
|
err.message = '[worker] ' + err.message
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
worker.on('message', (event) => {
|
||||||
|
if (event && event.port) {
|
||||||
|
workerAddress = 'http://localhost:' + event.port
|
||||||
|
activeWorker = worker
|
||||||
|
pendingWorker = null
|
||||||
|
resolve(workerAddress)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// App
|
||||||
|
const app = createApp()
|
||||||
|
|
||||||
|
// _nuxt and static
|
||||||
|
app.use(nitroContext._nuxt.publicPath, serveStatic(resolve(nitroContext._nuxt.buildDir, 'dist/client')))
|
||||||
|
app.use(nitroContext._nuxt.routerBase, serveStatic(resolve(nitroContext._nuxt.staticDir)))
|
||||||
|
|
||||||
|
// 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(nitroContext._nuxt.publicPath, servePlaceholder())
|
||||||
|
app.use(nitroContext._nuxt.routerBase, servePlaceholder({ skipUnknown: true }))
|
||||||
|
|
||||||
|
// SSR Proxy
|
||||||
|
const proxy = createProxy()
|
||||||
|
app.use((req, res) => {
|
||||||
|
if (workerAddress) {
|
||||||
|
proxy.web(req, res, { target: workerAddress }, (_err) => {
|
||||||
|
// console.error('[proxy]', err)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.end('Worker not ready!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen
|
||||||
|
let listeners: Listener[] = []
|
||||||
|
const _listen = async (port, opts?) => {
|
||||||
|
const listener = await listen(app, { port, ...opts })
|
||||||
|
listeners.push(listener)
|
||||||
|
return listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for dist and reload worker
|
||||||
|
const pattern = '**/*.{js,json}'
|
||||||
|
const events = ['add', 'change']
|
||||||
|
let watcher
|
||||||
|
function watch () {
|
||||||
|
if (watcher) { return }
|
||||||
|
const dReload = debounce(() => reload().catch(console.warn), 200, true)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
if (activeWorker) {
|
||||||
|
await activeWorker.terminate()
|
||||||
|
}
|
||||||
|
if (pendingWorker) {
|
||||||
|
await pendingWorker.terminate()
|
||||||
|
}
|
||||||
|
await Promise.all(listeners.map(l => l.close()))
|
||||||
|
listeners = []
|
||||||
|
}
|
||||||
|
nitroContext._internal.hooks.hook('close', close)
|
||||||
|
|
||||||
|
return {
|
||||||
|
reload,
|
||||||
|
listen: _listen,
|
||||||
|
close,
|
||||||
|
watch,
|
||||||
|
setLegacyMiddleware: legacyMiddleware.set,
|
||||||
|
setDevMiddleware: devMiddleware.set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDynamicMiddleware () {
|
||||||
|
let middleware
|
||||||
|
return {
|
||||||
|
set: (input) => {
|
||||||
|
if (!Array.isArray(input)) {
|
||||||
|
middleware = input
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const app = require('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()
|
||||||
|
}
|
||||||
|
}
|
78
packages/nitro/src/server/middleware.ts
Normal file
78
packages/nitro/src/server/middleware.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { resolve, join, extname } from 'upath'
|
||||||
|
import { joinURL } from 'ufo'
|
||||||
|
import globby from 'globby'
|
||||||
|
import { watch } from 'chokidar'
|
||||||
|
|
||||||
|
export interface ServerMiddleware {
|
||||||
|
route: string
|
||||||
|
handle: string
|
||||||
|
lazy?: boolean // Default is true
|
||||||
|
promisify?: boolean // Default is true
|
||||||
|
}
|
||||||
|
|
||||||
|
function filesToMiddleware (files: string[], baseDir: string, basePath: string, overrides?: Partial<ServerMiddleware>): ServerMiddleware[] {
|
||||||
|
return files.map((file) => {
|
||||||
|
const route = joinURL(basePath, file.substr(0, file.length - extname(file).length))
|
||||||
|
const handle = resolve(baseDir, file)
|
||||||
|
return {
|
||||||
|
route,
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.route.localeCompare(b.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 = '**/*.{js,ts}'
|
||||||
|
const globalDir = resolve(serverDir, 'middleware')
|
||||||
|
const apiDir = resolve(serverDir, 'api')
|
||||||
|
|
||||||
|
const scan = async () => {
|
||||||
|
const globalFiles = await globby(pattern, { cwd: globalDir })
|
||||||
|
const apiFiles = await globby(pattern, { cwd: apiDir })
|
||||||
|
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 function resolveMiddleware (serverMiddleware: any[], resolvePath: (string) => string) {
|
||||||
|
const middleware: ServerMiddleware[] = []
|
||||||
|
const legacyMiddleware: ServerMiddleware[] = []
|
||||||
|
|
||||||
|
for (let m of serverMiddleware) {
|
||||||
|
if (typeof m === 'string') { 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: resolvePath(handle),
|
||||||
|
route
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
middleware,
|
||||||
|
legacyMiddleware
|
||||||
|
}
|
||||||
|
}
|
11
packages/nitro/src/types.ts
Normal file
11
packages/nitro/src/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { $Fetch } from 'ohmyfetch'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
const $fetch: $Fetch
|
||||||
|
|
||||||
|
namespace NodeJS {
|
||||||
|
interface Global {
|
||||||
|
$fetch: $Fetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
packages/nitro/src/utils/index.ts
Normal file
122
packages/nitro/src/utils/index.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { relative, dirname, resolve } from 'upath'
|
||||||
|
import fse from 'fs-extra'
|
||||||
|
import jiti from 'jiti'
|
||||||
|
import defu from 'defu'
|
||||||
|
import Hookable from 'hookable'
|
||||||
|
import consola from 'consola'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import { get } from 'dot-prop'
|
||||||
|
import type { NitroPreset, NitroInput } from '../context'
|
||||||
|
|
||||||
|
export const MODULE_DIR = resolve(__dirname, '..')
|
||||||
|
|
||||||
|
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 = get(params, match)
|
||||||
|
if (!val) {
|
||||||
|
consola.warn(`cannot resolve template param '${match}' in ${contents.substr(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)(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tryImport (dir: string, path: string) {
|
||||||
|
try {
|
||||||
|
return jitiImport(dir, path)
|
||||||
|
} catch (_err) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function writeFile (file, contents, log = false) {
|
||||||
|
await fse.mkdirp(dirname(file))
|
||||||
|
await fse.writeFile(file, contents, 'utf-8')
|
||||||
|
if (log) {
|
||||||
|
consola.info('Generated', prettyPath(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePath (nitroContext: NitroInput, path: string | ((nitroContext) => string), resolveBase: string = ''): string {
|
||||||
|
if (typeof path === 'function') {
|
||||||
|
path = path(nitroContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof path !== 'string') {
|
||||||
|
throw new TypeError('Invalid path: ' + path)
|
||||||
|
}
|
||||||
|
|
||||||
|
path = compileTemplate(path)(nitroContext)
|
||||||
|
|
||||||
|
return resolve(resolveBase, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detectTarget () {
|
||||||
|
if (process.env.NETLIFY) {
|
||||||
|
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: Hookable.mergeHooks(base.hooks, preset.hooks)
|
||||||
|
}, preset, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _getDependenciesMode = {
|
||||||
|
dev: ['devDependencies'],
|
||||||
|
prod: ['dependencies'],
|
||||||
|
all: ['devDependencies', 'dependencies']
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
50
packages/nitro/src/utils/tree.ts
Normal file
50
packages/nitro/src/utils/tree.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { resolve, dirname, relative } from 'upath'
|
||||||
|
import globby from 'globby'
|
||||||
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
import gzipSize from 'gzip-size'
|
||||||
|
import { readFile } from 'fs-extra'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import stdenv from 'std-env'
|
||||||
|
|
||||||
|
export async function printFSTree (dir) {
|
||||||
|
if (stdenv.test) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await globby('**/*.*', { cwd: dir })
|
||||||
|
|
||||||
|
const items = (await Promise.all(files.map(async (file) => {
|
||||||
|
const path = resolve(dir, file)
|
||||||
|
const src = await 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`)
|
||||||
|
}
|
7
packages/nitro/src/utils/wpfs.ts
Normal file
7
packages/nitro/src/utils/wpfs.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { join } from 'upath'
|
||||||
|
import fsExtra from 'fs-extra'
|
||||||
|
|
||||||
|
export const wpfs = {
|
||||||
|
...fsExtra,
|
||||||
|
join
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user