Use HTML template + optimise cache control + gzip

This commit is contained in:
Sébastien Chopin 2017-02-21 17:10:19 +00:00
parent f5300e4e67
commit 10c4b1550f
8 changed files with 90 additions and 55 deletions

View File

@ -51,9 +51,9 @@ const defaults = {
analyze: false, analyze: false,
publicPath: '/_nuxt/', publicPath: '/_nuxt/',
filenames: { filenames: {
css: 'style.css', css: 'style.[hash].css',
vendor: 'vendor.bundle.js', vendor: 'vendor.bundle.[hash].js',
app: 'nuxt.bundle.js' app: 'nuxt.bundle.[chunkhash].js'
}, },
vendor: [], vendor: [],
loaders: [], loaders: [],
@ -377,6 +377,16 @@ function createWebpackMiddleware () {
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, { this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
log: () => {} log: () => {}
})) }))
clientCompiler.plugin('done', () => {
const fs = this.webpackDevMiddleware.fileSystem
const filePath = join(clientConfig.output.path, 'index.html')
if (fs.existsSync(filePath)) {
const template = fs.readFileSync(filePath, 'utf-8')
this.appTemplate = _.template(template, {
interpolate: /{{([\s\S]+?)}}/g
})
}
})
} }
function webpackWatchAndUpdate () { function webpackWatchAndUpdate () {

View File

@ -2,10 +2,9 @@
import _ from 'lodash' import _ from 'lodash'
import co from 'co' import co from 'co'
import compression from 'compression'
import fs from 'fs-extra' import fs from 'fs-extra'
import pify from 'pify' import pify from 'pify'
import ansiHTML from 'ansi-html'
import serialize from 'serialize-javascript'
import Server from './server' import Server from './server'
import * as build from './build' import * as build from './build'
import * as render from './render' import * as render from './render'
@ -13,7 +12,6 @@ import generate from './generate'
import serveStatic from 'serve-static' import serveStatic from 'serve-static'
import { resolve, join } from 'path' import { resolve, join } from 'path'
import * as utils from './utils' import * as utils from './utils'
utils.setAnsiColors(ansiHTML)
class Nuxt { class Nuxt {
@ -63,27 +61,34 @@ class Nuxt {
if (fs.existsSync(join(this.srcDir, 'middleware'))) { if (fs.existsSync(join(this.srcDir, 'middleware'))) {
this.options.middleware = true this.options.middleware = true
} }
// Template
this.appTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'app.html'), 'utf8'), {
imports: { serialize }
})
this.errorTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
imports: {
ansiHTML,
encodeHtml: utils.encodeHtml
}
})
// renderer used by Vue.js (via createBundleRenderer) // renderer used by Vue.js (via createBundleRenderer)
this.renderer = null this.renderer = null
// For serving static/ files to / // For serving static/ files to /
this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static'))) this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static')))
// For serving .nuxt/dist/ files (only when build.publicPath is not an URL) // For serving .nuxt/dist/ files (only when build.publicPath is not an URL)
this.serveStaticNuxt = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist'))) this.serveStaticNuxt = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist'), {
maxAge: (this.dev ? 0 : '1y') // 1 year in production
}))
// gzip for production
if (!this.dev) {
this.gzipMiddleware = pify(compression({
threshold: 0
}))
}
// Add this.Server Class // Add this.Server Class
this.Server = Server this.Server = Server
// Add this.build // Add this.build
build.options.call(this) // Add build options build.options.call(this) // Add build options
this.build = () => co(build.build.bind(this)) this.build = () => co(build.build.bind(this))
// Template
if (!this.dev && this.renderer) {
this.appTemplate = _.template(fs.readFileSync(resolve(this.dir, '.nuxt', 'dist', 'index.html'), 'utf8'), {
interpolate: /{{([\s\S]+?)}}/g
})
}
this.errorTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
interpolate: /{{([\s\S]+?)}}/g
})
// Add this.render and this.renderRoute // Add this.render and this.renderRoute
this.render = render.render.bind(this) this.render = render.render.bind(this)
this.renderRoute = render.renderRoute.bind(this) this.renderRoute = render.renderRoute.bind(this)

