Skip to content

Commit 8bd2c1c

Browse files
author
Guillaume Chau
committed
refactor: SSR
1 parent 459896f commit 8bd2c1c

File tree

11 files changed

+84
-46
lines changed

11 files changed

+84
-46
lines changed

generator/index.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const {
55
const chalk = require('chalk')
66

77
module.exports = (api, options, rootOptions) => {
8-
// TODO cleanup for next cli release
9-
if (!api.hasPlugin('router') && !api.generator.pkg.dependencies['vue-router']) {
8+
if (!api.hasPlugin('router')) {
109
throw new Error(`Please install router plugin with 'vue add router'.`)
1110
}
1211

@@ -30,8 +29,7 @@ module.exports = (api, options, rootOptions) => {
3029
}
3130

3231
const templateOptions = {
33-
// TODO cleanup for next cli release
34-
vuex: api.hasPlugin('vuex') || api.generator.pkg.dependencies['vuex'],
32+
vuex: api.hasPlugin('vuex'),
3533
pwa: api.hasPlugin('pwa'),
3634
apollo: api.hasPlugin('apollo'),
3735
}
@@ -78,7 +76,7 @@ module.exports = (api, options, rootOptions) => {
7876
7977
return result
8078
}`)
81-
contents = contents.replace('createProvider().provide()', 'apolloProvider.provide()')
79+
contents = contents.replace('apolloProvider: createProvider()', 'apolloProvider')
8280
fs.writeFileSync(file, contents, { encoding: 'utf8' })
8381
}
8482
}
@@ -127,7 +125,7 @@ module.exports = (api, options, rootOptions) => {
127125
}
128126

129127
contents = contents.replace(/export default app => {((.|\s)*)}/, `export default app => {$1
130-
ssrMiddleware(app)
128+
ssrMiddleware(app, { prodOnly: true })
131129
}`)
132130
contents = `import { ssrMiddleware } from '@akryum/vue-cli-plugin-ssr'\n` + contents
133131
fs.writeFileSync(file, contents, { encoding: 'utf8' })
@@ -139,7 +137,11 @@ module.exports = (api, options, rootOptions) => {
139137
// Lint generated/modified files
140138
try {
141139
const lint = require('@vue/cli-plugin-eslint/lint')
142-
lint({ silent: true }, api)
140+
const files = ['*.js', '.*.js', 'src']
141+
if (api.hasPlugin('apollo')) {
142+
files.push('apollo-server')
143+
}
144+
lint({ silent: true, _: files }, api)
143145
} catch (e) {
144146
// No ESLint vue-cli plugin
145147
}

generator/templates/default/src/index.template.html renamed to generator/templates/default/public/index.html

+3-7
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,17 @@
22
<html>
33
<head>
44
<meta charset="utf-8">
5-
<meta name="viewport" content="width=device-width">
6-
<meta name="theme-color" content="#40b883"/>
7-
<meta name="msapplication-navbutton-color" content="#40b883"/>
8-
<meta name="apple-mobile-web-app-capable" content="yes"/>
9-
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
7+
<link rel="icon" href="<%%= BASE_URL %%>favicon.ico">
108
<title>{{ title }}</title>
119
{{{ renderResourceHints() }}}
1210
{{{ renderStyles() }}}
1311
</head>
1412
<body>
1513
<!--vue-ssr-outlet-->
1614
{{{ renderState() }}}
17-
<%_ if (apollo) { _%>
1815
{{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}
19-
<%_ } _%>
2016
{{{ renderScripts() }}}
2117
</body>
2218
</html>

generator/templates/default/src/entry-client.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ createApp({
99
async beforeApp ({
1010
router
1111
}) {
12-
const components = await loadAsyncComponents({ router })
13-
console.log(components)
12+
await loadAsyncComponents({ router })
1413
},
1514

1615
afterApp ({

generator/templates/default/src/entry-server.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
<%_ if (apollo) { _%>
22
import 'isomorphic-fetch'
3+
import Vue from 'vue'
4+
import App from './App.vue'
5+
import ApolloSSR from 'vue-apollo/ssr'
36
<%_ } _%>
47
import { createApp } from './main'
58

9+
<%_ if (apollo) { _%>
10+
Vue.use(ApolloSSR)
11+
12+
<%_ } _%>
613
export default context => {
714
return new Promise(async (resolve, reject) => {
815
const {
@@ -21,11 +28,6 @@ export default context => {
2128
router.onReady(() => {
2229
const matchedComponents = router.getMatchedComponents()
2330

24-
if (!matchedComponents.length) {
25-
// eslint-disable-next-line prefer-promise-reject-errors
26-
return reject({ code: 404 })
27-
}
28-
2931
Promise.all([
3032
<%_ if (vuex) { _%>
3133
// Async data
@@ -40,9 +42,12 @@ export default context => {
4042
<%_ } _%>
4143
<%_ if (apollo) { _%>
4244
// Apollo prefetch
43-
apolloProvider.prefetchAll({
45+
ApolloSSR.prefetchAll(apolloProvider, [App, ...matchedComponents], {
46+
<%_ if (vuex) { _%>
47+
store,
48+
<%_ } _%>
4449
route: router.currentRoute,
45-
}, matchedComponents),
50+
}),
4651
<%_ } _%>
4752
]).then(() => {
4853
<%_ if (vuex) { _%>
@@ -55,12 +60,11 @@ export default context => {
5560
<%_ } _%>
5661

5762
<%_ if (apollo) { _%>
58-
// Apollo
59-
context.apolloState = apolloProvider.getStates()
63+
// Same for Apollo client cache
64+
context.apolloState = ApolloSSR.getStates(apolloProvider)
6065
<%_ } _%>
66+
resolve(app)
6167
})
62-
63-
resolve(app)
6468
}, reject)
6569
})
6670
}

lib/HtmlFilterPlugin.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const ID = 'vue-cli-plugin-ssr:html-filter'
2+
3+
module.exports = class HtmlPwaPlugin {
4+
apply (compiler) {
5+
compiler.hooks.compilation.tap(ID, compilation => {
6+
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, (data, cb) => {
7+
const autoGeneratedAssets = JSON.parse(data.plugin.assetJson)
8+
data.head = data.head.filter(tag => !this.isStylesheet(autoGeneratedAssets, tag))
9+
data.body = data.body.filter(tag => !this.isScript(autoGeneratedAssets, tag))
10+
cb(null, data)
11+
})
12+
})
13+
}
14+
15+
isStylesheet (assets, tag) {
16+
return tag.tagName === 'link' && tag.attributes.rel === 'stylesheet' && assets.includes(tag.attributes.href)
17+
}
18+
19+
isScript (assets, tag) {
20+
return tag.tagName === 'script' && assets.includes(tag.attributes.src)
21+
}
22+
}

lib/app.js

+20-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@ const compression = require('compression')
77

88
const config = require('./config')
99

10-
module.exports = app => {
10+
const DEFAULT_OPTIONS = {
11+
prodOnly: false,
12+
}
13+
14+
module.exports = (app, options) => {
15+
options = Object.assign({}, DEFAULT_OPTIONS, options)
16+
1117
const isProd = process.env.NODE_ENV === 'production'
18+
19+
if (options.prodOnly && !isProd) return
20+
1221
const templatePath = config.templatePath
1322

1423
try {
@@ -61,6 +70,7 @@ module.exports = app => {
6170
// Serve static files
6271
const serve = (filePath, cache) => express.static(filePath, {
6372
maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0,
73+
index: false,
6474
})
6575

6676
// Serve static files
@@ -69,7 +79,7 @@ module.exports = app => {
6979
if (config.api.hasPlugin('pwa')) {
7080
app.get('/service-worker.js', serve(config.serviceWorkerPath))
7181
}
72-
app.use(serve(config.distPath, true))
82+
app.use(/^((?!index\.).)*$/i, serve(config.distPath, true))
7383

7484
// Render the Vue app using the bundle renderer
7585
const renderApp = (req, res) => {
@@ -88,7 +98,14 @@ module.exports = app => {
8898

8999
// Render Error Page
90100
res.status(code)
91-
res.send('500 | Internal Server Error')
101+
let text = '500 | Internal Server Error'
102+
103+
if (!isProd) {
104+
text += '<br>'
105+
text += `<pre>${err.stack}</pre>`
106+
}
107+
108+
res.send(text)
92109
} else {
93110
res.status(context.httpCode || 200).send(html)
94111
}

lib/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = {
66
defaultTitle: 'My app',
77
favicon: './public/favicon.ico',
88
skipRequests: req => req.originalUrl === '/graphql',
9-
nodeExternalsWhitelist: [/\.css$/, /vue-cli-plugin-apollo/, /vue-apollo/],
9+
nodeExternalsWhitelist: [/\.css$/],
1010
// Paths
1111
distPath: null,
1212
templatePath: null,

lib/default-config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module.exports = (api, options) => {
33

44
return {
55
distPath: api.resolve(outputPath),
6-
templatePath: api.resolve('./src/index.template.html'),
6+
templatePath: api.resolve(`${outputPath}/index.html`),
77
serviceWorkerPath: api.resolve(`${outputPath}/service-worker.js`),
88
}
99
}

lib/dev-server.js

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const fs = require('fs')
22
const path = require('path')
33
const MFS = require('memory-fs')
4-
const chokidar = require('chokidar')
54
const webpack = require('webpack')
65
const chalk = require('chalk')
76

@@ -11,7 +10,7 @@ const { getWebpackConfig } = require('./webpack')
1110
module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Promise((resolve, reject) => {
1211
const service = config.service
1312
if (!service) {
14-
reject(new Error('No cli-service available'))
13+
reject(new Error('No cli-service available. Make sure you ran the command with `vue-cli-service`.'))
1514
return
1615
}
1716

@@ -31,7 +30,7 @@ module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Prom
3130

3231
let firstRun = true
3332
let copied = ''
34-
const url = `http://localhost:${config.port || process.env.VUE_APP_GRAPHQL_PORT}`
33+
const url = `http://localhost:${config.port}`
3534

3635
const update = () => {
3736
if (serverBundle && clientManifest) {
@@ -45,14 +44,6 @@ module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Prom
4544
}
4645
}
4746

48-
// read template from disk and watch
49-
template = fs.readFileSync(templatePath, 'utf-8')
50-
chokidar.watch(templatePath).on('change', () => {
51-
template = fs.readFileSync(templatePath, 'utf-8')
52-
console.log('index.html template updated.')
53-
update()
54-
})
55-
5647
// modify client config to work with hot middleware
5748
clientConfig.entry = ['webpack-hot-middleware/client', clientConfig.entry]
5849
clientConfig.plugins.push(
@@ -66,6 +57,7 @@ module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Prom
6657
noInfo: true,
6758
stats: 'none',
6859
logLevel: 'error',
60+
index: false,
6961
})
7062
server.use(devMiddleware)
7163

@@ -78,6 +70,10 @@ module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Prom
7870
devMiddleware.fileSystem,
7971
'vue-ssr-client-manifest.json'
8072
))
73+
74+
// HTML Template
75+
template = devMiddleware.fileSystem.readFileSync(templatePath, 'utf8')
76+
8177
update()
8278
onCompilationCompleted()
8379
})

