|
| 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 | + |
0 commit comments