Skip to content

Commit

Permalink
refactor: SSR
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume Chau committed Aug 30, 2018
1 parent 459896f commit 8bd2c1c
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 46 deletions.
16 changes: 9 additions & 7 deletions generator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ const {
const chalk = require('chalk')

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

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

const templateOptions = {
// TODO cleanup for next cli release
vuex: api.hasPlugin('vuex') || api.generator.pkg.dependencies['vuex'],
vuex: api.hasPlugin('vuex'),
pwa: api.hasPlugin('pwa'),
apollo: api.hasPlugin('apollo'),
}
Expand Down Expand Up @@ -78,7 +76,7 @@ module.exports = (api, options, rootOptions) => {
return result
}`)
contents = contents.replace('createProvider().provide()', 'apolloProvider.provide()')
contents = contents.replace('apolloProvider: createProvider()', 'apolloProvider')
fs.writeFileSync(file, contents, { encoding: 'utf8' })
}
}
Expand Down Expand Up @@ -127,7 +125,7 @@ module.exports = (api, options, rootOptions) => {
}

contents = contents.replace(/export default app => {((.|\s)*)}/, `export default app => {$1
ssrMiddleware(app)
ssrMiddleware(app, { prodOnly: true })
}`)
contents = `import { ssrMiddleware } from '@akryum/vue-cli-plugin-ssr'\n` + contents
fs.writeFileSync(file, contents, { encoding: 'utf8' })
Expand All @@ -139,7 +137,11 @@ module.exports = (api, options, rootOptions) => {
// Lint generated/modified files
try {
const lint = require('@vue/cli-plugin-eslint/lint')
lint({ silent: true }, api)
const files = ['*.js', '.*.js', 'src']
if (api.hasPlugin('apollo')) {
files.push('apollo-server')
}
lint({ silent: true, _: files }, api)
} catch (e) {
// No ESLint vue-cli plugin
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#40b883"/>
<meta name="msapplication-navbutton-color" content="#40b883"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%%= BASE_URL %%>favicon.ico">
<title>{{ title }}</title>
{{{ renderResourceHints() }}}
{{{ renderStyles() }}}
</head>
<body>
<!--vue-ssr-outlet-->
{{{ renderState() }}}
<%_ if (apollo) { _%>
{{{ renderState({ contextKey: 'apolloState', windowKey: '__APOLLO_STATE__' }) }}}
<%_ } _%>
{{{ renderScripts() }}}
</body>
</html>
3 changes: 1 addition & 2 deletions generator/templates/default/src/entry-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ createApp({
async beforeApp ({
router
}) {
const components = await loadAsyncComponents({ router })
console.log(components)
await loadAsyncComponents({ router })
},

afterApp ({
Expand Down
26 changes: 15 additions & 11 deletions generator/templates/default/src/entry-server.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<%_ if (apollo) { _%>
import 'isomorphic-fetch'
import Vue from 'vue'
import App from './App.vue'
import ApolloSSR from 'vue-apollo/ssr'
<%_ } _%>
import { createApp } from './main'

<%_ if (apollo) { _%>
Vue.use(ApolloSSR)

<%_ } _%>
export default context => {
return new Promise(async (resolve, reject) => {
const {
Expand All @@ -21,11 +28,6 @@ export default context => {
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()

if (!matchedComponents.length) {
// eslint-disable-next-line prefer-promise-reject-errors
return reject({ code: 404 })
}

Promise.all([
<%_ if (vuex) { _%>
// Async data
Expand All @@ -40,9 +42,12 @@ export default context => {
<%_ } _%>
<%_ if (apollo) { _%>
// Apollo prefetch
apolloProvider.prefetchAll({
ApolloSSR.prefetchAll(apolloProvider, [App, ...matchedComponents], {
<%_ if (vuex) { _%>
store,
<%_ } _%>
route: router.currentRoute,
}, matchedComponents),
}),
<%_ } _%>
]).then(() => {
<%_ if (vuex) { _%>
Expand All @@ -55,12 +60,11 @@ export default context => {
<%_ } _%>

<%_ if (apollo) { _%>
// Apollo
context.apolloState = apolloProvider.getStates()
// Same for Apollo client cache
context.apolloState = ApolloSSR.getStates(apolloProvider)
<%_ } _%>
resolve(app)
})

resolve(app)
}, reject)
})
}
22 changes: 22 additions & 0 deletions lib/HtmlFilterPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const ID = 'vue-cli-plugin-ssr:html-filter'

module.exports = class HtmlPwaPlugin {
apply (compiler) {
compiler.hooks.compilation.tap(ID, compilation => {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, (data, cb) => {
const autoGeneratedAssets = JSON.parse(data.plugin.assetJson)
data.head = data.head.filter(tag => !this.isStylesheet(autoGeneratedAssets, tag))
data.body = data.body.filter(tag => !this.isScript(autoGeneratedAssets, tag))
cb(null, data)
})
})
}

isStylesheet (assets, tag) {
return tag.tagName === 'link' && tag.attributes.rel === 'stylesheet' && assets.includes(tag.attributes.href)
}

isScript (assets, tag) {
return tag.tagName === 'script' && assets.includes(tag.attributes.src)
}
}
23 changes: 20 additions & 3 deletions lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ const compression = require('compression')

const config = require('./config')

module.exports = app => {
const DEFAULT_OPTIONS = {
prodOnly: false,
}

module.exports = (app, options) => {
options = Object.assign({}, DEFAULT_OPTIONS, options)

const isProd = process.env.NODE_ENV === 'production'

if (options.prodOnly && !isProd) return

const templatePath = config.templatePath

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

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

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

// Render Error Page
res.status(code)
res.send('500 | Internal Server Error')
let text = '500 | Internal Server Error'

This comment has been minimized.

Copy link
@houd1ni

houd1ni Aug 30, 2018

Could I make a PR with custom 500 html file, that would be taken from config and sent here?


if (!isProd) {
text += '<br>'
text += `<pre>${err.stack}</pre>`
}

res.send(text)
} else {
res.status(context.httpCode || 200).send(html)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
defaultTitle: 'My app',
favicon: './public/favicon.ico',
skipRequests: req => req.originalUrl === '/graphql',
nodeExternalsWhitelist: [/\.css$/, /vue-cli-plugin-apollo/, /vue-apollo/],
nodeExternalsWhitelist: [/\.css$/],
// Paths
distPath: null,
templatePath: null,
Expand Down
2 changes: 1 addition & 1 deletion lib/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = (api, options) => {

return {
distPath: api.resolve(outputPath),
templatePath: api.resolve('./src/index.template.html'),
templatePath: api.resolve(`${outputPath}/index.html`),
serviceWorkerPath: api.resolve(`${outputPath}/service-worker.js`),
}
}
18 changes: 7 additions & 11 deletions lib/dev-server.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const fs = require('fs')
const path = require('path')
const MFS = require('memory-fs')
const chokidar = require('chokidar')
const webpack = require('webpack')
const chalk = require('chalk')

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

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

let firstRun = true
let copied = ''
const url = `http://localhost:${config.port || process.env.VUE_APP_GRAPHQL_PORT}`
const url = `http://localhost:${config.port}`

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

// read template from disk and watch
template = fs.readFileSync(templatePath, 'utf-8')
chokidar.watch(templatePath).on('change', () => {
template = fs.readFileSync(templatePath, 'utf-8')
console.log('index.html template updated.')
update()
})

// modify client config to work with hot middleware
clientConfig.entry = ['webpack-hot-middleware/client', clientConfig.entry]
clientConfig.plugins.push(
Expand All @@ -66,6 +57,7 @@ module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Prom
noInfo: true,
stats: 'none',
logLevel: 'error',
index: false,
})
server.use(devMiddleware)