View File

@ -1,10 +1,13 @@
'use strict' 'use strict'
const debug = require('debug')('nuxt:render') const debug = require('debug')('nuxt:render')
import ansiHTML from 'ansi-html'
import co from 'co' import co from 'co'
import { urlJoin, getContext } from './utils' import serialize from 'serialize-javascript'
import { getContext, setAnsiColors, encodeHtml } from './utils'
// force blue color // force blue color
debug.color = 4 debug.color = 4
setAnsiColors(ansiHTML)
export function render (req, res) { export function render (req, res) {
if (!this.renderer && !this.dev) { if (!this.renderer && !this.dev) {
@ -12,7 +15,7 @@ export function render (req, res) {
process.exit(1) process.exit(1)
} }
/* istanbul ignore if */ /* istanbul ignore if */
if (!this.renderer) { if (!this.renderer || !this.appTemplate) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve(this.render(req, res)) resolve(this.render(req, res))
@ -27,6 +30,8 @@ export function render (req, res) {
// Call webpack middleware only in development // Call webpack middleware only in development
yield self.webpackDevMiddleware(req, res) yield self.webpackDevMiddleware(req, res)
yield self.webpackHotMiddleware(req, res) yield self.webpackHotMiddleware(req, res)
} else {
yield self.gzipMiddleware(req, res)
} }
// 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 (self.options.router.base !== '/' && req.url.indexOf(self.options.router.base) === 0) { if (self.options.router.base !== '/' && req.url.indexOf(self.options.router.base) === 0) {
@ -62,8 +67,14 @@ export function render (req, res) {
return html return html
}) })
.catch((err) => { .catch((err) => {
const html = this.errorTemplate({
error: err,
stack: ansiHTML(encodeHtml(err.stack))
})
res.statusCode = 500 res.statusCode = 500
res.end(this.errorTemplate({ err }), 'utf8') res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.setHeader('Content-Length', Buffer.byteLength(html))
res.end(html, 'utf8')
return err return err
}) })
} }
@ -76,21 +87,21 @@ export function renderRoute (url, context = {}) {
// Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well) // Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well)
const self = this const self = this
return co(function * () { return co(function * () {
let app = yield self.renderToString(context) let APP = yield self.renderToString(context)
if (!context.nuxt.serverRendered) { if (!context.nuxt.serverRendered) {
app = '<div id="__nuxt"></div>' APP = '<div id="__nuxt"></div>'
} }
const publicPath = self.options.build.publicPath.indexOf('http') === 0 ? self.options.build.publicPath : urlJoin(self.options.router.base, self.options.build.publicPath) const m = context.meta.inject()
let HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
if (self.options.router.base !== '/') {
HEAD += `<base href="${self.options.router.base}">`
}
APP += `<script type="text/javascript" defer>window.__NUXT__=${serialize(context.nuxt, { isJSON: true })}</script>`
const html = self.appTemplate({ const html = self.appTemplate({
dev: self.dev, // Use to add the extracted CSS <link> in production HTML_ATTRS: 'n-head-ssr ' + m.htmlAttrs.text(),
baseUrl: self.options.router.base, BODY_ATTRS: m.bodyAttrs.text(),
APP: app, HEAD,
context: context, APP
files: {
css: urlJoin(publicPath, self.options.build.filenames.css),
vendor: urlJoin(publicPath, self.options.build.filenames.vendor),
app: urlJoin(publicPath, self.options.build.filenames.app)
}
}) })
return { return {
html, html,

View File

@ -1,19 +0,0 @@
<% var m = context.meta.inject() %><!DOCTYPE html>
<html n-head-ssr <%= m.htmlAttrs.text() %>>
<head>
<%= m.meta.text() %>
<%= m.title.text() %>
<%= m.link.text() %>
<%= m.style.text() %>
<%= m.script.text() %>
<%= m.noscript.text() %>
<% if (baseUrl !== '/') { %><base href="<%= baseUrl %>"><% } %>
<% if (!dev) { %><link rel="stylesheet" href="<%= files.css %>"><% } %>
</head>
<body <%= m.bodyAttrs.text() %>>
<%= APP %>
<script type="text/javascript" defer>window.__NUXT__=<%= serialize(context.nuxt, { isJSON: true }) %></script>
<script src="<%= files.vendor %>" defer></script>
<script src="<%= files.app %>" defer></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>

View File

@ -2,10 +2,10 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Vue.js error</title> <title>Nuxt.js error</title>
</head> </head>
<body style="background-color: #a6004c;color: #efe;font-family: monospace;"> <body style="background-color: #a6004c;color: #efe;font-family: monospace;">
<h2>Vue.js error</h2> <h2>Nuxt.js error</h2>
<pre><%= ansiHTML(encodeHtml(err.stack)) %></pre> <pre>{{ stack }}</pre>
</body> </body>
</html> </html>

View File

@ -2,6 +2,9 @@
import { each } from 'lodash' import { each } from 'lodash'
import webpack from 'webpack' import webpack from 'webpack'
import HTMLPlugin from 'html-webpack-plugin'
import ScriptExtHtmlWebpackPlugin from 'script-ext-html-webpack-plugin'
import PreloadWebpackPlugin from 'preload-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin' import ExtractTextPlugin from 'extract-text-webpack-plugin'
import ProgressBarPlugin from 'progress-bar-webpack-plugin' import ProgressBarPlugin from 'progress-bar-webpack-plugin'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
@ -40,7 +43,7 @@ export default function () {
}) })
// Webpack plugins // Webpack plugins
config.plugins = (config.plugins || []).concat([ config.plugins = (config.plugins || []).concat([
// strip comments in Vue code // Strip comments in Vue code
new webpack.DefinePlugin(Object.assign(env, { new webpack.DefinePlugin(Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'), 'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
'process.BROWSER_BUILD': true, 'process.BROWSER_BUILD': true,
@ -50,6 +53,18 @@ export default function () {
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', name: 'vendor',
filename: this.options.build.filenames.vendor filename: this.options.build.filenames.vendor
}),
// Generate output HTML
new HTMLPlugin({
template: resolve(__dirname, 'views/app.template.html')
}),
// Add defer to scripts
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'defer'
}),
// Add prefetch code-splitted routes
new PreloadWebpackPlugin({
rel: 'prefetch'
}) })
]) ])

View File

@ -55,6 +55,7 @@
"chalk": "^1.1.3", "chalk": "^1.1.3",
"chokidar": "^1.6.1", "chokidar": "^1.6.1",
"co": "^4.6.0", "co": "^4.6.0",
"compression": "^1.6.2",
"css-loader": "^0.26.1", "css-loader": "^0.26.1",
"debug": "^2.6.1", "debug": "^2.6.1",
"extract-text-webpack-plugin": "2.0.0-beta.4", "extract-text-webpack-plugin": "2.0.0-beta.4",
@ -63,13 +64,16 @@
"glob": "^7.1.1", "glob": "^7.1.1",
"hash-sum": "^1.0.2", "hash-sum": "^1.0.2",
"html-minifier": "^3.3.1", "html-minifier": "^3.3.1",
"html-webpack-plugin": "^2.28.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"lru-cache": "^4.0.2", "lru-cache": "^4.0.2",
"memory-fs": "^0.4.1", "memory-fs": "^0.4.1",
"path-to-regexp": "^1.7.0", "path-to-regexp": "^1.7.0",
"pify": "^2.3.0", "pify": "^2.3.0",
"post-compile-webpack-plugin": "^0.1.1", "post-compile-webpack-plugin": "^0.1.1",
"preload-webpack-plugin": "^1.2.0",
"progress-bar-webpack-plugin": "^1.9.3", "progress-bar-webpack-plugin": "^1.9.3",
"script-ext-html-webpack-plugin": "^1.7.1",
"serialize-javascript": "^1.3.0", "serialize-javascript": "^1.3.0",
"serve-static": "^1.11.2", "serve-static": "^1.11.2",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",