move serverMiddleware to renderer

This commit is contained in:
Pooya Parsa 2017-06-20 03:45:57 +04:30
parent 0dabc38785
commit 185dfc1ecf
3 changed files with 107 additions and 94 deletions

View File

@ -17,7 +17,7 @@ export default class Nuxt extends Tapable {
this.renderer = new Renderer(this)
// Backward compatibility
this.render = this.renderer.render.bind(this.renderer)
this.render = (...args) => this.renderer.app(...args)
this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(this.renderer)

View File

@ -10,8 +10,9 @@ import _ from 'lodash'
import { join, resolve } from 'path'
import fs from 'fs-extra'
import { createBundleRenderer } from 'vue-server-renderer'
import { encodeHtml, getContext, setAnsiColors } from 'utils'
import { encodeHtml, getContext, setAnsiColors, isUrl } from 'utils'
import Debug from 'debug'
import connect from 'connect'
const debug = Debug('nuxt:render')
debug.color = 4 // Force blue color
@ -58,6 +59,9 @@ export default class Renderer extends Tapable {
this.webpackDevMiddleware = null
this.webpackHotMiddleware = null
// Create new connect instance
this.app = connect()
// Renderer runtime resources
this.resources = {
clientManifest: null,
@ -86,25 +90,16 @@ export default class Renderer extends Tapable {
return this._ready
}
// For serving static/ files to /
this.serveStatic = pify(serveStatic(resolve(this.options.srcDir, 'static'), this.options.render.static))
// For serving .nuxt/dist/ files
this.serveStaticNuxt = pify(serveStatic(resolve(this.options.buildDir, 'dist'), {
maxAge: (this.options.dev ? 0 : '1y') // 1 year in production
}))
// GZIP middleware for production
if (!this.options.dev && this.options.render.gzip) {
this.gzipMiddleware = pify(compression(this.options.render.gzip))
}
// Setup all middleWare
this.setupMiddleware()
// Load error template
const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html')
if (fs.existsSync(errorTemplatePath)) {
this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8'))
}
// Load resources from fs
// Load SSR resources from fs
if (!this.options.dev) {
await this.loadResources()
}
@ -166,62 +161,105 @@ export default class Renderer extends Tapable {
this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString)
}
async render (req, res) {
// Get context
const context = getContext(req, res)
res.statusCode = 200
try {
// Gzip middleware for production
if (this.gzipMiddleware) {
await this.gzipMiddleware(req, res)
useMiddleware (m) {
// Resolve
if (typeof m === 'string') {
let src = m
// Using ~ or ./ shorthand to resolve from project srcDir
if (src.indexOf('~') === 0 || src.indexOf('./') === 0) {
src = join(this.nuxt.options.srcDir, src.substr(1))
}
m = require(src)
}
// Use middleware
if (m instanceof Function) {
this.app.use(m)
} else if (m && m.path && m.handler) {
this.app.use(m.path, m.handler)
}
}
setupMiddleware () {
// Gzip middleware for production
if (!this.options.dev && this.options.render.gzip) {
this.useMiddleware(compression(this.options.render.gzip))
}
// Add User provided middleware
this.options.serverMiddleware.forEach(m => {
this.useMiddleware(m)
})
// Common URL checks
this.useMiddleware((req, res, next) => {
// If base in req.url, remove it for the middleware and vue-router
if (this.options.router.base !== '/' && req.url.indexOf(this.options.router.base) === 0) {
// Compatibility with base url for dev server
req.url = req.url.replace(this.options.router.base, '/')
}
// Call webpack middleware only in development
if (this.webpackDevMiddleware) {
await this.webpackDevMiddleware(req, res)
}
if (this.webpackHotMiddleware) {
await this.webpackHotMiddleware(req, res)
}
// Serve static/ files
await this.serveStatic(req, res)
// Serve .nuxt/dist/ files (only for production)
if (!this.options.dev && !ssrResourceRegex.test(req.url)) {
const url = req.url
if (req.url.indexOf(this.options.build.publicPath) === 0) {
req.url = req.url.replace(this.options.build.publicPath, '/')
}
await this.serveStaticNuxt(req, res)
/* istanbul ignore next */
req.url = url
}
if (this.options.dev && req.url.indexOf(this.options.build.publicPath) === 0 && req.url.includes('.hot-update.json')) {
// Prevent access to SSR resources
if (ssrResourceRegex.test(req.url)) {
res.statusCode = 404
return res.end()
}
next()
})
// Remove publicPath from requests when it is pointing to CDN
if (isUrl(this.options.build.publicPath)) {
this.useMiddleware((req, res, next) => {
if (req.url.indexOf(this.options.build.publicPath) === 0) {
req.url = req.url.replace(this.options.build.publicPath, '/')
}
next()
})
}
// Add webpack middleware only for development
if (this.options.dev) {
this.useMiddleware(async (req, res, next) => {
if (req.url.includes('.hot-update.json')) {
res.statusCode = 404
return res.end()
}
if (this.webpackDevMiddleware) {
await this.webpackDevMiddleware(req, res)
}
if (this.webpackHotMiddleware) {
await this.webpackHotMiddleware(req, res)
}
next()
})
}
// For serving static/ files to /
this.useMiddleware(serveStatic(resolve(this.options.srcDir, 'static'), this.options.render.static))
// Serve .nuxt/dist/ files only for production
// For dev they will be served with devMiddleware
if (!this.options.dev) {
this.useMiddleware(serveStatic(resolve(this.options.buildDir, 'dist'), {
maxAge: (this.options.dev ? 0 : '1y') // 1 year in production
}))
}
// Finally use nuxtMiddleware
this.useMiddleware(this.nuxtMiddleware.bind(this))
}
async nuxtMiddleware (req, res) {
// Get context
const context = getContext(req, res)
res.statusCode = 200
try {
const { html, error, redirected, resourceHints } = await this.renderRoute(req.url, context)
if (redirected) {
return html
}
if (error) {
res.statusCode = context.nuxt.error.statusCode || 500
}
// ETag header
// Add ETag header
if (!error && this.options.render.etag) {
const etag = generateETag(html, this.options.render.etag)
if (fresh(req.headers, { etag })) {
@ -263,7 +301,6 @@ export default class Renderer extends Tapable {
}
// Render error template
const html = this.resources.errorTemplate({
/* istanbul ignore if */
error: err,
stack: ansiHTML(encodeHtml(err.stack))
})

View File

@ -1,6 +1,4 @@
import http from 'http'
import connect from 'connect'
import path from 'path'
import chalk from 'chalk'
class Server {
@ -20,6 +18,9 @@ class Server {
return this._ready
})
}
// Stop server on nuxt.close()
this.nuxt.plugin('close', () => this.close())
}
async ready () {
@ -28,59 +29,34 @@ class Server {
return this._ready
}
this.app = connect()
this.server = http.createServer(this.app)
this.render = this.nuxt.renderer.app
this.server = http.createServer(this.render)
// Add Middleware
this.options.serverMiddleware.forEach(m => {
this.useMiddleware(m)
})
// Add default render middleware
this.useMiddleware(this.render.bind(this))
return this
}
useMiddleware (m) {
// Require if needed
if (typeof m === 'string') {
let src = m
// Using ~ or ./ shorthand to resolve from project srcDir
if (src.indexOf('~') === 0 || src.indexOf('./') === 0) {
src = path.join(this.nuxt.options.srcDir, src.substr(1))
}
m = require(src)
}
if (m instanceof Function) {
this.app.use(m)
} else if (m && m.path && m.handler) {
this.app.use(m.path, m.handler)
}
}
render (req, res, next) {
this.nuxt.render(req, res)
return this
}
listen (port, host) {
host = host || 'localhost'
port = port || 3000
this.nuxt.ready()
return this.ready()
.then(() => {
this.server.listen(port, host, () => {
let _host = host === '0.0.0.0' ? 'localhost' : host
// eslint-disable-next-line no-console
console.log('\n' + chalk.bold(chalk.bgBlue.black(' OPEN ') + chalk.blue(` http://${_host}:${port}\n`)))
})
})
.catch(this.nuxt.errorHandler)
return this
}).catch(this.nuxt.errorHandler)
}
close (cb) {
return this.server.close(cb)
close () {
return new Promise((resolve, reject) => {
this.server.close(err => {
if (err) {
return reject(err)
}
resolve()
})
})
}
}