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,
publicPath: '/_nuxt/',
filenames: {
css: 'style.css',
vendor: 'vendor.bundle.js',
app: 'nuxt.bundle.js'
css: 'style.[hash].css',
vendor: 'vendor.bundle.[hash].js',
app: 'nuxt.bundle.[chunkhash].js'
},
vendor: [],
loaders: [],
@ -377,6 +377,16 @@ function createWebpackMiddleware () {
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
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 () {

View File

@ -2,10 +2,9 @@
import _ from 'lodash'
import co from 'co'
import compression from 'compression'
import fs from 'fs-extra'
import pify from 'pify'
import ansiHTML from 'ansi-html'
import serialize from 'serialize-javascript'
import Server from './server'
import * as build from './build'
import * as render from './render'
@ -13,7 +12,6 @@ import generate from './generate'
import serveStatic from 'serve-static'
import { resolve, join } from 'path'
import * as utils from './utils'
utils.setAnsiColors(ansiHTML)
class Nuxt {
@ -63,27 +61,34 @@ class Nuxt {
if (fs.existsSync(join(this.srcDir, 'middleware'))) {
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)
this.renderer = null
// For serving static/ files to /
this.serveStatic = pify(serveStatic(resolve(this.srcDir, 'static')))
// 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
this.Server = Server
// Add this.build
build.options.call(this) // Add build options
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
this.render = render.render.bind(this)
this.renderRoute = render.renderRoute.bind(this)

View File

@ -1,10 +1,13 @@
'use strict'
const debug = require('debug')('nuxt:render')
import ansiHTML from 'ansi-html'
import co from 'co'
import { urlJoin, getContext } from './utils'
import serialize from 'serialize-javascript'
import { getContext, setAnsiColors, encodeHtml } from './utils'
// force blue color
debug.color = 4
setAnsiColors(ansiHTML)
export function render (req, res) {
if (!this.renderer && !this.dev) {
@ -12,7 +15,7 @@ export function render (req, res) {
process.exit(1)
}
/* istanbul ignore if */
if (!this.renderer) {
if (!this.renderer || !this.appTemplate) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.render(req, res))
@ -27,6 +30,8 @@ export function render (req, res) {
// Call webpack middleware only in development
yield self.webpackDevMiddleware(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 (self.options.router.base !== '/' && req.url.indexOf(self.options.router.base) === 0) {
@ -62,8 +67,14 @@ export function render (req, res) {
return html
})
.catch((err) => {
const html = this.errorTemplate({
error: err,
stack: ansiHTML(encodeHtml(err.stack))
})
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
})
}
@ -76,21 +87,21 @@ export function renderRoute (url, context = {}) {
// Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well)
const self = this
return co(function * () {
let app = yield self.renderToString(context)
let APP = yield self.renderToString(context)
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({
dev: self.dev, // Use to add the extracted CSS <link> in production
baseUrl: self.options.router.base,
APP: app,
context: context,
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)
}
HTML_ATTRS: 'n-head-ssr ' + m.htmlAttrs.text(),
BODY_ATTRS: m.bodyAttrs.text(),
HEAD,
APP
})
return {
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>
<head>
<meta charset="utf-8">
<title>Vue.js error</title>
<title>Nuxt.js error</title>
</head>
<body style="background-color: #a6004c;color: #efe;font-family: monospace;">
<h2>Vue.js error</h2>
<pre><%= ansiHTML(encodeHtml(err.stack)) %></pre>
<h2>Nuxt.js error</h2>
<pre>{{ stack }}</pre>
</body>
</html>

View File

@ -2,6 +2,9 @@
import { each } from 'lodash'
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 ProgressBarPlugin from 'progress-bar-webpack-plugin'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
@ -40,7 +43,7 @@ export default function () {
})
// Webpack plugins
config.plugins = (config.plugins || []).concat([
// strip comments in Vue code
// Strip comments in Vue code
new webpack.DefinePlugin(Object.assign(env, {
'process.env.NODE_ENV': JSON.stringify(this.dev ? 'development' : 'production'),
'process.BROWSER_BUILD': true,
@ -50,6 +53,18 @@ export default function () {
new webpack.optimize.CommonsChunkPlugin({
name: '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",
"chokidar": "^1.6.1",
"co": "^4.6.0",
"compression": "^1.6.2",
"css-loader": "^0.26.1",
"debug": "^2.6.1",
"extract-text-webpack-plugin": "2.0.0-beta.4",
@ -63,13 +64,16 @@
"glob": "^7.1.1",
"hash-sum": "^1.0.2",
"html-minifier": "^3.3.1",
"html-webpack-plugin": "^2.28.0",
"lodash": "^4.17.4",
"lru-cache": "^4.0.2",
"memory-fs": "^0.4.1",
"path-to-regexp": "^1.7.0",
"pify": "^2.3.0",
"post-compile-webpack-plugin": "^0.1.1",
"preload-webpack-plugin": "^1.2.0",
"progress-bar-webpack-plugin": "^1.9.3",
"script-ext-html-webpack-plugin": "^1.7.1",
"serialize-javascript": "^1.3.0",
"serve-static": "^1.11.2",
"url-loader": "^0.5.7",