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) this.renderer = new Renderer(this)
// Backward compatibility // 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.renderRoute = this.renderer.renderRoute.bind(this.renderer)
this.renderAndGetWindow = this.renderer.renderAndGetWindow.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 { join, resolve } from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { createBundleRenderer } from 'vue-server-renderer' import { createBundleRenderer } from 'vue-server-renderer'
import { encodeHtml, getContext, setAnsiColors } from 'utils' import { encodeHtml, getContext, setAnsiColors, isUrl } from 'utils'
import Debug from 'debug' import Debug from 'debug'
import connect from 'connect'
const debug = Debug('nuxt:render') const debug = Debug('nuxt:render')
debug.color = 4 // Force blue color debug.color = 4 // Force blue color
@ -58,6 +59,9 @@ export default class Renderer extends Tapable {
this.webpackDevMiddleware = null this.webpackDevMiddleware = null
this.webpackHotMiddleware = null this.webpackHotMiddleware = null
// Create new connect instance
this.app = connect()
// Renderer runtime resources // Renderer runtime resources
this.resources = { this.resources = {
clientManifest: null, clientManifest: null,
@ -86,25 +90,16 @@ export default class Renderer extends Tapable {
return this._ready return this._ready
} }
// For serving static/ files to / // Setup all middleWare
this.serveStatic = pify(serveStatic(resolve(this.options.srcDir, 'static'), this.options.render.static)) this.setupMiddleware()
// 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))
}
// Load error template
const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html') const errorTemplatePath = resolve(this.options.buildDir, 'views/error.html')
if (fs.existsSync(errorTemplatePath)) { if (fs.existsSync(errorTemplatePath)) {
this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8')) this.resources.errorTemplate = parseTemplate(fs.readFileSync(errorTemplatePath, 'utf8'))
} }
// Load resources from fs // Load SSR resources from fs
if (!this.options.dev) { if (!this.options.dev) {
await this.loadResources() await this.loadResources()
} }
@ -166,62 +161,105 @@ export default class Renderer extends Tapable {
this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString) this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString)
} }
async render (req, res) { useMiddleware (m) {
// Get context // Resolve
const context = getContext(req, res) if (typeof m === 'string') {
res.statusCode = 200 let src = m
// Using ~ or ./ shorthand to resolve from project srcDir
try { if (src.indexOf('~') === 0 || src.indexOf('./') === 0) {
// Gzip middleware for production src = join(this.nuxt.options.srcDir, src.substr(1))
if (this.gzipMiddleware) {
await this.gzipMiddleware(req, res)
} }
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 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) { 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, '/') req.url = req.url.replace(this.options.router.base, '/')
} }
// Prevent access to SSR resources
// Call webpack middleware only in development if (ssrResourceRegex.test(req.url)) {
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')) {
res.statusCode = 404 res.statusCode = 404
return res.end() 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) const { html, error, redirected, resourceHints } = await this.renderRoute(req.url, context)
if (redirected) { if (redirected) {
return html return html
} }
if (error) { if (error) {
res.statusCode = context.nuxt.error.statusCode || 500 res.statusCode = context.nuxt.error.statusCode || 500
} }
// ETag header // Add ETag header
if (!error && this.options.render.etag) { if (!error && this.options.render.etag) {
const etag = generateETag(html, this.options.render.etag) const etag = generateETag(html, this.options.render.etag)
if (fresh(req.headers, { etag })) { if (fresh(req.headers, { etag })) {
@ -263,7 +301,6 @@ export default class Renderer extends Tapable {
} }
// Render error template // Render error template
const html = this.resources.errorTemplate({ const html = this.resources.errorTemplate({
/* istanbul ignore if */
error: err, error: err,
stack: ansiHTML(encodeHtml(err.stack)) stack: ansiHTML(encodeHtml(err.stack))
}) })

View File

@ -1,6 +1,4 @@
import http from 'http' import http from 'http'
import connect from 'connect'
import path from 'path'
import chalk from 'chalk' import chalk from 'chalk'
class Server { class Server {
@ -20,6 +18,9 @@ class Server {
return this._ready return this._ready
}) })
} }
// Stop server on nuxt.close()
this.nuxt.plugin('close', () => this.close())
} }
async ready () { async ready () {
@ -28,59 +29,34 @@ class Server {
return this._ready return this._ready
} }
this.app = connect() this.render = this.nuxt.renderer.app
this.server = http.createServer(this.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 return this
} }
listen (port, host) { listen (port, host) {
host = host || 'localhost' host = host || 'localhost'
port = port || 3000 port = port || 3000
this.nuxt.ready() return this.ready()
.then(() => { .then(() => {
this.server.listen(port, host, () => { this.server.listen(port, host, () => {
let _host = host === '0.0.0.0' ? 'localhost' : host let _host = host === '0.0.0.0' ? 'localhost' : host
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('\n' + chalk.bold(chalk.bgBlue.black(' OPEN ') + chalk.blue(` http://${_host}:${port}\n`))) console.log('\n' + chalk.bold(chalk.bgBlue.black(' OPEN ') + chalk.blue(` http://${_host}:${port}\n`)))
}) })
}) }).catch(this.nuxt.errorHandler)
.catch(this.nuxt.errorHandler)
return this
} }
close (cb) { close () {
return this.server.close(cb) return new Promise((resolve, reject) => {
this.server.close(err => {
if (err) {
return reject(err)
}
resolve()
})
})
} }
} }