lib/webpack.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,24 @@ const nodeExternals = require('webpack-node-externals')
66
const WebpackBar = require('webpackbar')
77

88
const config = require('./config')
9+
const HtmlFilterPlugin = require('./HtmlFilterPlugin')
910

1011
exports.getWebpackConfig = ({ target }) => {
1112
const service = config.service
1213
const isProd = service.mode === 'production'
1314

1415
let webpackConfig = service.resolveChainableWebpackConfig()
1516

16-
webpackConfig.plugins.delete('html')
17+
// Remove unneeded plugins
1718
webpackConfig.plugins.delete('hmr')
1819
webpackConfig.plugins.delete('preload')
1920
webpackConfig.plugins.delete('prefetch')
20-
webpackConfig.plugins.delete('pwa')
2121
webpackConfig.plugins.delete('progress')
2222
webpackConfig.plugins.delete('no-emit-on-errors')
2323

24+
// HTML
25+
webpackConfig.plugin('html-filter').use(HtmlFilterPlugin)
26+
2427
webpackConfig = service.resolveWebpackConfig(webpackConfig)
2528

2629
webpackConfig = mergeWebpack(webpackConfig, {

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"main": "index.js",
2828
"dependencies": {
2929
"chalk": "^2.4.1",
30-
"chokidar": "^2.0.4",
3130
"clipboardy": "^1.2.3",
3231
"compression": "^1.7.3",
3332
"cross-env": "^5.2.0",

0 commit comments

Comments
 (0)