Commit 1003dd38 authored by Evan You's avatar Evan You

use html-webpack-plugin, better caching, handle 404

parent 95de8852
......@@ -3,11 +3,22 @@ const vueConfig = require('./vue-loader.config')
module.exports = {
devtool: '#source-map',
entry: './src/client-entry.js',
entry: {
app: './src/client-entry.js',
vendor: [
'es6-promise',
'firebase/app',
'firebase/database',
'vue',
'vue-router',
'vuex',
'vuex-router-sync'
]
},
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
filename: 'client-bundle.js'
filename: '[name].[chunkhash].js'
},
module: {
noParse: /es6-promise\.js$/, // avoid webpack shimming process
......
const webpack = require('webpack')
const base = require('./webpack.base.config')
const vueConfig = require('./vue-loader.config')
const HTMLPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const SWPrecachePlugin = require('sw-precache-webpack-plugin')
const config = Object.assign({}, base, {
resolve: {
......@@ -12,16 +15,22 @@ const config = Object.assign({}, base, {
// strip comments in Vue code
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
// extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: '[name].[chunkhash].js'
}),
// generate output HTML
new HTMLPlugin({
template: 'src/index.template.html'
})
])
})
if (process.env.NODE_ENV === 'production') {
// Use ExtractTextPlugin to extract CSS into a single file
// so it's applied on initial render
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const SWPrecachePlugin = require('sw-precache-webpack-plugin')
// so it's applied on initial render.
// vueConfig is already included in the config via LoaderOptionsPlugin
// here we overwrite the loader config for <style lang="stylus">
// so they are extracted.
......@@ -33,7 +42,7 @@ if (process.env.NODE_ENV === 'production') {
}
config.plugins.push(
new ExtractTextPlugin('styles.css'),
new ExtractTextPlugin('styles.[hash].css'),
// this is needed in webpack 2 for minifying CSS
new webpack.LoaderOptionsPlugin({
minimize: true
......
......@@ -19,10 +19,10 @@
"es6-promise": "^3.2.1",
"express": "^4.14.0",
"firebase": "^3.4.1",
"html-webpack-plugin": "^2.24.1",
"lru-cache": "^4.0.1",
"serialize-javascript": "^1.3.0",
"serve-favicon": "^2.3.0",
"sw-precache-webpack-plugin": "^0.5.1",
"vue": "^2.0.0",
"vue-router": "^2.0.0",
"vue-server-renderer": "^2.0.0",
......@@ -31,14 +31,15 @@
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"cross-env": "^2.0.0",
"css-loader": "^0.25.0",
"buble": "^0.14.2",
"buble-loader": "^0.3.2",
"cross-env": "^2.0.0",
"css-loader": "^0.25.0",
"extract-text-webpack-plugin": "^2.0.0-beta.3",
"file-loader": "^0.9.0",
"stylus": "^0.54.5",
"stylus-loader": "^2.1.2",
"sw-precache-webpack-plugin": "^0.5.1",
"url-loader": "^0.5.7",
"vue-loader": "^9.7.0",
"webpack": "^2.1.0-beta.25",
......
......@@ -3,11 +3,11 @@ const isProd = process.env.NODE_ENV === 'production'
const fs = require('fs')
const path = require('path')
const resolve = file => path.resolve(__dirname, file)
const express = require('express')
const favicon = require('serve-favicon')
const serialize = require('serialize-javascript')
const compression = require('compression')
const serialize = require('serialize-javascript')
const resolve = file => path.resolve(__dirname, file)
// https://github.com/vuejs/vue/blob/next/packages/vue-server-renderer/README.md#why-use-bundlerenderer
const createBundleRenderer = require('vue-server-renderer').createBundleRenderer
......@@ -16,13 +16,12 @@ const app = express()
// parse index.html template
const html = (() => {
const template = fs.readFileSync(resolve('./index.html'), 'utf-8')
const i = template.indexOf('{{ APP }}')
// styles are injected dynamically via vue-style-loader in development
const style = isProd ? '<link rel="stylesheet" href="/dist/styles.css">' : ''
const contentMarker = '<!-- APP -->'
const template = fs.readFileSync(resolve('./dist/index.html'), 'utf-8')
const i = template.indexOf(contentMarker)
return {
head: template.slice(0, i).replace('{{ STYLE }}', style),
tail: template.slice(i + '{{ APP }}'.length)
head: template.slice(0, i),
tail: template.slice(i + contentMarker.length)
}
})()
......@@ -47,8 +46,8 @@ function createRenderer (bundle) {
})
}
app.use(compression({threshold: 0}))
app.use('/dist', express.static(resolve('./dist')))
app.use(compression({ threshold: 0 }))
app.use('/dist', express.static(resolve('./dist'), { maxAge: 60 * 60 * 24 * 30 }))
app.use(favicon(resolve('./src/assets/logo.png')))
app.get('*', (req, res) => {
......@@ -61,7 +60,9 @@ app.get('*', (req, res) => {
const context = { url: req.url }
const renderStream = renderer.renderToStream(context)
res.write(html.head)
renderStream.once('data', () => {
res.write(html.head)
})
renderStream.on('data', chunk => {
res.write(chunk)
......@@ -81,6 +82,10 @@ app.get('*', (req, res) => {
})
renderStream.on('error', err => {
if (err && err.code === '404') {
res.status(404).end('404 | Page Not Found')
return
}
// Render Error Page or Redirect
res.status(500).end('Internal Error 500')
console.error(`error during render : ${req.url}`)
......
......@@ -6,10 +6,8 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,700' rel='stylesheet' type='text/css'>
{{ STYLE }}
</head>
<body>
{{ APP }}
<script src="/dist/client-bundle.js"></script>
<!-- APP -->
</body>
</html>
......@@ -18,6 +18,6 @@ export default new Router({
{ path: '/job/:page(\\d+)?', component: createListView('job') },
{ path: '/item/:id(\\d+)', component: ItemView },
{ path: '/user/:id', component: UserView },
{ path: '*', redirect: '/top' }
{ path: '/', redirect: '/top' }
]
})
......@@ -8,16 +8,22 @@ const isDev = process.env.NODE_ENV !== 'production'
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
const s = isDev && Date.now()
// set router's location
router.push(context.url)
const matchedComponents = router.getMatchedComponents()
const s = isDev && Date.now()
// no matched routes
if (!matchedComponents.length) {
return Promise.reject({ code: '404' })
}
// Call preFetch hooks on components matched by the route.
// A preFetch hook dispatches a store action and returns a Promise,
// which is resolved when the action is complete and store state has been
// updated.
return Promise.all(router.getMatchedComponents().map(component => {
return Promise.all(matchedComponents.map(component => {
if (component.preFetch) {
return component.preFetch(store)
}
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment