Skip to content

Commit 978f8b6

Browse files
committed
docs: include ssr setup in example
1 parent b85244b commit 978f8b6

File tree

5 files changed

+164
-94
lines changed

5 files changed

+164
-94
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
.DS_Store
33
node_modules
44
dist
5+
dist-ssr
56
link.sh
67
.cache
78
TODOs.md

example/main.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import App from './App.vue'
2-
import { createApp } from 'vue'
2+
import { createApp, createSSRApp } from 'vue'
33

4-
createApp(App).mount('#app')
4+
const app = __IS_SSR__ ? createSSRApp(App) : createApp(App)
5+
export default app
6+
7+
if (typeof window !== 'undefined') {
8+
app.mount('#app')
9+
}

example/ssr.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This script is part of `yarn build-example-ssr`.
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
const { renderToString } = require('@vue/server-renderer')
6+
const template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')
7+
8+
// here out server-side build directly exports an app instance.
9+
// in an actual SSR setup, you'll want to export a `createApp()` function that
10+
// returns a fresh app instance for each request. You probably also want to
11+
// return the router instance so that you can set the app's route state before
12+
// actually rendering it.
13+
const app = require('./dist-ssr/server/main.js').default
14+
15+
renderToString(app).then((html) => {
16+
fs.writeFileSync(
17+
path.resolve(__dirname, 'dist-ssr/index.html'),
18+
template.replace(/(<div id="app">)/, `$1${html}`)
19+
)
20+
})

example/webpack.config.js

+135-92
Original file line numberDiff line numberDiff line change
@@ -7,104 +7,147 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
77

