mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 13:15:12 +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