Skip to content

Commit 1d2cdac

Browse files
committed
progress
1 parent e3cec90 commit 1d2cdac

12 files changed

+460
-34
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
.DS_Store
33
_book
4+
test

en/README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,12 @@ There are also some trade-offs to consider when using SSR:
2626

2727
Before using SSR for your app, the first question you should ask it whether you actually need it. It mostly depends on how important time-to-content is for your app. For example, if you are building an internal dashboard where an extra few hundred milliseconds on initial load doesn't matter that much, SSR would be an overkill. However, in cases where time-to-content is absolutely critical, SSR can help you achieve the best possible initial load performance.
2828

29-
If you believe your use case justifies the use of SSR, then let's dive in!
29+
## About This Guide
30+
31+
This guide is focused on server-rendered Single-Page Applications using Node.js as the server. Mixing Vue SSR with other backend setups is a topic of its own and is not covered in this guide.
32+
33+
This guide assumes you are already familiar with Vue.js itself, and have working knowledge of Node.js and webpack. We acknowledge that it could be quite challenging to build a server-rendered Vue app if you lack prior experience. If you prefer a higher-level solution that provides a smoother on-boarding experience, you should probably give [Nuxt.js](http://nuxtjs.org/) a try. It's built upon the same Vue stack but abstracts away a lot of the complexities, and provides some extra features such as static site generation. However, it may not suit your use case if you need more direct control of your app's structure. Regardless, it would still be beneficial to read through this guide to better understand how things work together.
34+
35+
As you read along, it would be helpful to refer to the official [HackerNews Demo](https://github.com/vuejs/vue-hackernews-2.0/), which makes use of most of the techniques covered in this guide.
36+
37+
Finally, note that the solutions in this guide are not definitive - we've found them to be working well for us, but that doesn't mean they cannot be improved. Feel free to contribute ideas on how to solve them more elegantly!

en/SUMMARY.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
- [Basic Usage](basic.md)
22
- [Writing Universal Code](universal.md)
3-
- [Using a Page Template](template.md)
4-
- [Using the Bundle Renderer](bundle-renderer.md)
5-
- [Creating the Server Bundle](server-bundle.md)
6-
- [Creating the Client Manifest](client-manifest.md)
7-
- [Routing](routing.md)
3+
- [Source Code Structure](structure.md)
4+
- [Routing and Code-Splitting](routing.md)
85
- [Data Pre-fetching and State](data.md)
6+
- [Using a Page Template](template.md)
7+
- [Introducing Bundle Renderer](bundle-renderer.md)
8+
- [Build Configuration](build-config.md)
9+
- [CSS Management](css.md)
910
- [Head Management](head.md)
10-
- [Handling CSS](css.md)
1111
- [Caching](caching.md)
1212
- [Streaming](streaming.md)
1313
- [Client Side Hydration](hydration.md)
14+
- [Security](security.md)
1415
- [API Reference](api.md)

en/basic.md

+29-10
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
## Installation
44

55
``` bash
6-
npm install vue-server-renderer --save
6+
npm install vue vue-server-renderer --save
77
```
88

9+
We will be using NPM throughout the guide, but feel free to use [Yarn](https://yarnpkg.com/en/) instead.
10+
911
#### Notes
1012

1113
- It's recommended to use Node.js version 6+.
@@ -28,30 +30,47 @@ const renderer = require('vue-server-renderer').createRenderer()
2830
renderer.renderToString(app, (err, html) => {
2931
if (err) throw err
3032
console.log(html)
31-
// => <p server-rendered="true">hello world</p>
33+
// => <p data-server-rendered="true">hello world</p>
3234
})
3335
```
3436

37+
## Integrating with a Server
38+
3539
It is pretty straightforward when used inside a Node.js server, for example [Express](https://expressjs.com/):
3640

41+
``` bash
42+
npm install express --save
43+
```
44+
---
3745
``` js
38-
app.get('*', (req, res) => {
46+
const Vue = require('vue')
47+
const server = require('express')()
48+
const renderer = require('vue-server-renderer').createRenderer()
49+
50+
server.get('*', (req, res) => {
3951
const app = new Vue({
4052
data: {
4153
url: req.url
4254
},
43-
template: `<div>Hello {{ url }}</div>`
55+
template: `<div>The visited URL is: {{ url }}</div>`
4456
})
4557

4658
renderer.renderToString(app, (err, html) => {
47-
if (err) throw err
48-
res.end(html)
59+
if (err) {
60+
res.status(500).end('Internal Server Error')
61+
return
62+
}
63+
res.end(`
64+
<!DOCTYPE html>
65+
<html lang="en">
66+
<head><title>Hello</title></head>
67+
<body>${html}</body>
68+
</html>
69+
`)
4970
})
5071
})
72+
73+
server.listen(8080)
5174
```
5275

5376
This is the most basic API to render a Vue app on the server. However, this is far from sufficient for a real world server-rendered app. In the following chapters we will cover the common issues encountered and how to deal with them.
54-
55-
As you read along, it would also be helpful to refer to the official [HackerNews Demo](https://github.com/vuejs/vue-hackernews-2.0/), which makes use of most of the techniques covered in this guide.
56-
57-
We also realize it could be quite challenging to build a server-rendered Vue app if you are not already familiar with webpack, Node.js and Vue itself. If you prefer a higher-level solution that provides a smoother on-boarding experience, you should probably give [Nuxt.js](http://nuxtjs.org/) a try. It's built upon the same Vue stack but abstracts away a lot of the complexities, and provides some extra features such as static site generation. However, it may not suit your use case if you need more direct control of your app's structure. And it would still be beneficial to read through this guide to better understand how things work together.

en/build-config.md

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Build Configuration
2+
3+
> The following build config is meant for those who are interested in assembling an SSR app from scratch - we are going to provide official vue-cli templates that pre-configures everything for you in the near future.
4+
5+
We will assume you already know how to configure webpack for a client-only project. The config for an SSR project will be largely similar, but we suggest breaking the config into three files: *base*, *client* and *server*. The base config contains config shared for both environments, such as output path, aliases, and loaders. The server config and client config can simply extend the base config using [webpack-merge](https://github.com/survivejs/webpack-merge).
6+
7+
## Server Config
8+
9+
The server config is meant for generating the server bundle that will be passed to `createBundleRenderer`. It should look like this:
10+
11+
``` js
12+
const merge = require('webpack-merge')
13+
const baseConfig = require('./webpack.base.config.js')
14+
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
15+
16+
module.exports = merge(baseConfig, {
17+
// Point entry to your app's server entry file
18+
entry: '/path/to/entry-server.js',
19+
20+
// This allows webpack to handle dynamic imports in a Node-appropriate
21+
// fashion, and also tells `vue-loader` to emit server-oriented code when
22+
// compiling Vue components.
23+
target: 'node',
24+
25+
// For bundle renderer source map support
26+
devtool: 'source-map',
27+
28+
// This tells the server bundle to use Node-style exports
29+
output: {
30+
libraryTarget: 'commonjs2'
31+
},
32+
33+
// Externalize app dependencies. This makes the server build much faster
34+
// and generates a smaller bundle file. Note if you want a dependency
35+
// (e.g. UI lib that provides raw *.vue files) to be processed by webpack,
36+
// don't include it here.
37+
externals: Object.keys(require('/path/to/package.json').dependencies),
38+
39+
// This is the plugin that turns the entire output of the server build
40+
// into a single JSON file. The default file name will be
41+
// `vue-ssr-server-bundle.json`
42+
plugins: [
43+
new VueSSRServerPlugin()
44+
]
45+
})
46+
```
47+
48+
After `vue-ssr-server-bundle.json` has been generated, simply pass the file path to `createBundleRenderer`:
49+
50+
``` js
51+
const { createBundleRenderer } = require('vue-server-renderer')
52+
const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', {
53+
// ...other renderer options
54+
})
55+
```
56+
57+
Alternatively, you can also pass the bundle as an Object to `createBundleRenderer`. This is useful for hot-reload during development - see the HackerNews demo for a [reference setup](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js).
58+
59+
## Client Config
60+
61+
The client config can remain largely the same with the base config. Obviously you need to point `entry` to your client entry file. Aside from that, if you are using `CommonsChunkPlugin`, make sure to use it only in the client config because the server bundle requires a single entry chunk.
62+
63+
### Generating `clientManifest`
64+
65+
> requires version 2.3.0+
66+
67+
In addition to the server bundle, we can also generate a client build manifest. With the client manifest and the server bundle, the renderer now has information of both the server *and* client builds, so it can automatically infer and inject [preload / prefetch directives](https://css-tricks.com/prefetching-preloading-prebrowsing/) and script tags into the rendered HTML.
68+
69+
This is particularly useful when rendering a bundle that leverages webpack's on-demand code splitting features: we can ensure the optimal chunks are preloaded / prefetched, and also directly embed `<script>` tags for needed async chunks in the HTML to avoid waterfall requests on the client, thus improving TTI (time-to-interactive).
70+
71+
To make use of the client manifest, the client config would look something like this:
72+
73+
``` js
74+
const webpack = require('webpack')
75+
const merge = require('webpack-merge')
76+
const baseConfig = require('./webpack.base.config.js')
77+
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
78+
79+
module.exports = merge(baseConfig, {
80+
entry: '/path/to/entry-client.js',
81+
plugins: [
82+
// Important: this splits the webpack runtime into a leading chunk
83+
// so that async chunks can be injected right after it.
84+
// this also enables better caching for your app/vendor code.
85+
new webpack.optimize.CommonsChunkPlugin({
86+
name: "manifest",
87+
minChunks: Infinity
88+
}),
89+
// This plugins generates `vue-ssr-client-manifest.json` in the
90+
// output directory.
91+
new VueSSRClientPlugin()
92+
]
93+
})
94+
```
95+
96+
You can then use the generated client manifest, together with a page template:
97+
98+
``` js
99+
const { createBundleRenderer } = require('vue-server-renderer')
100+
101+
const template = require('fs').readFileSync('/path/to/template.html', 'utf-8')
102+
const serverBundle = require('/path/to/vue-ssr-bundle.json')
103+
const clientManifest = require('/path/to/vue-ssr-client-manifest.json')
104+
105+
const renderer = createBundleRenderer(serverBundle, {
106+
template,
107+
clientManifest
108+
})
109+
```
110+
111+
With this setup, your server-rendered HTML for a build with code-splitting will look something like this (everything auto-injected):
112+
113+
``` html
114+
<html>
115+
<head>
116+
<!-- chunks used for this render will be preloaded -->
117+
<link rel="preload" href="/manifest.js" as="script">
118+
<link rel="preload" href="/main.js" as="script">
119+
<link rel="preload" href="/0.js" as="script">
120+
<!-- unused async chunks will be prefetched (lower priority) -->
121+
<link rel="prefetch" href="/1.js" as="script">
122+
</head>
123+
<body>
124+
<!-- app content -->
125+
<div data-server-rendered="true"><div>async</div></div>
126+
<!-- manifest chunk should be first -->
127+
<script src="/manifest.js"></script>
128+
<!-- async chunks injected before main chunk -->
129+
<script src="/0.js"></script>
130+
<script src="/main.js"></script>
131+
</body>
132+
</html>`
133+
```
134+
135+
## More About Asset Injection
136+
137+
### `shouldPreload`
138+
139+
By default, only JavaScript chunks used during the server-side render will be preloaded, as they are absolutely needed for your application to boot.
140+
141+
For other types of assets such as images or fonts, how much and what to preload will be scenario-dependent. You can control precisely what to preload using the `shouldPreload` option:
142+
143+
``` js
144+
const renderer = createBundleRenderer(bundle, {
145+
template,
146+
clientManifest,
147+
shouldPreload: (file, type) => {
148+
// type is inferred based on the file extension.
149+
// https://fetch.spec.whatwg.org/#concept-request-destination
150+
if (type === 'script') {
151+
return true
152+
}
153+
if (type === 'font') {
154+
// only preload woff2 fonts
155+
return /\.woff2$/.test(file)
156+
}
157+
if (type === 'image') {
158+
// only preload important images
159+
return file === 'hero.jpg'
160+
}
161+
}
162+
})
163+
```
164+
165+
### Injection without Template
166+
167+

en/bundle-renderer.md

+44-12
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,56 @@
1-
# Using the BundleRenderer
1+
# Introducing Bundle Renderer
22

33
## Problems with Basic SSR
44

55
In our basic usage example, we directly required Vue and created an app instance in our Node.js server code. This is straightforward, however has quite a few issues in practice:
66

7-
- **Build Tool Integration:**
7+
- Typical Vue apps are built with webpack and `vue-loader`, and many webpack-specific features such as importing files via `file-loader`, importing CSS via `css-loader` would not work directly in Node.js.
88

9-
- **Source Map:**
9+
- Although the latest version of Node.js fully supports ES2015 features, we still need to transpile client-side code to cater to older browsers. This again involves a build step.
1010

11-
- **Development and Deployment:**
11+
- Getting source maps to work with Node.js can be tricky.
1212

13-
- **Code Splitting:**
13+
- Directly requiring Vue app code in the server process means whenever you edit your app source code, you would have to stop and restart the server. Ideally, we want our server-rendering logic to be hot-reloadable too!
1414

1515
## Enter BundleRenderer
1616

17-
- Generates a bundle JSON file by using webpack plugin
18-
- Source map support
19-
- Hot-reload of the bundle
20-
- Works well with route-level code-splitting
21-
- Automatic critical CSS injection with [`vue-style-loader`](https://github.com/vuejs/vue-style-loader)
22-
- Automatic asset injection with [clientManifest](./client-manifest.md)
17+
![architecture](https://cloud.githubusercontent.com/assets/499550/17607895/786a415a-5fee-11e6-9c11-45a2cfdf085c.png)
2318

24-
## The `runInNewContext` Option
19+
`vue-server-renderer` provides an API called `createBundleRenderer` to deal with these problems. The basic idea is that we use the same Vue app source code to generate two bundles - one for the client and one for the server. With a custom webpack plugin, the server bundle is generated as a special JSON file that can be passed to the bundle renderer. Once the bundle renderer is created, usage is the same as the normal renderer, however the bundle renderer provides the following benefits:
20+
21+
- Reuse the vast majority of the app source code and build configuration for both server and client
22+
23+
- Built-in source map support for runtime errors
24+
25+
- Hot-reload during development and even deployment (by simply reading the updated bundle and re-creating the renderer instance)
26+
27+
- Seamlessly supports webpack code-splitting (lazy loading)
28+
29+
- Critical CSS injection (when using `*.vue` files): automatically inlines the CSS needed by components used during the render. See the [CSS](./css.md) section for more details.
30+
31+
- Asset injection with [clientManifest](./client-manifest.md): automatically infers the optimal preload and prefetch directives, and the code-split chunks needed for the initial render.
32+
33+
---
34+
35+
We will discuss how to configure webpack to generate the build artifacts needed by the bundle renderer in the next section, but for now let's assume we already have what we need, and this is how to create a use a bundle renderer:
36+
37+
``` js
38+
const renderer = require('vue-server-renderer').createBundleRenderer(
39+
bundle, // server bundle generated by the server build
40+
{
41+
template, // page template
42+
clientManifest // client build manifest generated by the client build
43+
}
44+
)
45+
46+
// inside a server handler...
47+
server.get('*', (req, res) => {
48+
const context = { url: req.url }
49+
// No need to pass an app here because it is auto-created by the
50+
// executing the bundle. Now our server is decoupled from our Vue app!
51+
renderer.renderToString(context, (err, html) => {
52+
// handle error...
53+
res.end(html)
54+
})
55+
})
56+
```

en/code-splitting.md

Whitespace-only changes.
File renamed without changes.

en/server-bundle.md

Whitespace-only changes.

0 commit comments

Comments
 (0)