88
module.exports = (env = {}) => {
99
const isProd = env.prod
10-
const minimize = isProd && !env.noMinimize
11-
const babel = isProd && !env.noBabel
10+
const isSSR = env.ssr
1211

13-
return {
14-
mode: isProd ? 'production' : 'development',
15-
entry: path.resolve(__dirname, './main.js'),
16-
devtool: 'source-map',
17-
output: {
18-
path: path.resolve(__dirname, 'dist'),
19-
filename: '[name].js',
20-
publicPath: '/dist/',
21-
},
22-
module: {
23-
rules: [
24-
{
25-
test: /\.vue$/,
26-
loader: 'vue-loader',
27-
},
28-
{
29-
test: /\.png$/,
30-
use: [
31-
{
32-
loader: 'url-loader',
33-
options: {
34-
limit: 8192,
35-
},
12+
/**
13+
* Some notes regarding config for the server build of an SSR app:
14+
* 1. target: 'node'
15+
* 2. output.libraryTarget: 'commonjs' (so the exported app can be required)
16+
* 3. externals: this is mostly for faster builds.
17+
* - externalize @vue/* deps via commonjs require()
18+
* - externalize client side deps that are never used on the server, e.g.
19+
* ones that are only used in onMounted() to empty modules
20+
* 4. If using cache-loader or any other forms of cache, make sure the cache
21+
* key takes client vs. server builds into account!
22+
*/
23+
const genConfig = (isServerBuild = false) => {
24+
const minimize = isProd && !isServerBuild && !env.noMinimize
25+
const useBabel = isProd && !isServerBuild && !env.noBabel
26+
27+
return {
28+
mode: isProd ? 'production' : 'development',
29+
entry: path.resolve(__dirname, './main.js'),
30+
target: isServerBuild ? 'node' : 'web',
31+
devtool: 'source-map',
32+
output: {
33+
path: path.resolve(
34+
__dirname,
35+
isSSR ? (isServerBuild ? 'dist-ssr/server' : 'dist-ssr/dist') : 'dist'
36+
),
37+
filename: '[name].js',
38+
publicPath: '/dist/',
39+
libraryTarget: isServerBuild ? 'commonjs' : undefined,
40+
},
41+
externals: isServerBuild
42+
? [
43+
(ctx, request, cb) => {
44+
if (/^@vue/.test(request)) {
45+
return cb(null, 'commonjs ' + request)
46+
}
47+
cb()
3648
},
37-
],
38-
},
39-
{
40-
test: /\.css$/,
41-
use: [
42-
{
43-
loader: MiniCssExtractPlugin.loader,
44-
options: {
45-
hmr: !isProd,
49+
]
50+
: undefined,
51+
module: {
52+
rules: [
53+
{
54+
test: /\.vue$/,
55+
loader: 'vue-loader',
56+
},
57+
{
58+
test: /\.png$/,
59+
use: [
60+
{
61+
loader: 'url-loader',
62+
options: {
63+
limit: 8192,
64+
},
4665
},
47-
},
48-
'css-loader',
49-
],
50-
},
51-
{
52-
test: /\.js$/,
53-
use: [
54-
{
55-
loader: 'cache-loader',
56-
options: {
57-
cacheIdentifier: hash(
58-
fs.readFileSync(path.resolve(__dirname, '../package.json')) +
59-
JSON.stringify(env)
60-
),
61-
cacheDirectory: path.resolve(__dirname, '../.cache'),
66+
],
67+
},
68+
{
69+
test: /\.css$/,
70+
use: [
71+
{
72+
loader: MiniCssExtractPlugin.loader,
73+
options: {
74+
hmr: !isProd,
75+
},
6276
},
63-
},
64-
...(babel
65-
? [
66-
{
67-
loader: 'babel-loader',
68-
options: {
69-
// use yarn build-example --env.noMinimize to verify that
70-
// babel is properly applied to all js code, including the
71-
// render function compiled from SFC templates.
72-
presets: ['@babel/preset-env'],
77+
'css-loader',
78+
],
79+
},
80+
{
81+
test: /\.js$/,
82+
use: [
83+
{
84+
loader: 'cache-loader',
85+
options: {
86+
cacheIdentifier: hash(
87+
// deps
88+
fs.readFileSync(
89+
path.resolve(__dirname, '../package.json')
90+
) +
91+
// env
92+
JSON.stringify(env) +
93+
// client vs. server build
94+
isServerBuild
95+
),
96+
cacheDirectory: path.resolve(__dirname, '../.cache'),
97+
},
98+
},
99+
...(useBabel
100+
? [
101+
{
102+
loader: 'babel-loader',
103+
options: {
104+
// use yarn build-example --env.noMinimize to verify that
105+
// babel is properly applied to all js code, including the
106+
// render function compiled from SFC templates.
107+
presets: ['@babel/preset-env'],
108+
},
73109
},
74-
},
75-
]
76-
: []),
77-
],
78-
},
79-
// target <docs> custom blocks
80-
{
81-
resourceQuery: /blockType=docs/,
82-
loader: require.resolve('./docs-loader'),
83-
},
110+
]
111+
: []),
112+
],
113+
},
114+
// target <docs> custom blocks
115+
{
116+
resourceQuery: /blockType=docs/,
117+
loader: require.resolve('./docs-loader'),
118+
},
119+
],
120+
},
121+
plugins: [
122+
new VueLoaderPlugin(),
123+
new MiniCssExtractPlugin({
124+
filename: '[name].css',
125+
}),
126+
new webpack.DefinePlugin({
127+
__IS_SSR__: !!isSSR,
128+
__VUE_OPTIONS_API__: true,
129+
__VUE_PROD_DEVTOOLS__: false,
130+
}),
84131
],
85-
},
86-
plugins: [
87-
new VueLoaderPlugin(),
88-
new MiniCssExtractPlugin({
89-
filename: '[name].css',
90-
}),
91-
new webpack.DefinePlugin({
92-
__VUE_OPTIONS_API__: true,
93-
__VUE_PROD_DEVTOOLS__: false,
94-
}),
95-
],
96-
optimization: {
97-
minimize,
98-
},
99-
devServer: {
100-
stats: 'minimal',
101-
contentBase: __dirname,
102-
overlay: true,
103-
},
104-
resolveLoader: {
105-
alias: {
106-
'vue-loader': require.resolve('../'),
132+
optimization: {
133+
minimize,
134+
},
135+
devServer: {
136+
stats: 'minimal',
137+
contentBase: __dirname,
138+
overlay: true,
107139
},
108-
},
140+
resolveLoader: {
141+
alias: {
142+
'vue-loader': require.resolve('../'),
143+
},
144+
},
145+
}
146+
}
147+
148+
if (!isSSR) {
149+
return genConfig()
150+
} else {
151+
return [genConfig(), genConfig(true)]
109152
}
110153
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"test:webpack5": "WEBPACK5=true jest -c --coverage",
1717
"dev-example": "webpack-dev-server --config example/webpack.config.js --inline --hot",
1818
"build-example": "rm -rf example/dist && webpack --config example/webpack.config.js --env.prod",
19+
"build-example-ssr": "rm -rf example/dist-ssr && webpack --config example/webpack.config.js --env.prod --env.ssr && node example/ssr.js",
1920
"lint": "prettier --write --parser typescript \"{src,test}/**/*.{j,t}s\"",
2021
"prepublishOnly": "tsc"
2122
},

0 commit comments

Comments
 (0)