Commit 1c85abcd authored by Evan You's avatar Evan You

wip

parent 3c4a1075
......@@ -8,18 +8,6 @@ module.exports = {
devtool: isProd
? false
: '#cheap-module-eval-source-map',
entry: {
app: './src/entry-client.js',
vendor: [
'es6-promise/auto',
'firebase/app',
'firebase/database',
'vue',
'vue-router',
'vuex',
'vuex-router-sync'
]
},
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/dist/',
......
......@@ -4,8 +4,10 @@ const base = require('./webpack.base.config')
const vueConfig = require('./vue-loader.config')
const HTMLPlugin = require('html-webpack-plugin')
const SWPrecachePlugin = require('sw-precache-webpack-plugin')
const VueSSRPlugin = require('vue-ssr-webpack-plugin').client
const config = merge(base, {
entry: './src/entry-client.js',
resolve: {
alias: {
'create-api': './create-api-client.js'
......@@ -19,23 +21,33 @@ const config = merge(base, {
}),
// extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
name: 'vendor',
minChunks: function (module) {
// this assumes your vendor imports exist in the node_modules directory
return module.context && module.context.indexOf('node_modules') !== -1;
}
}),
// extract webpack runtime & manifest to avoid vendor chunk hash changing
// on every build.
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
// generate output HTML
new HTMLPlugin({
template: 'src/index.template.html'
})
}),
new VueSSRPlugin()
]
})
if (process.env.NODE_ENV === 'production') {
config.plugins.push(
// minify JS
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
// new webpack.optimize.UglifyJsPlugin({
// compress: {
// warnings: false
// }
// }),
// auto generate service worker
new SWPrecachePlugin({
cacheId: 'vue-hn',
......
const webpack = require('webpack')
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const VueSSRPlugin = require('vue-ssr-webpack-plugin')
const VueSSRPlugin = require('vue-ssr-webpack-plugin').server
module.exports = merge(base, {
target: 'node',
......
module.exports = function createMapper (serverStats, clientStats) {
const fileMap = createFileMap(serverStats, clientStats)
return function mapFiles (files) {
const res = new Set()
for (let i = 0; i < files.length; i++) {
const mapped = fileMap.get(files[i])
for (let j = 0; j < mapped.length; j++) {
res.add(mapped[j])
}
}
return Array.from(res)
}
}
function createFileMap (serverStats, clientStats) {
const fileMap = new Map()
serverStats.assets
.filter(asset => /\.js$/.test(asset.name))
.forEach(asset => fileMap.set(asset.name, mapFile(asset.name, serverStats, clientStats)))
return fileMap
}
function mapFile (file, serverStats, clientStats) {
// 1. server files -> server chunk ids
const serverChunkIds = new Set()
serverStats.assets.forEach(asset => {
if (asset.name === file) {
asset.chunks.forEach(id => {
const chunk = serverStats.chunks.find(c => c.id === id)
if (!chunk.initial) {
serverChunkIds.add(id)
}
})
}
})
// 2. server chunks -> module identifiers
const moduleIdentifiers = []
serverStats.modules.forEach(module => {
if (module.chunks.some(id => serverChunkIds.has(id))) {
moduleIdentifiers.push(module.identifier)
}
})
// 3. module identifiers -> client chunk ids
const clientChunkIds = new Set()
moduleIdentifiers.forEach(identifier => {
const clientModule = clientStats.modules.find(m => m.identifier === identifier)
if (clientModule && clientModule.chunks.length === 1) { // ignore modules duplicated in multiple chunks
clientChunkIds.add(clientModule.chunks[0])
}
})
// 4. client chunks -> client files
const clientFiles = new Set()
Array.from(clientChunkIds).forEach(id => {
const chunk = clientStats.chunks.find(chunk => chunk.id === id)
if (!chunk.initial) {
chunk.files.forEach(file => clientFiles.add(file))
}
})
return Array.from(clientFiles)
}
......@@ -3,8 +3,28 @@ const path = require('path')
const express = require('express')
const favicon = require('serve-favicon')
const compression = require('compression')
const createFileMapper = require('./mapFiles')
const resolve = file => path.resolve(__dirname, file)
const serverStats = require('./dist/server-stats.json')
const clientStats = require('./dist/client-stats.json')
const clientInitialFiles = []
const clientAsyncFiles = []
clientStats.chunks.forEach(chunk => {
chunk.files.forEach(file => {
if (chunk.initial) {
clientInitialFiles.push(file)
} else {
clientAsyncFiles.push(file)
}
})
})
const clientInitialFileLinks = clientInitialFiles.map(file => {
return `<link rel="preload" href="/dist/${file}" as="${ /\.css$/.test(file) ? 'style' : 'script' }">`
}).join('')
const isProd = process.env.NODE_ENV === 'production'
const serverInfo =
`express/${require('express/package.json').version} ` +
......@@ -12,7 +32,7 @@ const serverInfo =
const app = express()
let renderer
let renderer, mapFiles
if (isProd) {
// In production: create server renderer using server bundle and index HTML
// template from real fs.
......@@ -21,8 +41,10 @@ if (isProd) {
// src/index.template.html is processed by html-webpack-plugin to inject
// build assets and output as dist/index.html.
const template = fs.readFileSync(resolve('./dist/index.html'), 'utf-8')
mapFiles = createFileMapper(serverStats, clientStats)
renderer = createRenderer(bundle, template)
} else {
mapFiles = () => []
// In development: setup the dev server with watch and hot-reload,
// and create a new renderer on bundle / index template update.
require('./build/setup-dev-server')(app, (bundle, template) => {
......@@ -73,8 +95,30 @@ app.get('*', (req, res) => {
}
}
renderer.renderToStream({ url: req.url })
const context = { url: req.url }
renderer.renderToStream(context)
.on('error', errorHandler)
.on('beforeStart', () => {
// load the needed async chunk
const usedFiles = Object.keys(context._evaluatedFiles)
const neededFiles = mapFiles(usedFiles)
context.asyncChunks = neededFiles.map(file => {
return `<script src="/dist/${file}"></script>`
}).join('')
// preload initial chunks
context.head = (context.head || '') + clientInitialFileLinks
// prefetch async chunks
const laterFiles = clientAsyncFiles.map(file => {
if (neededFiles.indexOf(file) < 0) {
return `<link rel="prefetch" href="/dist/${file}" as="script">`
} else {
return ''
}
}).join('')
context.head += laterFiles
})
.on('end', () => console.log(`whole request: ${Date.now() - s}ms`))
.pipe(res)
})
......
......@@ -16,5 +16,5 @@ router.onReady(() => {
// service worker
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
// navigator.serviceWorker.register('/service-worker.js')
}
......@@ -26,7 +26,7 @@ export default context => {
// which is resolved when the action is complete and store state has been
// updated.
Promise.all(matchedComponents.map(component => {
return component.preFetch && component.preFetch(store)
// return component.preFetch && component.preFetch(store)
})).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
// After all preFetch hooks are resolved, our store is now
......
......@@ -8,10 +8,6 @@
<link rel="shortcut icon" sizes="48x48" href="/public/logo-48.png">
<meta name="theme-color" content="#f60">
<link rel="manifest" href="/manifest.json">
<% for (var chunk of webpack.chunks) {
for (var file of chunk.files) {
if (file.match(/\.(js|css)$/)) { %>
<link rel="<%= chunk.initial?'preload':'prefetch' %>" href="<%= htmlWebpackPlugin.files.publicPath + file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
</head>
<body>
<!--vue-ssr-outlet-->
......
......@@ -867,13 +867,7 @@ de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
debug@*, debug@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
dependencies:
ms "0.7.1"
debug@2.6.1:
debug@*, debug@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351"
dependencies:
......@@ -885,6 +879,12 @@ debug@2.6.3, debug@^2.2.0:
dependencies:
ms "0.7.2"
debug@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
dependencies:
ms "0.7.1"
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
......@@ -1289,6 +1289,16 @@ friendly-errors-webpack-plugin@^1.6.1:
error-stack-parser "^2.0.0"
string-length "^1.0.1"
fs-extra@^0.30.0:
version "0.30.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^2.1.0"
klaw "^1.0.0"
path-is-absolute "^1.0.0"
rimraf "^2.2.8"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
......@@ -1414,7 +1424,7 @@ got@^5.0.0:
unzip-response "^1.0.2"
url-parse-lax "^1.0.0"
graceful-fs@^4.1.11, graceful-fs@^4.1.2:
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
......@@ -1845,6 +1855,12 @@ json5@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
jsonfile@^2.1.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
optionalDependencies:
graceful-fs "^4.1.6"
jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
......@@ -1891,6 +1907,12 @@ kind-of@^3.0.2:
dependencies:
is-buffer "^1.0.2"
klaw@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
optionalDependencies:
graceful-fs "^4.1.9"
latest-version@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b"
......@@ -1983,7 +2005,7 @@ lodash.uniq@^4.3.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@^4.14.0, lodash@^4.17.3, lodash@^4.17.4:
"lodash@>=3.5 <5", lodash@^4.14.0, lodash@^4.17.3, lodash@^4.17.4:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
......@@ -3669,6 +3691,13 @@ webpack-hot-middleware@^2.17.1:
querystring "^0.2.0"
strip-ansi "^3.0.0"
webpack-manifest-plugin@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-1.1.0.tgz#6b6c718aade8a2537995784b46bd2e9836057caa"
dependencies:
fs-extra "^0.30.0"
lodash ">=3.5 <5"
webpack-merge@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.0.tgz#6ad72223b3e0b837e531e4597c199f909361511e"
......
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