Commit 392bc154 authored by Evan You's avatar Evan You

set maxAge and warm cache periodically

parent daae797e
...@@ -4,7 +4,7 @@ module.exports = { ...@@ -4,7 +4,7 @@ module.exports = {
devtool: '#source-map', devtool: '#source-map',
entry: { entry: {
app: './src/client-entry.js', app: './src/client-entry.js',
vendor: ['vue', 'vue-router', 'vuex', 'firebase', 'lru-cache'] vendor: ['vue', 'vue-router', 'vuex', 'firebase', 'lru-cache', 'es6-promise']
}, },
output: { output: {
path: path.resolve(__dirname, '../dist'), path: path.resolve(__dirname, '../dist'),
......
const base = require('./webpack.base.config') const base = require('./webpack.base.config')
const webpack = require('webpack') const webpack = require('webpack')
const isProd = process.env.NODE_ENV === 'production'
const config = Object.assign({}, base, { const config = Object.assign({}, base, {
plugins: [ plugins: [
// strip comments in Vue code
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}), }),
// extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', name: 'vendor',
filename: 'client-vendor-bundle.js' filename: 'client-vendor-bundle.js'
...@@ -14,7 +15,8 @@ const config = Object.assign({}, base, { ...@@ -14,7 +15,8 @@ const config = Object.assign({}, base, {
] ]
}) })
if (isProd) { if (process.env.NODE_ENV === 'production') {
// extract CSS into a single file so it's applied on initial render
const ExtractTextPlugin = require('extract-text-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin')
config.vue = { config.vue = {
...@@ -28,10 +30,11 @@ if (isProd) { ...@@ -28,10 +30,11 @@ if (isProd) {
config.plugins.push( config.plugins.push(
new ExtractTextPlugin('styles.css'), new ExtractTextPlugin('styles.css'),
// this is needed in webpack 2 for minifying CSS
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
minimize: true minimize: true
}), }),
// minify // minify JS
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
compress: { compress: {
warnings: false warnings: false
......
...@@ -10,9 +10,7 @@ const createBundleRenderer = require('vue-server-renderer').createBundleRenderer ...@@ -10,9 +10,7 @@ const createBundleRenderer = require('vue-server-renderer').createBundleRenderer
let renderer let renderer
function createRenderer (fs) { function createRenderer (fs) {
const bundlePath = path.resolve(__dirname, 'dist/server-bundle.js') const bundlePath = path.resolve(__dirname, 'dist/server-bundle.js')
return createBundleRenderer(fs.readFileSync(bundlePath, 'utf-8'), { return createBundleRenderer(fs.readFileSync(bundlePath, 'utf-8'))
cache: require('lru-cache')({ max: 1000 })
})
} }
const app = express() const app = express()
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
<script> <script>
export default { export default {
name: 'NewsItem', name: 'NewsItem',
props: ['item'], props: ['item']
serverCacheKey: ({ item }) => `${item.id}:${item.score}:${item.descendents}`
} }
</script> </script>
...@@ -7,20 +7,36 @@ const inBrowser = typeof window !== 'undefined' ...@@ -7,20 +7,36 @@ const inBrowser = typeof window !== 'undefined'
// context for each request. To allow caching across multiple requests, we need // context for each request. To allow caching across multiple requests, we need
// to attach the cache to the process which is shared across all requests. // to attach the cache to the process which is shared across all requests.
const cache = inBrowser const cache = inBrowser
? null ? createCache()
: (process.__API_CACHE__ || (process.__API_CACHE__ = LRU({ max: 1000 }))) : (process.__API_CACHE__ || (process.__API_CACHE__ = createCache()))
function createCache () {
return LRU({
max: 1000,
maxAge: 1000 * 60 * 15 // 15 min cache
})
}
// create a single api instance for all server-side requests // create a single api instance for all server-side requests
// and cache the latest top Ids on it.
const api = inBrowser const api = inBrowser
? new Firebase('https://hacker-news.firebaseio.com/v0') ? new Firebase('https://hacker-news.firebaseio.com/v0')
: (process.__API__ || (process.__API__ = createServerSideAPI())) : (process.__API__ || (process.__API__ = createServerSideAPI()))
function createServerSideAPI () { function createServerSideAPI () {
const api = new Firebase('https://hacker-news.firebaseio.com/v0') const api = new Firebase('https://hacker-news.firebaseio.com/v0')
// cache the latest top stories' ids
api.child(`topstories`).on('value', snapshot => { api.child(`topstories`).on('value', snapshot => {
api.__topIds__ = snapshot.val() api.__topIds__ = snapshot.val()
}) })
// warm the cache every 15 min, since the front page changes quite often
warmCache()
function warmCache () {
fetchItems((api.__topIds__ || []).slice(0, 30))
setTimeout(warmCache, 1000 * 60 * 15)
}
return api return api
} }
...@@ -47,11 +63,11 @@ export function watchTopIds (cb) { ...@@ -47,11 +63,11 @@ export function watchTopIds (cb) {
} }
export function fetchItem (id, forceRefresh) { export function fetchItem (id, forceRefresh) {
if (!forceRefresh && cache && cache.has(id)) { if (!forceRefresh && cache.has(id)) {
return Promise.resolve(cache.get(id)) return Promise.resolve(cache.get(id))
} else { } else {
return fetch(`item/${id}`).then(item => { return fetch(`item/${id}`).then(item => {
cache && cache.set(id, item) cache.set(id, item)
return item return item
}) })
} }
......
...@@ -23,7 +23,7 @@ const store = new Vuex.Store({ ...@@ -23,7 +23,7 @@ const store = new Vuex.Store({
}) })
}, },
FETCH_NEWS: ({ commit, state }) => { FETCH_NEWS: ({ commit, state }) => {
const ids = getDisplayedIds(state).filter(id => !state.items[id]) const ids = getDisplayedIds(state)
return fetchItems(ids).then(items => { return fetchItems(ids).then(items => {
commit('RECEIVE_ITEMS', { items }) commit('RECEIVE_ITEMS', { items })
}) })
......
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