Expand All @@ -78,6 +70,10 @@ module.exports.setupDevServer = ({ server, templatePath, onUpdate }) => new Prom
devMiddleware.fileSystem,
'vue-ssr-client-manifest.json'
))

// HTML Template
template = devMiddleware.fileSystem.readFileSync(templatePath, 'utf8')

update()
onCompilationCompleted()
})
Expand Down
7 changes: 5 additions & 2 deletions lib/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@ const nodeExternals = require('webpack-node-externals')
const WebpackBar = require('webpackbar')

const config = require('./config')
const HtmlFilterPlugin = require('./HtmlFilterPlugin')

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

let webpackConfig = service.resolveChainableWebpackConfig()

webpackConfig.plugins.delete('html')
// Remove unneeded plugins
webpackConfig.plugins.delete('hmr')
webpackConfig.plugins.delete('preload')
webpackConfig.plugins.delete('prefetch')
webpackConfig.plugins.delete('pwa')
webpackConfig.plugins.delete('progress')
webpackConfig.plugins.delete('no-emit-on-errors')

// HTML
webpackConfig.plugin('html-filter').use(HtmlFilterPlugin)

webpackConfig = service.resolveWebpackConfig(webpackConfig)

webpackConfig = mergeWebpack(webpackConfig, {
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"main": "index.js",
"dependencies": {
"chalk": "^2.4.1",
"chokidar": "^2.0.4",
"clipboardy": "^1.2.3",
"compression": "^1.7.3",
"cross-env": "^5.2.0",
Expand Down

0 comments on commit 8bd2c1c

Please sign in to comment.