From 3259dade98099cec4fe6f4d042629df85ff06196 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Thu, 27 Apr 2017 19:42:17 +0200 Subject: [PATCH 1/7] [fr] Prepare french translation --- LANGS.md | 1 + fr/README.md | 50 ++++++++ fr/SUMMARY.md | 27 +++++ fr/api.md | 227 ++++++++++++++++++++++++++++++++++++ fr/basic.md | 147 ++++++++++++++++++++++++ fr/build-config.md | 209 ++++++++++++++++++++++++++++++++++ fr/bundle-renderer.md | 58 ++++++++++ fr/caching.md | 87 ++++++++++++++ fr/css.md | 114 +++++++++++++++++++ fr/data.md | 259 ++++++++++++++++++++++++++++++++++++++++++ fr/head.md | 108 ++++++++++++++++++ fr/hydration.md | 32 ++++++ fr/routing.md | 153 +++++++++++++++++++++++++ fr/streaming.md | 33 ++++++ fr/structure.md | 123 ++++++++++++++++++++ fr/universal.md | 31 +++++ 16 files changed, 1659 insertions(+) create mode 100644 fr/README.md create mode 100644 fr/SUMMARY.md create mode 100644 fr/api.md create mode 100644 fr/basic.md create mode 100644 fr/build-config.md create mode 100644 fr/bundle-renderer.md create mode 100644 fr/caching.md create mode 100644 fr/css.md create mode 100644 fr/data.md create mode 100644 fr/head.md create mode 100644 fr/hydration.md create mode 100644 fr/routing.md create mode 100644 fr/streaming.md create mode 100644 fr/structure.md create mode 100644 fr/universal.md diff --git a/LANGS.md b/LANGS.md index 25b60e8a..bf3ac484 100644 --- a/LANGS.md +++ b/LANGS.md @@ -1 +1,2 @@ * [English](en/) +* [Français](fr/) diff --git a/fr/README.md b/fr/README.md new file mode 100644 index 00000000..084c25b9 --- /dev/null +++ b/fr/README.md @@ -0,0 +1,50 @@ +# Vue.js Server-Side Rendering Guide + +> **Note:** this guide requires the following minimum versions of Vue and supporting libraries: +> - vue & vue-server-renderer >= 2.3.0 +> - vue-router >= 2.5.0 +> - vue-loader >= 12.0.0 & vue-style-loader >= 3.0.0 + +> If you have previously used Vue 2.2 with SSR, you will notice that the recommended code structure is now [a bit different](./structure.md) (with the new [runInNewContext](./api.md#runinnewcontext) option set to `false`). Your existing app should continue to work, but it's recommended to migrate to the new recommendations. + +## What is Server-Side Rendering (SSR)? + +Vue.js is a framework for building client-side applications. By default, Vue components produce and manipulate DOM in the browser as output. However, it is also possible to render the same components into HTML strings on the server, send them directly to the browser, and finally "hydrate" the static markup into a fully interactive app on the client. + +A server-rendered Vue.js app can also be considered "isomorphic" or "universal", in the sense that the majority of your app's code runs on both the server **and** the client. + +## Why SSR? + +Compared to a traditional SPA (Single-Page Application), the advantage of SSR primarily lies in: + +- Better SEO, as the search engine crawlers will directly see the fully rendered page. + + Note that as of now, Google and Bing can index synchronous JavaScript applications just fine. Synchronous being the key word there. If your app starts with a loading spinner, then fetches content via Ajax, the crawler will not wait for you to finish. This means if you have content fetched asynchronously on pages where SEO is important, SSR might be necessary. + +- Faster time-to-content, especially on slow internet or slow devices. Server-rendered markup doesn't need to wait until all JavaScript has been downloaded and executed to be displayed, so your user will see a fully-rendered page sooner. This generally results in better user experience, and can be critical for applications where time-to-content is directly associated with conversion rate. + +There are also some trade-offs to consider when using SSR: + +- Development constraints. Browser-specific code can only be used inside certain lifecycle hooks; some external libraries may need special treatment to be able to run in a server-rendered app. + +- More involved build setup and deployment requirements. Unlike a fully static SPA that can be deployed on any static file server, a server-rendered app requires an environment where a Node.js server can run. + +- More server-side load. Rendering a full app in Node.js is obviously going to be more CPU-intensive than just serving static files, so if you expect high traffic, be prepared for corresponding server load and wisely employ caching strategies. + +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. + +## SSR vs Prerendering + +If you're only investigating SSR to improve the SEO of a handful of marketing pages (e.g. `/`, `/about`, `/contact`, etc), then you probably want __prerendering__ instead. Rather than using a web server to compile HTML on-the-fly, prerendering simply generates static HTML files for specific routes at build time. The advantage is setting up prerendering is much simpler and allows you to keep your frontend as a fully static site. + +If you're using Webpack, you can easily add prerendering with the [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin). It's been extensively tested with Vue apps - and in fact, [the creator](https://github.com/chrisvfritz) is a member of the Vue core team. + +## About This Guide + +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. + +This guide will be very in-depth and assumes you are already familiar with Vue.js itself, and have decent working knowledge of Node.js and webpack. If you prefer a higher-level solution that provides a smooth out-of-the-box 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 boilerplate, 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. + +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. + +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. They might get revised in the future - and feel free to contribute by submitting pull requests! diff --git a/fr/SUMMARY.md b/fr/SUMMARY.md new file mode 100644 index 00000000..efd9eaf7 --- /dev/null +++ b/fr/SUMMARY.md @@ -0,0 +1,27 @@ +- [Basic Usage](basic.md) +- [Writing Universal Code](universal.md) +- [Source Code Structure](structure.md) +- [Routing and Code-Splitting](routing.md) +- [Data Pre-fetching and State](data.md) +- [Client Side Hydration](hydration.md) +- [Introducing Bundle Renderer](bundle-renderer.md) +- [Build Configuration](build-config.md) +- [CSS Management](css.md) +- [Head Management](head.md) +- [Caching](caching.md) +- [Streaming](streaming.md) +- [API Reference](api.md) + - [createRenderer](api.md#createrendereroptions) + - [createBundleRenderer](api.md#createbundlerendererbundle-options) + - [Class: Renderer](api.md#class-renderer) + - [Class: BundleRenderer](api.md#class-bundlerenderer) + - [Renderer Options](api.md#renderer-options) + - [template](api.md#template) + - [clientManifest](api.md#clientmanifest) + - [inject](api.md#inject) + - [shouldPreload](api.md#shouldpreload) + - [runInNewContext](api.md#runinnewcontext) + - [basedir](api.md#basedir) + - [cache](api.md#cache) + - [directives](api.md#directives) + - [Webpack Plugins](api.md#webpack-plugins) diff --git a/fr/api.md b/fr/api.md new file mode 100644 index 00000000..8c82f6b5 --- /dev/null +++ b/fr/api.md @@ -0,0 +1,227 @@ +# API Reference + +## `createRenderer([options])` + +Create a [`Renderer`](#class-renderer) instance with (optional) [options](#renderer-options). + +``` js +const { createRenderer } = require('vue-server-renderer') +const renderer = createRenderer({ ... }) +``` + +## `createBundleRenderer(bundle[, options])` + +Create a [`BundleRenderer`](#class-bundlerenderer) instance with a server bundle and (optional) [options](#renderer-options). + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer(serverBundle, { ... }) +``` + +The `serverBundle` argument can be one of the following: + +- An absolute path to generated bundle file (`.js` or `.json`). Must start with `/` to be treated as a file path. + +- A bundle object generated by webpack + `vue-server-renderer/server-plugin`. + +- A string of JavaScript code (not recommended). + +See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configuration](./build-config.md) for more details. + +## `Class: Renderer` + +- #### `renderer.renderToString(vm[, context], callback)` + + Render a Vue instance to string. The context object is optional. The callback is a typical Node.js style callback where the first argument is the error and the second argument is the rendered string. + +- #### `renderer.renderToStream(vm[, context])` + + Render a Vue instance to a Node.js stream. The context object is optional. See [Streaming](./streaming.md) for more details. + +## `Class: BundleRenderer` + +- #### `bundleRenderer.renderToString([context, ]callback)` + + Render the bundle to a string. The context object is optional. The callback is a typical Node.js style callback where the first argument is the error and the second argument is the rendered string. + +- #### `bundleRenderer.renderToStream([context])` + + Render the bundle to a Node.js stream. The context object is optional. See [Streaming](./streaming.md) for more details. + +## Renderer Options + +- #### `template` + + Provide a template for the entire page's HTML. The template should contain a comment `` which serves as the placeholder for rendered app content. + + The template also supports basic interpolation using the render context: + + - Use double-mustache for HTML-escaped interpolation; + - Use triple-mustache for non-HTML-escaped interpolation. + + The template automatically injects appropriate content when certain data is found on the render context: + + - `context.head`: (string) any head markup that should be injected into the head of the page. + + - `context.styles`: (string) any inline CSS that should be injected into the head of the page. Note this property will be automatically populated if using `vue-loader` + `vue-style-loader` for component CSS. + + - `context.state`: (Object) initial Vuex store state that should be inlined in the page as `window.__INITIAL_STATE__`. The inlined JSON is automatically sanitized with [serialize-javascript](https://github.com/yahoo/serialize-javascript) to prevent XSS. + + In addition, when `clientManifest` is also provided, the template automatically injects the following: + + - Client-side JavaScript and CSS assets needed by the render (with async chunks automatically inferred); + - Optimal `` resource hints for the rendered page. + + You can disable all automatic injections by also passing `inject: false` to the renderer. + + See also: + + - [Using a Page Template](./basic.md#using-a-page-template) + - [Manual Asset Injection](./build-config.md#manual-asset-injection) + +- #### `clientManifest` + + - 2.3.0+ + - only used in `createBundleRenderer` + + Provide a client build manifest object generated by `vue-server-renderer/server-plugin`. The client manifest provides the bundle renderer with the proper information for automatic asset injection into the HTML template. For more details, see [Generating clientManifest](./build-config.md#generating-clientmanifest). + +- #### `inject` + + - 2.3.0+ + + Controls whether to perform automatic injections when using `template`. Defaults to `true`. + + See also: [Manual Asset Injection](./build-config.md#manual-asset-injection). + +- #### `shouldPreload` + + - 2.3.0+ + + A function to control what files should have `` resource hints generated. + + By default, only JavaScript and CSS files will be preloaded, as they are absolutely needed for your application to boot. + + For other types of assets such as images or fonts, preloading too much may waste bandwidth and even hurt performance, so what to preload will be scenario-dependent. You can control precisely what to preload using the `shouldPreload` option: + + ``` js + const renderer = createBundleRenderer(bundle, { + template, + clientManifest, + shouldPreload: (file, type) => { + // type is inferred based on the file extension. + // https://fetch.spec.whatwg.org/#concept-request-destination + if (type === 'script' || type === 'style') { + return true + } + if (type === 'font') { + // only preload woff2 fonts + return /\.woff2$/.test(file) + } + if (type === 'image') { + // only preload important images + return file === 'hero.jpg' + } + } + }) + ``` + +- #### `runInNewContext` + + - 2.3.0+ + - only used in `createBundleRenderer` + + By default, for each render the bundle renderer will create a fresh V8 context and re-execute the entire bundle. This has some benefits - for example, we don't need to worry about the "stateful singleton" problem we mentioned earlier. However, this mode comes at some considerable performance cost because re-executing the bundle is expensive especially when the app gets bigger. + + This option defaults to `true` for backwards compatibility, but it is recommended to use `runInNewContext: false` whenever you can. + + See also: [Source Code Structure](./structure.md) + +- #### `basedir` + + - 2.2.0+ + - only used in `createBundleRenderer` + + Explicitly declare the base directory for the server bundle to resolve `node_modules` dependencies from. This is only needed if your generated bundle file is placed in a different location from where the externalized NPM dependencies are installed, or your `vue-server-renderer` is npm-linked into your current project. + +- #### `cache` + + Provide a [component cache](./caching.md#component-level-caching) implementation. The cache object must implement the following interface (using Flow notations): + + ``` js + type RenderCache = { + get: (key: string, cb?: Function) => string | void; + set: (key: string, val: string) => void; + has?: (key: string, cb?: Function) => boolean | void; + }; + ``` + + A typical usage is passing in an [lru-cache](https://github.com/isaacs/node-lru-cache): + + ``` js + const LRU = require('lru-cache') + + const renderer = createRenderer({ + cache: LRU({ + max: 10000 + }) + }) + ``` + + Note that the cache object should at least implement `get` and `set`. In addition, `get` and `has` can be optionally async if they accept a second argument as callback. This allows the cache to make use of async APIs, e.g. a redis client: + + ``` js + const renderer = createRenderer({ + cache: { + get: (key, cb) => { + redisClient.get(key, (err, res) => { + // handle error if any + cb(res) + }) + }, + set: (key, val) => { + redisClient.set(key, val) + } + } + }) + ``` + +- #### `directives` + + Allows you to provide server-side implementations for your custom directives: + + ``` js + const renderer = createRenderer({ + directives: { + example (vnode, directiveMeta) { + // transform vnode based on directive binding metadata + } + } + }) + ``` + + As an example, check out [`v-show`'s server-side implementation](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js). + +## Webpack Plugins + +The webpack plugins are provided as standalone files and should be required directly: + +``` js +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +``` + +The default files generated are: + +- `vue-ssr-server-bundle.json` for the server plugin; +- `vue-ssr-client-manifest.json` for the client plugin. + +The filenames can be customized when creating the plugin instances: + +``` js +const plugin = new VueSSRServerPlugin({ + filename: 'my-server-bundle.json' +}) +``` + +See [Build Configuration](./build-config.md) for more information. diff --git a/fr/basic.md b/fr/basic.md new file mode 100644 index 00000000..c5dcebbe --- /dev/null +++ b/fr/basic.md @@ -0,0 +1,147 @@ +# Basic Usage + +## Installation + +``` bash +npm install vue vue-server-renderer --save +``` + +We will be using NPM throughout the guide, but feel free to use [Yarn](https://yarnpkg.com/en/) instead. + +#### Notes + +- It's recommended to use Node.js version 6+. +- `vue-server-renderer` and `vue` must have matching versions. +- `vue-server-renderer` relies on some Node.js native modules and therefore can only be used in Node.js. We may provide a simpler build that can be run in other JavaScript runtimes in the future. + +## Rendering a Vue Instance + +``` js +// Step 1: Create a Vue instance +const Vue = require('vue') +const app = new Vue({ + template: `
Hello World
` +}) + +// Step 2: Create a renderer +const renderer = require('vue-server-renderer').createRenderer() + +// Step 3: Render the Vue instance to HTML +renderer.renderToString(app, (err, html) => { + if (err) throw err + console.log(html) + // =>

hello world

+}) +``` + +## Integrating with a Server + +It is pretty straightforward when used inside a Node.js server, for example [Express](https://expressjs.com/): + +``` bash +npm install express --save +``` +--- +``` js +const Vue = require('vue') +const server = require('express')() +const renderer = require('vue-server-renderer').createRenderer() + +server.get('*', (req, res) => { + const app = new Vue({ + data: { + url: req.url + }, + template: `
The visited URL is: {{ url }}
` + }) + + renderer.renderToString(app, (err, html) => { + if (err) { + res.status(500).end('Internal Server Error') + return + } + res.end(` + + + Hello + ${html} + + `) + }) +}) + +server.listen(8080) +``` + +## Using a Page Template + +When you render a Vue app, the renderer only generates the markup of the app. In the example we had to wrap the output with an extra HTML page shell. + +To simplify this, you can directly provide a page template when creating the renderer. Most of the time we will put the page template in its own file, e.g. `index.template.html`: + +``` html + + + Hello + + + + +``` + +Notice the `` comment -- this is where your app's markup will be injected. + +We can then read and pass the file to the Vue renderer: + +``` js +const renderer = createRenderer({ + template: require('fs').readFileSync('./index.template.html', 'utf-8') +}) + +renderer.renderToString(app, (err, html) => { + console.log(html) // will be the full page with app content injected. +}) +``` + +### Template Interpolation + +The template also supports simple interpolation. Given the following template: + +``` html + + + {{ title }} + {{{ meta }}} + + + + + +``` + +We can provide interpolation data by passing a "render context object" as the second argument to `renderToString`: + +``` js +const context = { + title: 'hello', + meta: ` + + + ` +} + +renderer.renderToString(app, context, (err, html) => { + // page title will be "hello" + // with meta tags injected +}) +``` + +The `context` object can also be shared with the Vue app instance, allowing components to dynamically register data for template interpolation. + +In addition, the template supports some advanced features such as: + +- Auto injection of critical CSS when using `*.vue` components; +- Auto injection of asset links and resource hints when using `clientManifest`; +- Auto injection and XSS prevention when embedding Vuex state for client-side hydration. + +We will discuss these when we introduce the associated concepts later in the guide. diff --git a/fr/build-config.md b/fr/build-config.md new file mode 100644 index 00000000..3beb9032 --- /dev/null +++ b/fr/build-config.md @@ -0,0 +1,209 @@ +# Build Configuration + +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). + +## Server Config + +The server config is meant for generating the server bundle that will be passed to `createBundleRenderer`. It should look like this: + +``` js +const merge = require('webpack-merge') +const nodeExternals = require('wepback-node-externals') +const baseConfig = require('./webpack.base.config.js') +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') + +module.exports = merge(baseConfig, { + // Point entry to your app's server entry file + entry: '/path/to/entry-server.js', + + // This allows webpack to handle dynamic imports in a Node-appropriate + // fashion, and also tells `vue-loader` to emit server-oriented code when + // compiling Vue components. + target: 'node', + + // For bundle renderer source map support + devtool: 'source-map', + + // This tells the server bundle to use Node-style exports + output: { + libraryTarget: 'commonjs2' + }, + + // https://webpack.js.org/configuration/externals/#function + // https://github.com/liady/webpack-node-externals + // Externalize app dependencies. This makes the server build much faster + // and generates a smaller bundle file. + externals: nodeExternals({ + // do not externalize dependencies that need to be processed by webpack. + // you can add more file types here e.g. raw *.vue files + whitelist: /\.css$/ + }), + + // This is the plugin that turns the entire output of the server build + // into a single JSON file. The default file name will be + // `vue-ssr-server-bundle.json` + plugins: [ + new VueSSRServerPlugin() + ] +}) +``` + +After `vue-ssr-server-bundle.json` has been generated, simply pass the file path to `createBundleRenderer`: + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { + // ...other renderer options +}) +``` + +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). + +## Client Config + +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. + +### Generating `clientManifest` + +> requires version 2.3.0+ + +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 css links / script tags into the rendered HTML. + +The benefits is two-fold: + +1. It can replace `html-webpack-plugin` for injecting the correct asset URLs when there are hashes in your generated filenames. + +2. When rendering a bundle that leverages webpack's on-demand code splitting features, we can ensure the optimal chunks are preloaded / prefetched, and also intelligently inject ` + + + + +` +``` + +### Manual Asset Injection + +By default, asset injection is automatic when you provide the `template` render option. But sometimes you might want finer-grained control over how assets are injected into the template, or maybe you are not using a template at all. In such a case, you can pass `inject: false` when creating the renderer and manually perform asset injection. + +In the `renderToString` callback, the `context` object you passed in will expose the following methods: + +- `context.renderStyles()` + + This will return inline `