Skip to content
Permalink
8d14cd4781
Go to file
 
 
Cannot retrieve contributors at this time
276 lines (225 sloc) 7.48 KB
import path from 'path'
import consola from 'consola'
import launchMiddleware from 'launch-editor-middleware'
import serveStatic from 'serve-static'
import servePlaceholder from 'serve-placeholder'
import connect from 'connect'
import { determineGlobals, isUrl } from '@nuxt/utils'
import ServerContext from './context'
import renderAndGetWindow from './jsdom'
import nuxtMiddleware from './middleware/nuxt'
import errorMiddleware from './middleware/error'
import Listener from './listener'
import createModernMiddleware from './middleware/modern'
import createTimingMiddleware from './middleware/timing'
export default class Server {
constructor(nuxt) {
this.nuxt = nuxt
this.options = nuxt.options
this.globals = determineGlobals(nuxt.options.globalName, nuxt.options.globals)
this.publicPath = isUrl(this.options.build.publicPath)
? this.options.build._publicPath
: this.options.build.publicPath
// Runtime shared resources
this.resources = {}
// Will be set after listen
this.listeners = []
// Create new connect instance
this.app = connect()
// Close hook
this.nuxt.hook('close', () => this.close())
// devMiddleware placeholder
if (this.options.dev) {
this.nuxt.hook('server:devMiddleware', (devMiddleware) => {
this.devMiddleware = devMiddleware
})
}
}
async ready() {
if (this._readyCalled) {
return this
}
this._readyCalled = true
await this.nuxt.callHook('render:before', this, this.options.render)
// Initialize vue-renderer
const { VueRenderer } = await import('@nuxt/vue-renderer')
const context = new ServerContext(this)
this.renderer = new VueRenderer(context)
await this.renderer.ready()
// Setup nuxt middleware
await this.setupMiddleware()
// Call done hook
await this.nuxt.callHook('render:done', this)
return this
}
async setupMiddleware() {
// Apply setupMiddleware from modules first
await this.nuxt.callHook('render:setupMiddleware', this.app)
// Compression middleware for production
if (!this.options.dev) {
const { compressor } = this.options.render
if (typeof compressor === 'object') {
// If only setting for `compression` are provided, require the module and insert
const compression = this.nuxt.resolver.requireModule('compression')
this.useMiddleware(compression(compressor))
} else if (compressor) {
// Else, require own compression middleware if compressor is actually truthy
this.useMiddleware(compressor)
}
}
if (this.options.server.timing) {
this.useMiddleware(createTimingMiddleware(this.options.server.timing))
}
// For serving static/ files to /
const staticMiddleware = serveStatic(
path.resolve(this.options.srcDir, this.options.dir.static),
this.options.render.static
)
staticMiddleware.prefix = this.options.render.static.prefix
this.useMiddleware(staticMiddleware)
// Serve .nuxt/dist/client files only for production
// For dev they will be served with devMiddleware
if (!this.options.dev) {
const distDir = path.resolve(this.options.buildDir, 'dist', 'client')
this.useMiddleware({
path: this.publicPath,
handler: serveStatic(
distDir,
this.options.render.dist
)
})
}
this.useMiddleware(createModernMiddleware({
context: this.renderer.context
}))
// Dev middleware
if (this.options.dev) {
this.useMiddleware((req, res, next) => {
if (!this.devMiddleware) {
return next()
}
this.devMiddleware(req, res, next)
})
// open in editor for debug mode only
if (this.options.debug) {
this.useMiddleware({
path: '__open-in-editor',
handler: launchMiddleware(this.options.editor)
})
}
}
// Add user provided middleware
for (const m of this.options.serverMiddleware) {
this.useMiddleware(m)
}
// Graceful 404 error handler
const { fallback } = this.options.render
if (fallback) {
// Dist files
if (fallback.dist) {
this.useMiddleware({
path: this.publicPath,
handler: servePlaceholder(fallback.dist)
})
}
// Other paths
if (fallback.static) {
this.useMiddleware({
path: '/',
handler: servePlaceholder(fallback.static)
})
}
}
// Finally use nuxtMiddleware
this.useMiddleware(nuxtMiddleware({
options: this.options,
nuxt: this.nuxt,
renderRoute: this.renderRoute.bind(this),
resources: this.resources
}))
// Apply errorMiddleware from modules first
await this.nuxt.callHook('render:errorMiddleware', this.app)
// Error middleware for errors that occurred in middleware that declared above
this.useMiddleware(errorMiddleware({
resources: this.resources,
options: this.options
}))
}
useMiddleware(middleware) {
let handler = middleware.handler || middleware
// Resolve handler setup as string (path)
if (typeof handler === 'string') {
try {
const requiredModuleFromHandlerPath = this.nuxt.resolver.requireModule(handler)
// In case the "handler" is not derived from an object but is a normal string, another object with
// path and handler could be the result
// If the required module has handler, treat the module as new "middleware" object
if (requiredModuleFromHandlerPath.handler) {
middleware = requiredModuleFromHandlerPath
}
handler = requiredModuleFromHandlerPath.handler || requiredModuleFromHandlerPath
} catch (err) {
consola.error(err)
// Throw error in production mode
if (!this.options.dev) {
throw err
}
}
}
// Resolve path
const path = (
(middleware.prefix !== false ? this.options.router.base : '') +
(typeof middleware.path === 'string' ? middleware.path : '')
).replace(/\/\//g, '/')
// Use middleware
this.app.use(path, handler)
}
renderRoute() {
return this.renderer.renderRoute.apply(this.renderer, arguments)
}
loadResources() {
return this.renderer.loadResources.apply(this.renderer, arguments)
}
renderAndGetWindow(url, opts = {}) {
return renderAndGetWindow(url, opts, {
loadedCallback: this.globals.loadedCallback,
ssr: this.options.render.ssr,
globals: this.globals
})
}
async listen(port, host, socket) {
// Ensure nuxt is ready
await this.nuxt.ready()
// Create a new listener
const listener = new Listener({
port: isNaN(parseInt(port)) ? this.options.server.port : port,
host: host || this.options.server.host,
socket: socket || this.options.server.socket,
https: this.options.server.https,
app: this.app,
dev: this.options.dev,
baseURL: this.options.router.base
})
// Listen
await listener.listen()
// Push listener to this.listeners
this.listeners.push(listener)
await this.nuxt.callHook('listen', listener.server, listener)
}
async close() {
if (this.__closed) {
return
}
this.__closed = true
await Promise.all(this.listeners.map(l => l.close()))
this.listeners = []
if (typeof this.renderer.close === 'function') {
await this.renderer.close()
}
this.app.removeAllListeners()
this.app = null
for (const key in this.resources) {
delete this.resources[key]
}
}
}
You can’t perform that action at this time.