From 435f75d3c9684ace392fc07b4305de58673487aa Mon Sep 17 00:00:00 2001 From: Olivier Bertrand Date: Fri, 28 Apr 2017 12:21:31 +0200 Subject: [PATCH 001/239] Fix nodeExternals require typo (#4) Fix typo in `webpack-node-externals` --- en/build-config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/build-config.md b/en/build-config.md index 3beb9032..d857c107 100644 --- a/en/build-config.md +++ b/en/build-config.md @@ -8,7 +8,7 @@ The server config is meant for generating the server bundle that will be passed ``` js const merge = require('webpack-merge') -const nodeExternals = require('wepback-node-externals') +const nodeExternals = require('webpack-node-externals') const baseConfig = require('./webpack.base.config.js') const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') From 67740e902812b687aa268752686178a607535189 Mon Sep 17 00:00:00 2001 From: Sam Richard Date: Sun, 30 Apr 2017 10:04:44 +0530 Subject: [PATCH 002/239] :bug: Fix rendered example (#6) The component in `Rendering a Vue Instance` renders to a `div` tag, not a `p` tag. --- en/basic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/basic.md b/en/basic.md index c5dcebbe..bc50f593 100644 --- a/en/basic.md +++ b/en/basic.md @@ -30,7 +30,7 @@ const renderer = require('vue-server-renderer').createRenderer() renderer.renderToString(app, (err, html) => { if (err) throw err console.log(html) - // =>

hello world

+ // =>
hello world
}) ``` From 32069479b961a38e1303a0b51ecb401fe8fb0cd0 Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Sun, 30 Apr 2017 14:29:13 +0300 Subject: [PATCH 003/239] Update bundle-renderer.md (#8) Fix typo in example --- en/bundle-renderer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/bundle-renderer.md b/en/bundle-renderer.md index 2be30fe9..50476243 100644 --- a/en/bundle-renderer.md +++ b/en/bundle-renderer.md @@ -31,7 +31,7 @@ const { createBundleRenderer } = require('vue-server-renderer') const renderer = createBundleRenderer(serverBundle, { runInNewContext: false, // recommended - template, // (optinal) page template + template, // (optional) page template clientManifest // (optional) client build manifest }) From 0d6345c7ffc8ae03ad4d6f5777790131e42110f6 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Sun, 30 Apr 2017 16:01:51 +0430 Subject: [PATCH 004/239] fix link to clientManifest (#7) --- en/bundle-renderer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/bundle-renderer.md b/en/bundle-renderer.md index 50476243..d354eb29 100644 --- a/en/bundle-renderer.md +++ b/en/bundle-renderer.md @@ -20,7 +20,7 @@ This is straightforward, however whenever you edit your app source code, you wou - 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. -- 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. +- Asset injection with [clientManifest](./api.md#clientmanifest): automatically infers the optimal preload and prefetch directives, and the code-split chunks needed for the initial render. --- From f0ed9f5a5a46518f72970fee47a6c2be00a0490e Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 1 May 2017 14:20:48 +0800 Subject: [PATCH 005/239] improve information regarding bundle global context --- en/build-config.md | 7 +++++++ en/bundle-renderer.md | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/en/build-config.md b/en/build-config.md index d857c107..740e0dfb 100644 --- a/en/build-config.md +++ b/en/build-config.md @@ -36,6 +36,7 @@ module.exports = merge(baseConfig, { 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 + // you should also whitelist deps that modifies `global` (e.g. polyfills) whitelist: /\.css$/ }), @@ -59,6 +60,12 @@ const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { 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). +### Externals Caveats + +Notice that in the `externals` option we are whitelisting CSS files. This is because CSS imported from dependencies should still be handled by webpack. If you are importing any other types of files that also rely on webpack (e.g. `*.vue`, `*.sass`), you should add them to the whitelist as well. + +Another type of modules to whitelist are polyfills that modify `global`, e.g. `babel-polyfill`. This is because **code inside a server bundle has its own `global` object.** Since you don't really need it on the server when using Node 7.6+, it's actually easier to just import it in the client entry. + ## 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. diff --git a/en/bundle-renderer.md b/en/bundle-renderer.md index d354eb29..3c0b7cae 100644 --- a/en/bundle-renderer.md +++ b/en/bundle-renderer.md @@ -53,6 +53,8 @@ When `rendertoString` is called on a bundle renderer, it will automatically exec ### The `runInNewContext` Option -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. +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 entire bundle is expensive especially when the app gets bigger. In `vue-server-renderer >= 2.3.0`, this option still defaults to `true` for backwards compatibility, but it is recommended to use `runInNewContext: false` whenever you can. + +Note that when using `runInNewContext: false`, the bundle is still **evaluated in a separate `global` context**, however only once. This prevents the bundle accidentally polluting the server process' `global` object. The difference from the default behavior is that it will not create **new** contexts for each render call. From df122dc9506ee9e602242d23934f3cb7612cdd78 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 1 May 2017 13:03:28 +0300 Subject: [PATCH 006/239] Update data.md (#11) --- en/data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/data.md b/en/data.md index 7d41bd38..bc9e042d 100644 --- a/en/data.md +++ b/en/data.md @@ -148,7 +148,7 @@ export default context => { } ``` -When using `template`, `context.state` will automatically be embedded in the final HTML as `window.__INITIAL__` state. On the client, the store should pick up the state before mounting the application: +When using `template`, `context.state` will automatically be embedded in the final HTML as `window.__INITIAL_STATE__` state. On the client, the store should pick up the state before mounting the application: ``` js // entry-client.js From 047366bf2f8a6f3d7e2a86e7984e93b594d58747 Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Mon, 1 May 2017 13:35:58 +0300 Subject: [PATCH 007/239] Fix typo in bundle-renderer.md (#12) --- en/bundle-renderer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/bundle-renderer.md b/en/bundle-renderer.md index 3c0b7cae..5d30dfac 100644 --- a/en/bundle-renderer.md +++ b/en/bundle-renderer.md @@ -47,7 +47,7 @@ server.get('*', (req, res) => { }) ``` -When `rendertoString` is called on a bundle renderer, it will automatically execute the function exported by the bundle to create an app instance (passing `context` as the argument) , and then render it. +When `renderToString` is called on a bundle renderer, it will automatically execute the function exported by the bundle to create an app instance (passing `context` as the argument) , and then render it. --- From 39e6e25065f17185b211266467edcc9a9a83f5f4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 2 May 2017 15:44:41 +0800 Subject: [PATCH 008/239] document runInNewContext changes in 2.3.1 --- en/api.md | 14 ++++++++++++-- en/build-config.md | 2 +- en/bundle-renderer.md | 10 +--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/en/api.md b/en/api.md index 8c82f6b5..f82bfd3a 100644 --- a/en/api.md +++ b/en/api.md @@ -130,10 +130,20 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati - 2.3.0+ - only used in `createBundleRenderer` + - Expects: `boolean | 'once'` (`'once'` only supported in 2.3.1+) - 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. + 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, the app code is isolated from the server process and we don't need to worry about the [stateful singleton problem](./structure.md#avoid-stateful-singletons) mentioned in the docs. 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. + This option defaults to `true` for backwards compatibility, but it is recommended to use `runInNewContext: false` or `runInNewContext: 'once'` whenever you can. + + > In 2.3.0 this option has a bug where `runInNewContext: false` still executes the bundle using a separate global context. The following information assumes version 2.3.1+. + + With `runInNewContext: false`, the bundle code will run in the same `global` context with the server process, so be careful about code that modifies `global` in your application code. + + With `runInNewContext: 'once'` (2.3.1+), the bundle is evaluated in a separate `global` context, however only once at startup. This provides better app code isolation since it prevents the bundle from accidentally polluting the server process' `global` object. The caveats are that: + + 1. Dependencies that modifies `global` (e.g. polyfills) cannot be externalized in this mode; + 2. Values returned from the bundle execution will be using different global constructors, e.g. an error caught inside the bundle will not be an instance of `Error` in the server process. See also: [Source Code Structure](./structure.md) diff --git a/en/build-config.md b/en/build-config.md index 740e0dfb..646f4760 100644 --- a/en/build-config.md +++ b/en/build-config.md @@ -64,7 +64,7 @@ Alternatively, you can also pass the bundle as an Object to `createBundleRendere Notice that in the `externals` option we are whitelisting CSS files. This is because CSS imported from dependencies should still be handled by webpack. If you are importing any other types of files that also rely on webpack (e.g. `*.vue`, `*.sass`), you should add them to the whitelist as well. -Another type of modules to whitelist are polyfills that modify `global`, e.g. `babel-polyfill`. This is because **code inside a server bundle has its own `global` object.** Since you don't really need it on the server when using Node 7.6+, it's actually easier to just import it in the client entry. +If you are using `runInNewContext: 'once'` or `runInNewContext: true`, then you also need to whitelist polyfills that modify `global`, e.g. `babel-polyfill`. This is because when using the new context mode, **code inside a server bundle has its own `global` object.** Since you don't really need it on the server when using Node 7.6+, it's actually easier to just import it in the client entry. ## Client Config diff --git a/en/bundle-renderer.md b/en/bundle-renderer.md index 5d30dfac..f90950b0 100644 --- a/en/bundle-renderer.md +++ b/en/bundle-renderer.md @@ -49,12 +49,4 @@ server.get('*', (req, res) => { When `renderToString` is called on a bundle renderer, it will automatically execute the function exported by the bundle to create an app instance (passing `context` as the argument) , and then render it. ---- - -### The `runInNewContext` Option - -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 entire bundle is expensive especially when the app gets bigger. - -In `vue-server-renderer >= 2.3.0`, this option still defaults to `true` for backwards compatibility, but it is recommended to use `runInNewContext: false` whenever you can. - -Note that when using `runInNewContext: false`, the bundle is still **evaluated in a separate `global` context**, however only once. This prevents the bundle accidentally polluting the server process' `global` object. The difference from the default behavior is that it will not create **new** contexts for each render call. +Note it's recommended to set the `runInNewContext` option to `false` or `'once'`. See its [API reference](./api.md#runinnewcontext) for more details. From e86b8eec668274aa63c8765625903afc1479afd5 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 2 May 2017 18:45:18 +0800 Subject: [PATCH 009/239] update head management --- en/head.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/en/head.md b/en/head.md index 864f824f..90458c6c 100644 --- a/en/head.md +++ b/en/head.md @@ -2,27 +2,9 @@ Similar to asset injection, head management follows the same idea: we can dynamically attach data to the render `context` in a component's lifecycle, and then interpolate those data in `template`. -To do that we need to have access to the SSR context inside a nested component. We can simply pass the `context` to `createApp()` and expose it on the root instance's `$options`: +> In version >=2.3.2, you can directly access the SSR context in a component as `this.$ssrContext`. In older versions you'd have to manually inject the SSR context by passing it to `createApp()` and expose it on the root instance's `$options` - child components can then access it via `this.$root.$options.ssrContext`. -``` js -// app.js - -export function createApp (ssrContext) { - // ... - const app = new Vue({ - router, - store, - // all child components can access this as this.$root.$options.ssrContext - ssrContext, - render: h => h(App) - }) - // ... -} -``` - -This can also be done via `provide/inject`, but since we know it's going to be on `$root`, we can avoid the injection resolution costs. - -With the context injected, we can write a simple mixin to perform title management: +We can write a simple mixin to perform title management: ``` js // title-mixin.js @@ -42,7 +24,7 @@ const serverTitleMixin = { created () { const title = getTitle(this) if (title) { - this.$root.$options.ssrContext.title = title + this.$ssrContext.title = title } } } From 90d8bee3833ceb3e614ecc0f5971930c074e06e7 Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 3 May 2017 06:19:04 -0500 Subject: [PATCH 010/239] Update routing.md (#14) --- en/routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/routing.md b/en/routing.md index 4ccf64f9..2ecfb840 100644 --- a/en/routing.md +++ b/en/routing.md @@ -35,7 +35,7 @@ export function createApp () { // create router instance const router = createRouter() - const app new Vue({ + const app = new Vue({ // inject router into root Vue instance router, render: h => h(App) From e122f57c1cc5624e4ac3ca699b181458f76cfce9 Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Sat, 6 May 2017 21:38:42 +0300 Subject: [PATCH 011/239] [READY TO MERGE] Russian translation (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [RU] init commit * langs.md add russian * basic.md переведён * basic.md тире и переведены примеры * universal.md переведён * structure.md переведён * routing.md переведён * data.md переведён * hydration.md переведён * bundle-renderer.md переведён * build-config.md в процессе * bundle-renderer.md исправлена ссылка * build-config.md переведён * css.md переведён * head.md переведён * cache.md переведён * streaming.md переведён * SUMMARY.md допереведён * api.md начат перевод * build-config.md доработки * bundle-rendered.md доработки * api.md допереведён * api.md исправлены опечатки * basic.md исправлены опечатки * build-config.md исправлены опечатки * bundle-renderer.md исправлены опечатки * caching.md исправлены опечатки * css.md исправлены опечатки * data.md исправлены опечатки * head.md исправлены опечатки * hydration.md исправлены опечатки * routing.md исправлены опечатки * streaming.md исправлены опечатки * structure.md исправлены опечатки * universal.md исправлены опечатки * api.md доработки к 2.3.1 * build-config.md доработки к 2.3.1 * bundle-renderer.md доработки к 2.3.1 * head.md доработки * routing.md исправлен пример * README.md переведён * Исправления очепяток * Стилистические исправления --- LANGS.md | 1 + ru/README.md | 50 ++++++++ ru/SUMMARY.md | 27 +++++ ru/api.md | 237 ++++++++++++++++++++++++++++++++++++++ ru/basic.md | 147 ++++++++++++++++++++++++ ru/build-config.md | 215 +++++++++++++++++++++++++++++++++++ ru/bundle-renderer.md | 52 +++++++++ ru/caching.md | 87 ++++++++++++++ ru/css.md | 114 +++++++++++++++++++ ru/data.md | 259 ++++++++++++++++++++++++++++++++++++++++++ ru/head.md | 90 +++++++++++++++ ru/hydration.md | 32 ++++++ ru/routing.md | 153 +++++++++++++++++++++++++ ru/streaming.md | 33 ++++++ ru/structure.md | 123 ++++++++++++++++++++ ru/universal.md | 31 +++++ 16 files changed, 1651 insertions(+) create mode 100644 ru/README.md create mode 100644 ru/SUMMARY.md create mode 100644 ru/api.md create mode 100644 ru/basic.md create mode 100644 ru/build-config.md create mode 100644 ru/bundle-renderer.md create mode 100644 ru/caching.md create mode 100644 ru/css.md create mode 100644 ru/data.md create mode 100644 ru/head.md create mode 100644 ru/hydration.md create mode 100644 ru/routing.md create mode 100644 ru/streaming.md create mode 100644 ru/structure.md create mode 100644 ru/universal.md diff --git a/LANGS.md b/LANGS.md index 25b60e8a..f379e75a 100644 --- a/LANGS.md +++ b/LANGS.md @@ -1 +1,2 @@ * [English](en/) +* [Русский](ru/) diff --git a/ru/README.md b/ru/README.md new file mode 100644 index 00000000..84d1c08d --- /dev/null +++ b/ru/README.md @@ -0,0 +1,50 @@ +# Руководство по серверному рендерингу Vue.js + +> **Примечание:** для этого руководства требуются следующие версии Vue и библиотек: +> - vue & vue-server-renderer >= 2.3.0 +> - vue-router >= 2.5.0 +> - vue-loader >= 12.0.0 & vue-style-loader >= 3.0.0 + +> Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь [немного отличается](./structure.md) (с новой опцией [runInNewContext](./api.md#runinnewcontext) установленной в `false`). Ваше существующее приложение по-прежнему будет работать, но советуем внести изменения с учётом новых рекомендаций. + +## Что такое серверный рендеринг (SSR)? + +Vue.js — это фреймворк для создания приложений, выполняемых на клиенте (client-side). По умолчанию компоненты Vue создают и манипулируют DOM в браузере. Однако, также возможно рендерить те же компоненты в HTML-строки на сервере, отправлять их в браузер, и наконец «гидрировать» статическую разметку в полностью интерактивное приложение на клиенте. + +Приложение Vue.js отрендеренное на сервере также можно считать «изоморфным» или «универсальным», в том смысле, что большая часть кода приложения **является общей для сервера и клиента**. + +## Нужен ли вам SSR? + +По сравнению с традиционным SPA (Single-Page Application), преимуществами серверного рендеринга будут: + +- Лучшее SEO, поскольку поисковые роботы будут видеть полностью отрендеренную страницу. + + Обратите внимание, что на данный момент Google and Bing могут без проблем индексировать синхронные приложения JavaScript. Ключевое слово здесь — синхронные. Если ваше приложение запускается с индикатором загрузки, а потом догружает контент через Ajax, то поисковый робот просто не будет дожидаться окончания загрузки. Это значит, что если у вас есть асинхронный контент на страницах где SEO важен, то может потребоваться серверный рендеринг. + +- Лучшие показатели времени до отображения контента (time-to-content), особенно при плохом интернете или на медленных устройствах. Для разметки, отрендеренной на сервере, не требуется дожидаться пока весь JavaScript будет загружен и выполнен, поэтому ваш пользователь увидит полностью отрендеренную страницу раньше. Как правило, это приводит к лучшему пользовательскому опыту и может быть критичным для приложений, где время до отображения контента напрямую связано с коэффициентом конверсии. + +Следует учитывать и некоторые компромиссы при использовании серверного рендеринга: + +- Ограничения при разработке. Код только для браузера может быть использован лишь в определённых хуках жизненного цикла; некоторые внешние библиотеки могут нуждаться в особой обработке, чтобы иметь возможность запускаться в приложении с серверным рендерингом. + +- Более сложные требования по настройке и развёртыванию сборки. В отличие от полностью статичного SPA, который может быть развёрнут на любом статичном файловом сервере, приложение с серверным рендерингом требует окружения, где есть возможность запустить сервер Node.js. + +- Повышенная нагрузка на стороне сервера. Рендеринг полноценного приложения в Node.js очевидно более требователен к ресурсам процессора, чем простая раздача статичных файлов, поэтому если вы ожидаете большой трафик, будьте готовы к соответствующей нагрузке на сервер и используйте стратегии кэширования. + +Прежде чем использовать серверный рендеринг для вашего приложения, задайте себе вопрос, действительно ли он вам нужен. Ответ зависит от того, насколько важно время до контента для вашего приложения. Например, если вы разрабатываете панель мониторинга для внутренних нужд, где дополнительные несколько сотен миллисекунд начальной загрузки не так важны, то серверный рендеринг будет излишеством. Однако, в тех случаях, когда время до контента критично, серверный рендеринг может позволит достичь наилучшей производительности начальной загрузки. + +## SSR vs Пререндеринг + +Если вы интересуетесь серверным рендерингом только для того, чтобы улучшить SEO на нескольких маркетинговых страницах (например, `/`, `/about`, `/contact`, и т.д.), вам скорее всего будет достаточно __пререндеринга__. Вместо того, чтобы заставлять веб-сервер компилировать HTML на лету, пререндеринг просто сгенерирует статичные HTML-файлы для указанных маршрутов на этапе сборки. Преимуществом пререндеринга будет простота реализации, кроме того этот подход позволит вам оставить фронтенд полностью статичным. + +Если вы используете Webpack, то для добавления пререндеринга достаточно установить плагин [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin). Он был тщательно протестирован с приложениями Vue, а его [создатель](https://github.com/chrisvfritz) — член основной команды разработки Vue. + +## Об этом руководстве + +Это руководство ориентировано на SPA приложения с рендерингом на сервере, используя Node.js в качестве сервера. Использование серверного рендеринга Vue совместно с другими технологиями и настройками бэкэнда являются отдельной темой и не рассматриваются в этом руководстве. + +Это руководство будет очень детальным и предполагает, что вы уже знакомы с самим Vue.js, имеете знания и опыт работы с Node.js и Webpack. Если вы предпочитаете более высокоуровневые решения, обеспечивающие работу из коробки — вам следует попробовать [Nuxt.js](http://nuxtjs.org/). Он построен на том же стеке Vue, но позволяет абстрагироваться от написания шаблонного кода, а также предоставляет некоторые дополнительные возможности, такие как генерация статичного сайта. Однако он может не подойти, если вам необходим полный контроль над структурой приложения. В любом случае, вам будет полезно прочитать это руководство, чтобы лучше понимать, как все составляющие работают вместе. + +По мере прочтения руководства, будет полезным обращаться к официальному [демо HackerNews](https://github.com/vuejs/vue-hackernews-2.0/), в котором используется большинство техник, изложенных в этом руководстве. + +Наконец, обратите внимание, что решения в этом руководстве не являются окончательными — мы решили, что они хорошо работают для нас, но это не означает, что они не могут быть улучшены. Они могут быть пересмотрены в будущем — поэтому не стесняйтесь вносить свой вклад, отправляя пулл-реквесты! diff --git a/ru/SUMMARY.md b/ru/SUMMARY.md new file mode 100644 index 00000000..5fb37d1c --- /dev/null +++ b/ru/SUMMARY.md @@ -0,0 +1,27 @@ +- [Использование](basic.md) +- [Написание универсального кода](universal.md) +- [Структура исходного кода](structure.md) +- [Маршрутизация и разделение кода](routing.md) +- [Предзагрузка данных и состояния](data.md) +- [Гидратация клиентской части](hydration.md) +- [Представляем Bundle Renderer](bundle-renderer.md) +- [Конфигурация сборки](build-config.md) +- [Управление CSS](css.md) +- [Управление заголовочными тегами (head)](head.md) +- [Кэширование](caching.md) +- [Стриминг](streaming.md) +- [Справочник API](api.md) + - [createRenderer](api.md#createrendereroptions) + - [createBundleRenderer](api.md#createbundlerendererbundle-options) + - [Класс: Renderer](api.md#class-renderer) + - [Класс: BundleRenderer](api.md#class-bundlerenderer) + - [Опции рендерера](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 плагины](api.md#webpack-plugins) diff --git a/ru/api.md b/ru/api.md new file mode 100644 index 00000000..82aea676 --- /dev/null +++ b/ru/api.md @@ -0,0 +1,237 @@ +# Справочник API + +## `createRenderer([options])` + +Создаёт экземпляр [`Renderer`](#class-renderer) с (опциональными) [настройками](#renderer-options). + +``` js +const { createRenderer } = require('vue-server-renderer') +const renderer = createRenderer({ ... }) +``` + +## `createBundleRenderer(bundle[, options])` + +Создаёт экземпляр [`BundleRenderer`](#class-bundlerenderer) с сборкой сервера и (опциональными) [настройками](#renderer-options). + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer(serverBundle, { ... }) +``` + +Аргумент `serverBundle` может быть одним из следующих: + +- Абсолютный путь к созданному файлу сборки (`.js` или `.json`). Должен начинаться с `/`, чтобы трактоваться как путь к файлу. + +- Объект сборки, сгенерированный Webpack + `vue-server-renderer/server-plugin`. + +- Строка с кодом JavaScript (не рекомендуется). + +См. также [Представляем Bundle Renderer](./bundle-renderer.md) и [Конфигурация сборки](./build-config.md) для подробностей. + +## `Класс: Renderer` + +- #### `renderer.renderToString(vm[, context], callback)` + + Рендерит экземпляр Vue в строку. Объект контекста опционален. Коллбэк является обычным для Node.js коллбэком, где первый аргумент является ошибкой, а второй аргумент — отрендеренной строкой. + +- #### `renderer.renderToStream(vm[, context])` + + Рендерит экземпляр Vue в поток (stream) Node.js. Объект контекста опционален. См. также [Стриминг](./streaming.md) для подробностей. + +## `Класс: BundleRenderer` + +- #### `bundleRenderer.renderToString([context, ]callback)` + + Рендерит сборку в строку. Объект контекста опционален. Коллбэк является обычным для Node.js коллбэком, где первый аргумент является ошибкой, а второй аргумент — отрендеренной строкой. + +- #### `bundleRenderer.renderToStream([context])` + + Рендерит сборку в поток (stream) Node.js. Объект контекста опционален. См. также [Стриминг](./streaming.md) для подробностей. + +## Настройки рендерера + +- #### `template` + + Предоставляет шаблон для всей HTML-страницы. Шаблон должен содержать комментарий ``, который определяет место подстановки отрендеренного контента приложения. + + Шаблон также поддерживает базовые интерполяции с использованием контекста рендера: + + - Используйте двойные фигурные скобки для интерполяции экранированного HTML; + - Используйте тройные фигурные скобки для интерполяции сырого HTML. + + Шаблон автоматически внедряет соответствующий контент, когда определённые свойства найдены в контексте рендера: + + - `context.head`: (string) любая разметка для head, которая должна быть вставлена в заголовочный тег страницы. + + - `context.styles`: (string) любой встроенный CSS, который должен быть вставлен в заголовочный тег страницы. Обратите внимание, что это свойство будет автоматически заполнено при использовании `vue-loader` + `vue-style-loader` для CSS компонента. + + - `context.state`: (Object) начальное состояние хранилища Vuex, которое должно быть внедрено в страницу как `window.__INITIAL_STATE__`. Внедряемый JSON автоматически обрабатывается с помощью [serialize-javascript](https://github.com/yahoo/serialize-javascript) для предотвращения XSS-уязвимостей. + + Кроме того, когда предоставлен `clientManifest`, шаблон автоматически внедряет следующее: + + - JavaScript и CSS ресурсы для клиентской части, необходимые для рендеринга (с асинхронными фрагментами добавляемыми автоматически); + - Оптимальные ресурсы `` для отображаемой страницы. + + Вы можете отключить все автоматические внедрения передав `inject: false` в рендерер. + + См. также: + + - [Использование шаблона страниц](./basic.md#using-a-page-template) + - [Внедрение ресурсов вручную](./build-config.md#manual-asset-injection) + +- #### `clientManifest` + + - 2.3.0+ + - используется только в `createBundleRenderer` + + Предоставляет объект манифеста клиентской сборки, сгенерированный `vue-server-renderer/server-plugin`. Клиентский манифест предоставляет для рендерера сборки необходимую информацию для автоматического внедрения ресурсов в шаблон HTML. Подробнее в разделе [Генерация `clientManifest`](./build-config.md#generating-clientmanifest). + +- #### `inject` + + - 2.3.0+ + + Контролирует, выполнять ли автоматические внедрения при использовании `template`. По умолчанию `true`. + + См. также: [Внедрение ресурсов вручную](./build-config.md#manual-asset-injection). + +- #### `shouldPreload` + + - 2.3.0+ + + Функция, определяющая какие файлы должны иметь `` в генерируемых ресурсах. + + По умолчанию, только JavaScript и CSS файлы будут предзагружаться, так как они абсолютно необходимы для загрузки приложения. + + Для других типов ресурсов, таких как изображения или шрифты, предзагрузка может привести к ненужному увеличению объёмов передаваемой информации и даже к ухудшению производительности, поэтому список файлов, которые нужно предзагружать, зависит от ситуации. Вы можете точно контролировать, что требует предзагрузки, используя опцию `shouldPreload`: + + ``` js + const renderer = createBundleRenderer(bundle, { + template, + clientManifest, + shouldPreload: (file, type) => { + // тип определяется на основе расширения файла. + // https://fetch.spec.whatwg.org/#concept-request-destination + if (type === 'script' || type === 'style') { + return true + } + if (type === 'font') { + // предзагружать только woff2 шрифты + return /\.woff2$/.test(file) + } + if (type === 'image') { + // предзагружать только важные изображения + return file === 'hero.jpg' + } + } + }) + ``` + +- #### `runInNewContext` + + - 2.3.0+ + - используется только в `createBundleRenderer` + - Ожидается: `boolean | 'once'` (`'once'` поддерживается только с версии 2.3.1+) + + По умолчанию, рендерер сборки будет создавать новый контекст V8 для каждого рендеринга и повторно исполнять всю сборку. Это имеет некоторые преимущества — например, код приложения изолирован от процесса сервера и не нужно беспокоиться [о проблеме «синглетона с состоянием»](./structure.md#avoid-stateful-singletons), которая упоминалась ранее в руководстве. Однако этот режим требует значительных затрат производительности, поскольку повторное выполнение сборки обходится дорого, особенно когда приложение становится большим. + + По умолчанию эта опция имеет значение `true` для обеспечения обратной совместимости, но рекомендуется использовать `runInNewContext: false` или `runInNewContext: 'once'` всегда, когда это возможно. + + > В версии 2.3.0 у этой опции есть ошибка, когда при `runInNewContext: false` сборка всё ещё исполнялась в отдельном глобальном контексте. Информация далее предполагает использование версии 2.3.1+. + + С опцией `runInNewContext: false`, код сборки будет выполняться в том же контексте `global`, что и серверный процесс, поэтому нужно быть осторожным с кодом, который изменяет `global` в вашем приложении. + + С опцией `runInNewContext: 'once'` (добавлено в версии 2.3.1+), сборка выполняется в отдельном контексте `global`, но только один раз при запуске. Это обеспечивает лучшую изоляцию кода приложения поскольку предотвращает случайно загрязнение объекта `global` серверного процесса. Предостережения заключаются в следующем: + + 1. Зависимости, которые изменяют `global` (например, полифиллы) не должны быть объявлены внешними зависимостями в этом режиме; + 2. Значения, возвращаемые при выполнении сборки будут использовать разные глобальные конструкторы, например, ошибка внутри сборки не будет экземпляром `Error` в серверном процессе. + + См. также: [Структура исходного кода](./structure.md) + +- #### `basedir` + + - 2.2.0+ + - используется только в `createBundleRenderer` + + Указание пути базового каталога для серверной сборки для разрешения зависимостей из `node_modules` в нём. Это необходимо только в том случае, если сгенерированный файл сборки располагается в другом месте, в отличии от используемых NPM-зависимостей, или когда ваш `vue-server-renderer` подключен NPM-ссылкой в вашем текущем проекте. + +- #### `cache` + + Реализация [кэширования на уровне компонентов](./caching.md#component-level-caching). Объект кэша должен реализовать следующий интерфейс (соответствуя нотациям Flow): + + ``` js + type RenderCache = { + get: (key: string, cb?: Function) => string | void; + set: (key: string, val: string) => void; + has?: (key: string, cb?: Function) => boolean | void; + }; + ``` + + Для обычного использования достаточно передать [lru-cache](https://github.com/isaacs/node-lru-cache): + + ``` js + const LRU = require('lru-cache') + + const renderer = createRenderer({ + cache: LRU({ + max: 10000 + }) + }) + ``` + + Обратите внимание, что объект кэша по крайне мере должен реализовывать `get` и `set`. Кроме того, `get` и `has` опционально могут быть асинхронными, если они принимают второй аргумент как коллбэк. Это позволяет кэшу использовать асинхронные API, например для Redis: + + ``` js + const renderer = createRenderer({ + cache: { + get: (key, cb) => { + redisClient.get(key, (err, res) => { + // обработка ошибок, если таковые будут + cb(res) + }) + }, + set: (key, val) => { + redisClient.set(key, val) + } + } + }) + ``` + +- #### `directives` + + Позволяет предоставить серверную реализацию для ваших пользовательских директив: + + ``` js + const renderer = createRenderer({ + directives: { + example (vnode, directiveMeta) { + // преобразуем vnode на основе метаданных привязанных к директиве + } + } + }) + ``` + + Например, можете посмотреть [серверную реализацию для директивы `v-show`](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js). + +## Webpack плагины + +Webpack плагины предоставляются как отдельные файлы, которые должны быть подключены напрямую: + +``` js +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +``` + +По умолчанию генерируются файлы: + +- `vue-ssr-server-bundle.json` для серверной сборки; +- `vue-ssr-client-manifest.json` для клиентской сборки. + +Имена файлов могут быть изменены при создании экземпляров плагина: + +``` js +const plugin = new VueSSRServerPlugin({ + filename: 'my-server-bundle.json' +}) +``` + +См. также [Конфигурация сборки](./build-config.md) для подробной информации. diff --git a/ru/basic.md b/ru/basic.md new file mode 100644 index 00000000..cac87f2e --- /dev/null +++ b/ru/basic.md @@ -0,0 +1,147 @@ +# Использование + +## Установка + +``` bash +npm install vue vue-server-renderer --save +``` + +В руководстве мы будем использовать NPM, но вы свободно можете использовать и [Yarn](https://yarnpkg.com/en/). + +#### Примечания + +- Рекомендуется использовать Node.js версии 6+. +- `vue-server-renderer` и `vue` должны иметь одинаковые версии. +- `vue-server-renderer` зависит от некоторых нативных модулей Node.js и поэтому может использоваться только в Node.js. Возможно в будущем мы предоставим более простую сборку, которая сможет быть запущена в других средах исполнения JavaScript. + +## Рендеринг экземпляра Vue + +``` js +// Шаг 1: Создаём экземпляр Vue +const Vue = require('vue') +const app = new Vue({ + template: `
Hello World
` +}) + +// Шаг 2: Создаём рендерер +const renderer = require('vue-server-renderer').createRenderer() + +// Шаг 3: Рендерим экземпляр Vue в HTML +renderer.renderToString(app, (err, html) => { + if (err) throw err + console.log(html) + // =>
hello world
+}) +``` + +## Интеграция с сервером + +Это достаточно просто когда мы используем сервер на Node.js, например [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: `
Вы открыли URL: {{ url }}
` + }) + + renderer.renderToString(app, (err, html) => { + if (err) { + res.status(500).end('Внутренняя ошибка сервера') + return + } + res.end(` + + + Привет + ${html} + + `) + }) +}) + +server.listen(8080) +``` + +## Использование шаблона страниц + +Когда вы рендерите приложение Vue, рендерер генерирует только разметку приложения. В примере выше нам потребовалось обернуть вывод дополнительным кодом для создания обычной HTML-страницы. + +Вы можете упростить это, предоставив шаблон страницы при создании рендерера. Чаще всего нам требуется расположить шаблон в отдельном файле, например `index.template.html`: + +``` html + + + Привет + + + + +``` + +Обратите внимание на комментарий `` — сюда будет подставлена разметка вашего приложения. + +Теперь мы можем прочитать этот файл и передать его в рендерер Vue: + +``` js +const renderer = createRenderer({ + template: require('fs').readFileSync('./index.template.html', 'utf-8') +}) + +renderer.renderToString(app, (err, html) => { + console.log(html) // будет выведен код всей страницы, с подставленным кодом приложения. +}) +``` + +### Интерполяции в шаблоне + +Шаблон поддерживает простые интерполяции. Например: + +``` html + + + {{ title }} + {{{ meta }}} + + + + + +``` + +Мы можем предоставить необходимые данные для интерполяции, передав объект контекста для рендера вторым аргументом в `renderToString`: + +``` js +const context = { + title: 'привет', + meta: ` + + + ` +} + +renderer.renderToString(app, context, (err, html) => { + // заголовок страницы будет "привет" + // meta-теги также будут подставлены в код страницы +}) +``` + +Объект `context` может также использоваться совместно с экземпляром Vue приложения, что разрешает компонентам динамически регистрировать данные для интерполяции в шаблоне. + +Кроме того, шаблон поддерживает некоторые продвинутые функции: + +- Автоматическую подстановку критически важного CSS при использовании `*.vue` компонентов; +- Автоматическую подстановку ссылок и подсказок для ресурсов (preload / prefetch) при использовании `clientManifest`; +- Автоматическую подстановку и предотвращение XSS при встраивании Vuex-состояния для гидратации на стороне клиента. + +Мы обсудим это дальше, когда будем разбирать все связанные концепции. diff --git a/ru/build-config.md b/ru/build-config.md new file mode 100644 index 00000000..4f8bbcec --- /dev/null +++ b/ru/build-config.md @@ -0,0 +1,215 @@ +# Конфигурация сборки + +Мы предполагаем, что вы уже знаете как настраивать Webpack для клиентской части проектов. Конфигурация для проекта SSR будет во многом схожей, но мы предлагаем разбивать конфигурацию на три файла: *base*, *client* и *server*. Базовая конфигурация (base) содержит конфигурацию, совместно используемую для обоих окружений, такие как пути вывода, псевдонимы и загрузчики. Конфигурация сервера (server) и конфигурация клиента (client) просто расширяют базовую конфигурацию, используя [webpack-merge](https://github.com/survivejs/webpack-merge). + +## Конфигурация серверной части + +Конфигурация серверной части предназначена для создания серверной сборки, которая будет передана в `createBundleRenderer`. Это должно выглядеть так: + +``` js +const merge = require('webpack-merge') +const nodeExternals = require('webpack-node-externals') +const baseConfig = require('./webpack.base.config.js') +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') + +module.exports = merge(baseConfig, { + // Укажите точку входа серверной части вашего приложения + entry: '/path/to/entry-server.js', + + // Это позволяет Webpack обрабатывать динамические импорты в Node-стиле, + // а также сообщает `vue-loader` генерировать серверно-ориентированный код + // при компиляции компонентов Vue. + target: 'node', + + // Для поддержки source map в bundle renderer + devtool: 'source-map', + + // Это сообщает что в серверной сборке следует использовать экспорты в стиле Node + output: { + libraryTarget: 'commonjs2' + }, + + // https://webpack.js.org/configuration/externals/#function + // https://github.com/liady/webpack-node-externals + // Внешние зависимости приложения. Это значительно ускоряет процесс + // сборки серверной части и уменьшает размер итогового файла сборки. + externals: nodeExternals({ + // не выделяйте зависимости, которые должны обрабатываться Webpack. + // здесь вы можете добавить больше типов файлов, например сырые *.vue файлы + // нужно также указывать белый список зависимостей изменяющих `global` (например, полифиллы) + whitelist: /\.css$/ + }), + + // Этот плагин преобразует весь результат серверной сборки + // в один JSON-файл. Имя по умолчанию будет + // `vue-ssr-server-bundle.json` + plugins: [ + new VueSSRServerPlugin() + ] +}) +``` + +После создания `vue-ssr-server-bundle.json` просто передайте путь к файлу в `createBundleRenderer`: + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { + // ...другие настройки рендерера +}) +``` + +В качестве альтернативы, вы также можете передать сборку как объект в `createBundleRenderer`. Это полезно для горячей перезагрузки во время разработки — см. демо HackerNews для [примера настройки](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js). + +### Ограничения externals + +Обратите внимание, что в параметре `externals` мы указываем белый список CSS файлов. Это связано с тем, что CSS, импортированный из зависимостей всё равно должен быть обработан Webpack. Если вы импортируете любые другие типы файлов, которые также полагаются на Webpack (например, `*.vue`, `*.sass`), вы должны их также добавить в белый список. + +Если вы используете `runInNewContext: 'once'` или `runInNewContext: true`, вам также требуется добавить в белый список являются полифиллы, которые изменяют `global`, например `babel-polyfill`. Это связано с тем, что при использовании режима нового контекста, **код внутри серверной сборки имеет свой собственный объект `global`**. Поскольку это не будет нужно на сервере при использовании Node 7.6+, на самом деле проще просто импортировать его в клиентской точке входа. + +## Конфигурация клиентской части + +Конфигурация клиентской части может оставаться практически такой же, как и базовой. Очевидно, вам нужно указать `entry` на файл входной точки клиентской части. Кроме того, если вы используете `CommonsChunkPlugin`, убедитесь, что используете его только в конфигурации клиентской части, потому что для серверной сборки требуется одна точка входа. + +### Генерация `clientManifest` + +> требуется версия 2.3.0+ + +Помимо серверной сборки, мы также можем сгенерировать манифест сборки. С помощью манифеста клиентской части и серверной сборки, у рендерера появится информация о серверной *и* клиентской сборке, поэтому он может автоматически внедрять [директивы preload/prefetch](https://css-tricks.com/prefetching-preloading-prebrowsing/) в ссылки на CSS / теги script в отображаемом HTML. + +Выгода тут двойная: + +1. Он может заменить `html-webpack-plugin` для внедрения правильных URL-адресов ресурсов, когда в генерируемых именах файлов есть хэши. + +2. При рендеринге сборки, которая использует возможности разделения кода Webpack, мы можем гарантировать, что оптимальные части были предзагружены и предзаполнены, а также интеллектуально внедрять теги ` + + + + +` +``` + +### Внедрение ресурсов вручную + +По умолчанию, внедрение ресурсов выполняется автоматически при использовании опции `template` для рендера. Но иногда вам может понадобиться больше контроля над тем, как ресурсы должны внедряться в шаблон, или, возможно, вы не используете шаблон вообще. В таком случае вы можете передать опцию `inject: false` при создании рендерера и производить внедрение ресурсов вручную. + +В коллбэке `renderToString` объект `context`, который вы передали, предоставляет следующие методы: + +- `context.renderStyles()` + + Возвращает встроенные теги ``태그가 반환됩니다. 자세한 내용은 [CSS 관리](./css.md)를 참조하십시오. + +`clientManifest`가 제공되면 반환되는 문자열에는 webpack에서 생성한 CSS파일 (예: `extract-text-webpack-plugin` 또는 imported with `file-loader`)에 대한 `` 태그가 포함됩니다. + +- `context.renderState(options?: Object)` + +이 메소드는 `context.state`를 직렬화하고 스테이트를 `window.__INITIAL_STATE__`로 포함하는 인라인 스크립트를 리턴합니다. + +컨텍스트 스테이트 키와 윈도우 스테이트 키는 옵션 객체를 전달하여 사용자 정의할 수 있습니다. + +```js + context.renderState({ + contextKey: 'myCustomState', + windowKey: '__MY_STATE__' + }) + // -> +``` + +- `context.renderScripts()` + - `clientManifest`를 필요로 합니다. + +이 메소드는 클라이언트 애플리케이션이 시작하는데 필요한 ``태그를 반환합니다. 애플리케이션 코드에서 비동기 코드 분할을 사용하는 경우 이 메소드는 포함할 올바른 비동기 청크를 지능적으로 유추합니다. + +- `context.renderResourceHints()` + - `clientManifest`를 필요로 합니다. + +이 메소드는 현재 렌더링된 페이지에 필요한 `` 리소스 힌트를 반환합니다. 기본적으로 다음과 같습니다. + +- 페이지에 필요한 JavaScript 및 CSS 파일을 미리 로드 +- 나중에 필요할 수 있는 비동기 JavaScript 청크 프리페치 + +미리 로드된 파일은 [`shouldPreload`](./api.md#shouldpreload)옵션을 사용해 추가로 사용자 정의할 수 있습니다. + +- `context.getPreloadFiles()` + - `clientManifest`를 필요로 합니다. + +이 메소드는 문자열을 반환하지 않고 대신 미리 로드해야 할 에셋을 나타내는 파일 객체의 배열을 반환합니다. 이는 프로그래밍 방식으로 HTTP/2 서버 푸시를 하는데 사용할 수 있습니다. + +`createBundleRenderer`에 전달된 `template`은 `context`를 사용하여 보간되므로 템플릿안에서 이러한 메소드를 사용할 수 있습니다.(`inject: false` 옵션과 함께) + +```html + + + + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + + + + {{{ renderState() }}} + {{{ renderScripts() }}} + + +``` + +`template`을 사용하지 않으면 문자열을 직접 연결할 수 있습니다. From a50ba2d54661d7252cd799e700424a3a2b3b07b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ChangJoo=20Park=28=EB=B0=95=EC=B0=BD=EC=A3=BC=29?= Date: Fri, 9 Jun 2017 01:59:33 +0900 Subject: [PATCH 037/239] Translate basic.md via GitLocalize --- ko/basic.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 ko/basic.md diff --git a/ko/basic.md b/ko/basic.md new file mode 100644 index 00000000..a24074f0 --- /dev/null +++ b/ko/basic.md @@ -0,0 +1,144 @@ +# 기본 사용 방법 + +## 설치 + +```bash +npm install vue vue-server-renderer --save +``` + +이 가이드에서는 NPM을 사용하지만 [Yarn](https://yarnpkg.com/en/)을 사용하여도 전혀 문제가 되지 않습니다. + +#### 참고 사항 + +- Node.js 6 이상을 권장합니다. +- `vue-server-renderer` 와`vue`는 반드시 서로 맞는 버전을 사용해야합니다. +- `vue-server-renderer`는 일부 Node.js 네이티브 모듈을 사용하므로 Node.js에서만 사용할 수 있습니다. 앞으로 다른 JavaScript 런타임에서 실행할 수 있는 보다 간단한 방법을 제공할 예정입니다. + +## Vue 인스턴스 렌더링 + +```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
+}) +``` + +## 서버와 통합하는 방법 + +[Express](https://expressjs.com/)와 같이 Node.js 서버에서 사용하면 매우 간단합니다. + +```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) +``` + +## 페이지 템플릿 이용하기 + +Vue 앱을 렌더링할 때 렌더러는 앱의 마크업만 생성합니다. 이 예제에서 추가 HTML 페이지 쉘로 출력을 레핑해야합니다. + +이를 간단히 하기 위해 렌더러를 만들 때 페이지 템플릿을 직접 제공할 수 있습니다. 대부분의 경우 페이지 템플릿을 자체 파일에 저장합니다. (예: `index.template.html`) + +```html + + + Hello + + + + +``` + +Notice the `` comment -- this is where your app's markup will be injected. + +그 다음 파일을 읽고 Vue 렌더러로 전달합니다. + +```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. +}) +``` + +### 템플릿 인터폴레이션 + +템플릿은 간단한 인터폴레이션(보간)도 지원합니다. 다음 템플릿을 확인하세요. + +```html + + + + {{ title }} + + {{{ meta }}} + + + + + +``` + +`renderToString`의 두번째 전달인자로 "render context object" 를 전달하여 인터폴레이션 데이터를 제공할 수 있습니다. + +```js +const context = { + title: 'hello', + meta: ` + + + ` +} +renderer.renderToString(app, context, (err, html) => { + // page title will be "Hello" + // with meta tags injected +}) +``` + +`컨텍스트`객체는 Vue 앱 인스턴스와 공유할 수 있으므로 컴포넌트가 템플릿 인터폴레이션을 위해 데이터를 동적으로 등록할 수 있습니다. + +또한 템플릿은 다음과 같은 몇 가지 고급 기능을 지원합니다. + +- `*.vue` 컴포넌트를 사용할 때 CSS를 자동으로 주입합니다. +- `clientManifest`를 사용할 때 에셋 링크 및 리소스에 관련한 힌트를 자동으로 주입합니다. +- 클라이언트 측 하이드레이션을 위한 Vuex 스테이트 포함시 자동 주입 및 XSS를 예방을 지원합니다. + +나중에 이 가이드에서 관련 개념을 소개할 때 자세히 다룰 것 입니다. From c617c9f05fcbc3e01d5ebaca26ffb15f2418075b Mon Sep 17 00:00:00 2001 From: Ryo Chikazawa Date: Fri, 9 Jun 2017 01:59:34 +0900 Subject: [PATCH 038/239] Translate basic.md via GitLocalize --- ko/basic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ko/basic.md b/ko/basic.md index a24074f0..51890d42 100644 --- a/ko/basic.md +++ b/ko/basic.md @@ -86,7 +86,7 @@ Vue 앱을 렌더링할 때 렌더러는 앱의 마크업만 생성합니다. ``` -Notice the `` comment -- this is where your app's markup will be injected. +`` 주석을 주목하세요. 이것은 앱의 마크업이 삽입되는 곳 입니다. 그 다음 파일을 읽고 Vue 렌더러로 전달합니다. From 784752da35e5128a6d0ea4df51338be62c1ccc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ChangJoo=20Park=28=EB=B0=95=EC=B0=BD=EC=A3=BC=29?= Date: Fri, 9 Jun 2017 01:59:39 +0900 Subject: [PATCH 039/239] Translate api.md via GitLocalize --- ko/api.md | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 ko/api.md diff --git a/ko/api.md b/ko/api.md new file mode 100644 index 00000000..e04d9496 --- /dev/null +++ b/ko/api.md @@ -0,0 +1,233 @@ +# API 레퍼런스 + +## `createRenderer([options])` + +[options](#renderer-options)와 함께 [`Renderer`](#class-renderer)인스턴스를 만듭니다. + +```js +const { createRenderer } = require('vue-server-renderer') +const renderer = createRenderer({ ... }) +``` + +## `createBundleRenderer(bundle[, options])` + +서버 번들과 [options](#renderer-options)을 이용해 [`BundleRenderer`](#class-bundlerenderer)인스턴스를 만듭니다. + +```js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer(serverBundle, { ... }) +``` + +`serverBundle` 전달인자는 다음 중 하나입니다. + +- 절대경로를 가지는 번들파일(`.js` or `.json`). `/`로 시작해야 파일 경로로 판단합니다. +- `vue-server-renderer/server-plugin`로 생성한 번들 객체입니다. +- JavaScript 코드 문자열 (권장하지 않습니다.) + +[Introducing the Server Bundle](./bundle-renderer.md)와 [를 참조하세요.](./build-config.md) + +## `Class: Renderer` + +- #### `renderer.renderToString(vm[, context], callback)` + +Vue 인스턴스를 문자열로 렌더링합니다. 컨텍스트 객체는 옵션입니다. 콜백은 일반적인 Node.js 스타일이며 첫번째 전달인자는 오류, 두번째 전달인자는 렌더링된 문자열 입니다. + +- #### `renderer.renderToStream(vm[, context])` + +Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객체는 옵션입니다. 자세한 내용은 [스트리밍](./streaming.md)을 참조하세요. + +## `Class: BundleRenderer` + +- #### `bundleRenderer.renderToString([context, ]callback)` + +번들을 문자열로 렌더링합니다. 컨텍스트 객체는 옵션입니다. 콜백은 일반적인 Node.js 스타일이며 첫번째 전달인자는 오류, 두번째 전달인자는 렌더링된 문자열 입니다. + +- #### `bundleRenderer.renderToStream([context])` + +Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객체는 옵션입니다. 자세한 내용은 [스트리밍](./streaming.md)을 참조하세요. + +## 렌더러 옵션 + +- #### `template` + +전체 페이지 HTML에 대한 템플릿을 제공하십시오. 템플릿에는 렌더링된 앱 컨텐츠의 플레이스홀더 역할을 하는 주석 ``이 있어야 합니다. + +템플릿은 렌더링 컨텍스트를 사용하여 기본 인터폴레이션을 지원합니다. + +- 이중 mustache를 이용해 HTML 이스케이프 인터폴레이션을 합니다. +- 삼중 mustache를 이용해 비 HTML 이스케이프 인터폴레이션을 합니다. + +템플릿은 렌더링 컨텍스트에서 특정 데이터가 발견되면 적절한 컨텐츠를 자동으로 주입합니다. + +- `context.head`: (string) 페이지 head에 삽입되어야하는 마크업 +- `context.styles`: (string) 페이지 head에 삽입되어야하는 모든 인라인 CSS. 컴포넌트 CSS에 `vue-loader` + `vue-style-loader`를 사용하는 경우 이 속성이 자동으로 채워집니다. +- `context.state`: (Object) `window.__INITIAL_STATE__`에서 반드시 인라인되어야하는 초기 Vuex 스토어 스테이트 인라인된 JSON은 XSS를 방지하기 위해 [serialize-javascript](https://github.com/yahoo/serialize-javascript)를 사용해 자동으로 삭제합니다. + +`clientManifest`이 제공되면 템플릿은 자동으로 아래 내용을 주입합니다. + +- 렌더링에 필요한 클라이언트 측 JavaScript 및 CSS 에셋 (비동기 청크가 자동으로 유추됨) +- 최적의 ` 렌더링된 페`이지에 대한 리소스 힌트 + +렌더러에 `inject: false`를 전달하여 모든 자동 주입을 비활성화 할 수 있습니다. + +참고하세요 + +- [페이지 템플릿 사용](./basic.md#using-a-page-template) +- [수동 에셋 주입](./build-config.md#manual-asset-injection) + - #### `clientManifest` +- 2.3.0+ + +`vue-server-renderer/client-plugin`에 의해 생성된 클라이언트 매니페스트 객체를 제공합니다. 클라이언트 매니페스트는 번들 렌더러에게 HTML 템플릿으로 자동 에셋 주입을 위한 적절한 정보를 제공합니다. 자세한 내용은[Generating clientManifest](./build-config.md#generating-clientmanifest)을 참조하세요. + +- +#### `inject` + + - 2.3.0+ + +`template`을 사용할 때 자동 주입 여부를 선택합니다. 기본값은 `true`입니다. + +[Manual Asset Injection](./build-config.md#manual-asset-injection)을 참조하세요 + +- +#### `shouldPreload` + + - 2.3.0+ + +` 리소스 힌트`가 생성되어야하는 파일을 제어하는 함수입니다. + +기본적으로 애플리케이션 시작에 절대적으로 필요한 JavaScript 및 CSS파일만 미리 로드합니다. + +이미지 또는 글꼴과 같은 다른 유형의 에셋의 경우 프리로드를 너무 많이하면 대역폭을 낭비하고 성능을 저하시키므로 프리로드할 대상은 시나리오에 따라 달라야합니다. `shouldPreload`옵션을 사용하여 프리로드할 항목을 정확하게 선택해야합니다. + +```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+ + - `createBundleRenderer`에서만 사용할 수 있습니다. + - 예상: `boolean | 'once'` (`'once'` 2.3.1+ 에서만 지원함) + +기본적으로 각 렌더에 대해 번들 렌더러는 새로운 V8 컨텍스트를 만들고 전체 번들을 다시 실행합니다. 이는 몇가지 장점을 가집니다. 예를 들어 애플리케이션 코드는 서버 프로세스와 분리되어 있으며 문서에 언급된 [stateful singleton problem](./structure.md#avoid-stateful-singletons)에 대해 걱정할 필요가 없습니다. 그러나 번들을 다시 실행하는 것은 앱이 커지면 비용이 많이 들기 때문에 이 모드는 상당한 성능 비용을 발생시킵니다. + +이 옵션은 하위 호환성을 위해 `true`가 기본값이지만 가능할 때마다 `runInNewContext: false` 또는 `runInNewContext: 'once'`를 사용하는 것이 좋습니다. + +> 2.3.0에서 이 옵션은 `runInNewContext: false`가 별ㄷ로의 전역 컨텍스트를 사용하여 번들을 실행하는 버그가 있습니다. 2.3.1버전 이후 버전을 사용한다고 가정합니다. + +`runInNewContext: false`를 사용하면 번들 코드가 서버 프로세스와 동일한 `global` 컨텍스트에서 실행되므로 애플리케이션 코드에서 `global`을 수정하는 코드를 주의해야 합니다. + +`runInNewContext: 'once'`(2.3.1+)를 사용하면 번들은 별도의 `global` 컨텍스트로 평가되지만 시작할 때 한번뿐입니다. 번들이 실수로 서버 프로세스의 `global` 객체를 오염시키는 것을 방지하므로 더 안전한 코드 관리를 할 수 있습니다. 주의사항은 다음과 같습니다. + +1. 이 모드에서는 `global`(예: 폴리필)을 수정하는 종속성을 외부에 둘 수 없습니다. +2. 번들 실행에서 반환된 값은 다른 전역 생성자를 사용합니다. 번들 내부에서 발견된 오류는 서버 프로세스에서 `Error` 인스턴스가 되지 않습니다. + +[Source Code Structure](./structure.md)를 참조하세요 + +- +#### `basedir` + + - 2.2.0+ + - `createBundleRenderer`에서만 사용할 수 있습니다. + +`node_modules` 종속성 처리를 위해 서버 번들의 기본 디렉토리를 명시적으로 선언해야합니다. 생성된 번들 파일이 외부화된 NPM 종속성이 설치되어있거나 `vue-server-renderer`가 npm으로 연결된 다른 위치에 있는 경우에만 필요합니다. + +- #### `cache` + +[컴포넌트 캐시](./caching.md#component-level-caching)를 제공합니다. 캐시 객체는 Flow 표기법을 사용하여 다음 인터페이스를 구현해야합니다. + +```js + type RenderCache = { + get: (key: string, cb?: Function) => string | void; + set: (key: string, val: string) => void; + has?: (key: string, cb?: Function) => boolean | void; + }; +``` + +[lru-cache](https://github.com/isaacs/node-lru-cache):일반적으로 [lru-cache](https://github.com/isaacs/node-lru-cache)를 전달하여 사용합니다. + +```js + const LRU = require('lru-cache') + const renderer = createRenderer({ + cache: LRU({ + max: 10000 + }) + }) +``` + +캐시 객체는 최소한 `get`과 `set`을 구현해야합니다. 또한 두번째 전달인자를 콜백으로 허용하면 `get`과 `has`는 선택적으로 비동기화 할수 있습니다. 이렇게하면 캐시에서 비동기 API를 사용할 수 있습니다. 예: redis 클라이언트 + +```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` + +사용자 정의 디렉티브에 대한 서버측 구현을 제공할 수 있습니다. + +```js + const renderer = createRenderer({ + directives: { + example (vnode, directiveMeta) { + // transform vnode based on directive binding metadata + } + } + }) +``` + +[`v-show`의 서버측 구현](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js)을 확인하세요 + +## webpack 플러그인 + +webpack 플러그인은 독립실행형으로 제공되므로 직접 require 해야합니다. + +```js +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +``` + +생성되는 기본 파일입니다. + +- 서버 플러그인을 위한 `vue-ssr-server-bundle.json` +- 클라이언트 플러그인을 위한 `vue-ssr-client-manifest.json` + +파일 이름은 플러그인 인스턴스를 생성할 때 사용자 정의할 수 있습니다. + +```js +const plugin = new VueSSRServerPlugin({ + filename: 'my-server-bundle.json' +}) +``` + +[빌드 설정](./build-config.md)을 참조하세요. From dd70a00e4496caa414820055f43e370367fd61f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ChangJoo=20Park=28=EB=B0=95=EC=B0=BD=EC=A3=BC=29?= Date: Tue, 13 Jun 2017 15:45:41 +0900 Subject: [PATCH 040/239] Translate structure.md via GitLocalize (#54) --- ko/structure.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 ko/structure.md diff --git a/ko/structure.md b/ko/structure.md new file mode 100644 index 00000000..276ad176 --- /dev/null +++ b/ko/structure.md @@ -0,0 +1,114 @@ +# 소스 코드 구조 + +## 상태를 보존하는 싱글톤을 피하세요 + +클라이언트 전용 코드를 작성할 때 코드가 항상 새로운 컨텍스트에서 구동된다는 것에 익숙할 것입니다. 그러나 Node.js 서버는 오랫동안 실행되는 프로세스입니다. 프로세스에 코드가 필요한 경우 한번 계산되어 메모리에 남아있게 됩니다. 즉, 싱글톤 객체를 만들면 들어오는 모든 요청간에 공유될 수 있습니다. + +기본 예제에서 보듯이, 각 요청에 따라 **새로운 루트 Vue인스턴스를 생성**합니다. 이는 각 사용자가 브라우저에서 앱의 새로운 인스턴스를 사용하는 것과 유사합니다. 서로 다른 요청에서 공유된 인스턴스를 사용하면 상호 요청 상태 오염을 일으킬 수 있습니다. + +따라서 앱 인스턴스를 직접 생성하는 대신 반복적으로 실행할 수 있는 팩토리 함수를 노출하고 각 요청에 따라 새로운 앱 인스턴스를 만들어야 합니다. + +```js +// app.js +const Vue = require('vue') +module.exports = function createApp (context) { + return new Vue({ + data: { + url: context.url + }, + template: `
방문한 URL은 : {{ url }}
` + }) +} +``` + +이제 서버 측 코드는 아래와 같이 변경합니다. + +```js +// server.js +const createApp = require('./app') +server.get('*', (req, res) => { + const context = { url: req.url } + const app = createApp(context) + renderer.renderToString(app, (err, html) => { + // handle error... + res.end(html) + }) +}) +``` + +동일한 규칙이 라우터, 스토어 및 이벤트 버스 인스턴스에도 적용됩니다. 모듈에서 직접 export하고 앱에서 import 하는 대신 `createApp`에 새 인스턴스를 만들고 루트 Vue인스턴스에서 이를 주입해야합니다. + +> 이러한 제약 조건은 번들 렌더러를 `{ runInNewContext: true }`와 함께 사용할 때 제거할 수 있지만 각 요청에 대해 새로운 vm context를 만들어야하기 때문에 성능에 상당한 비용이 발생합니다. + +## 빌드 순서 소개 + +지금까지 클라이언트에 동일한 Vue 앱을 전달하는 방법을 다루지 않았습니다. 이를 위해 webpack을 사용해 Vue 앱을 번들링해야합니다. 사실, webpack을 사용해 서버에 Vue 앱을 번들링해야합니다. 이유는 아래와 같습니다. + +- 일반적으로 Vue 앱은 webpack과 `vue-loader`로 구성되어 있으며 `file-loader`를 통해 파일을 가져오는 것, `css-loader`를 통해 CSS를 가져오는 것과 같은 많은 webpack 관련 기능은 Node.js에서 직접 작동하지 않습니다. +- Node.js 최신 버전은 ES2015를 완벽히 지원하지만 이전 버전의 브라우저에서 작동할 수 있도록 만들기 위해 클라이언트 측 코드를 변환해야합니다. 이 때문에 빌드 단계가 필요합니다. + +따라서 webpack을 사용하여 클라이언트와 서버 모두를 번들로 제공할 것입니다. 서버 번들은 서버에서 필요하며 SSR에 사용되며, 클라이언트 번들은 정적 마크업을 위해 브라우저로 전송됩니다. + +![architecture](https://cloud.githubusercontent.com/assets/499550/17607895/786a415a-5fee-11e6-9c11-45a2cfdf085c.png) + +이후 섹션에서 설정의 세부 사항을 논의 할 것입니다. 이제 빌드 설정을 이해한다고 가정하고 webpack을 사용하여 Vue 앱 코드를 작성할 수 있습니다. + +## Webpack을 이용한 코드 구조 + +webpack을 이용해 서버와 클라이언트 모두에서 애플리케이션을 처리하므로 대부분의 소스 코드를 모든 webpack 기반으로 작성할 수 있습니다. 범용적인 코드를 작성할 때 주의해야할 [몇가지 사항](./universal.md)들이 있습니다. + +간단한 프로젝트는 아래와 같을 것 입니다. + +```bash +src +├── components +│   ├── Foo.vue +│   ├── Bar.vue +│   └── Baz.vue +├── App.vue +├── app.js # universal entry +├── entry-client.js # runs in browser only +└── entry-server.js # runs on server only +``` + +### `app.js ` + +`app.js`는 앱의 범용적인 시작 지점입니다. 클라이언트 전용 애플리케이션에서는 이 파일에 루트 Vue 인스턴스를 만들고 DOM에 직접 마운트합니다. 그러나 SSR의 경우에는 책임이 클라이언트 전용 엔트리 파일로 옮겨갑니다. + +```js +import Vue from 'vue' +import App from './App.vue' +// export a factory function for creating fresh app, router and store +// instances +export function createApp () { + const app = new Vue({ + // the root instance simply renders the App component. + render: h => h(App) + }) + return { app } +} +``` + +### `entry-client.js`: + +클라이언트 엔트리는 단순히 앱을 생성하고 DOM에 마운트하는 역할만 합니다. + +```js +import { createApp } from './app' +// client-specific bootstrapping logic... +const { app } = createApp() +// this assumes App.vue template root element has `id="app"` +app.$mount('#app') +``` + +### `entry-server.js`: + +서버 항목은 각 렌더링마다 반복적으로 호출할 수있는 함수인 export default를 사용합니다. 현재 이 인스턴스는 앱 인스턴스를 생성하고 반환하는 것 이외에는 하지 않지만 나중에 서버 측 라우트 매칭 및 데이터 프리-페칭 로직(pre-fetching logic)을 다룹니다. + +```js +import { createApp } from './app' +export default context => { + const { app } = createApp() + return app +} +``` From 5a9688ce1ac8764696f21e90eadbe050d1c743ab Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 15 Jun 2017 12:28:39 +0800 Subject: [PATCH 041/239] Translate api.md via GitLocalize --- zh/api.md | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 zh/api.md diff --git a/zh/api.md b/zh/api.md new file mode 100644 index 00000000..15daaddf --- /dev/null +++ b/zh/api.md @@ -0,0 +1,233 @@ +# API 参考 + +## `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: + +- 绝对路径,指向一个已经构建好的 bundle 文件(`.js` 或 `.json`)。必须以 `/` 开头才会被识别为文件路径。 +- 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)` + + 将 Vue 实例渲染为字符串。上下文对象(context object)可选。回调函数是典型的 Node.js 风格回调,其中第一个参数是 error,第二个参数是渲染的字符串。 + +- #### `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+ + + Provide a client build manifest object generated by `vue-server-renderer/client-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` + - Expects: `boolean | 'once'` (`'once'` only supported in 2.3.1+) + + 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, the app code is isolated from the server process and we don't need to worry about the [stateful singleton problem](./structure.md#avoid-stateful-singletons) mentioned in the docs. 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` or `runInNewContext: 'once'` whenever you can. + +> In 2.3.0 this option has a bug where `runInNewContext: false` still executes the bundle using a separate global context. The following information assumes version 2.3.1+. + + With `runInNewContext: false`, the bundle code will run in the same `global` context with the server process, so be careful about code that modifies `global` in your application code. + + With `runInNewContext: 'once'` (2.3.1+), the bundle is evaluated in a separate `global` context, however only once at startup. This provides better app code isolation since it prevents the bundle from accidentally polluting the server process' `global` object. The caveats are that: + +1. Dependencies that modifies `global` (e.g. polyfills) cannot be externalized in this mode; +2. Values returned from the bundle execution will be using different global constructors, e.g. an error caught inside the bundle will not be an instance of `Error` in the server process. + + 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. From 2f916161ae2468b984f2a35faeb52316c4023c10 Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Thu, 15 Jun 2017 12:28:40 +0800 Subject: [PATCH 042/239] Translate api.md via GitLocalize --- zh/api.md | 108 +++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/zh/api.md b/zh/api.md index 15daaddf..1125a36d 100644 --- a/zh/api.md +++ b/zh/api.md @@ -2,7 +2,7 @@ ## `createRenderer([options])` -Create a [`Renderer`](#class-renderer) instance with (optional) [options](#renderer-options). +使用(可选的)[选项](#renderer-options)创建一个 [`Renderer`](#class-renderer) 实例。 ```js const { createRenderer } = require('vue-server-renderer') @@ -11,20 +11,20 @@ const renderer = createRenderer({ ... }) ## `createBundleRenderer(bundle[, options])` -Create a [`BundleRenderer`](#class-bundlerenderer) instance with a server bundle and (optional) [options](#renderer-options). +使用 server bundle 和(可选的)[选项](#renderer-options)创建一个 [`BundleRenderer`](#class-bundlerenderer) 实例。 ```js const { createBundleRenderer } = require('vue-server-renderer') const renderer = createBundleRenderer(serverBundle, { ... }) ``` -The `serverBundle` argument can be one of the following: +`serverBundle` 参数可以是以下之一: - 绝对路径,指向一个已经构建好的 bundle 文件(`.js` 或 `.json`)。必须以 `/` 开头才会被识别为文件路径。 -- A bundle object generated by webpack + `vue-server-renderer/server-plugin`. -- A string of JavaScript code (not recommended). +- 由 webpack + `vue-server-renderer/server-plugin` 生成的 bundle 对象。 +- JavaScript 代码字符串(不推荐)。 -See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configuration](./build-config.md) for more details. +更多细节请查看 [Server Bundle 指引](./bundle-renderer.md) 和 [构建配置](./build-config.md)。 ## `Class: Renderer` @@ -34,59 +34,59 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati - #### `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. + 将 Vue 示例渲染为 Node.js 流(stream)。上下文对象(context object)可选。更多细节请查看[流式渲染](./streaming.md)。 ## `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. +将 bundle 渲染为字符串。上下文对象(context object)可选。回调是一个典型的Node.js样式回调,其中第一个参数是错误,第二个参数是呈现的字符串。 - #### `bundleRenderer.renderToStream([context])` - Render the bundle to a Node.js stream. The context object is optional. See [Streaming](./streaming.md) for more details. + 将 bundle 渲染为 Node.js 流(stream). 上下文对象(context object)可选。更多细节请查看[流式渲染](./streaming.md)。 -## Renderer Options +## Renderer 选项 - #### `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: +模板还支持使用渲染上下文(render context)进行基本插值: -- Use double-mustache for HTML-escaped interpolation; -- Use triple-mustache for non-HTML-escaped interpolation. +- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation); +- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation)。 - The template automatically injects appropriate content when certain data is found on the render context: +当在渲染上下文(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. +- `context.head`:(字符串)任意 head 标记(markup),将注入到页面头部。 +- `context.styles`:(字符串)任意内联 CSS,将注入到页面头部。注意,如果对组件 CSS 使用 `vue-loader` + `vue-style-loader`,此属性将自动填充。 +- `context.state`:(对象)初始 Vuex store 状态,将作为 `window.__INITIAL_STATE__` 内联到页面。内联的 JSON 将使用 [serialize-javascript](https://github.com/yahoo/serialize-javascript) 自动清理,以防止 XSS 攻击。 - In addition, when `clientManifest` is also provided, the template automatically injects the following: + 此外,当提供 `clientManifest` 时,模板会自动注入以下内容: -- Client-side JavaScript and CSS assets needed by the render (with async chunks automatically inferred); +- 渲染所需的客户端 JavaScript 和 CSS 资源(使用异步 chunk 自动推断); - Optimal `` resource hints for the rendered page. - You can disable all automatic injections by also passing `inject: false` to the renderer. + 你也可以通过将 `inject: false` 传递给 renderer,来禁用所有自动注入。 - See also: +具体查看: -- [Using a Page Template](./basic.md#using-a-page-template) -- [Manual Asset Injection](./build-config.md#manual-asset-injection) +- [使用一个页面模板](./basic.md#using-a-page-template) +- [手动资源注入(Manual Asset Injection)](./build-config.md#manual-asset-injection) - #### `clientManifest` - 2.3.0+ - Provide a client build manifest object generated by `vue-server-renderer/client-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). + 生成由 `vue-server-renderer/client-plugin` 生成的客户端构建 manifest 对象(client build manifest object)。客户端 manifest 对象(client manifest)通过「向 HTML 模板自动资源注入」可以为 bundle renderer 提供合适信息。更多详细信息,请查看[生成 clientManifest](./build-config.md#generating-clientmanifest)。 - #### `inject` - 2.3.0+ - Controls whether to perform automatic injections when using `template`. Defaults to `true`. + 控制使用 `template` 时是否执行自动注入。默认是 `true`。 - See also: [Manual Asset Injection](./build-config.md#manual-asset-injection). + 参考:[手动资源注入(Manual Asset Injection)](./build-config.md#manual-asset-injection)。 - #### `shouldPreload` @@ -95,16 +95,16 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati 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. +默认情况下,只有 JavaScript 和 CSS 文件会被预加载,因为它们是引导应用程序所必需的。 - 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: + 对于其他类型的资源(如图像或字体),预加载过多可能会浪费带宽,甚至损害性能,因此预加载什么资源具体依赖于场景。你可以使用 `shouldPreload` 选项精确控制预加载资源: ```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 @@ -125,35 +125,35 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati #### `runInNewContext` - 2.3.0+ - - only used in `createBundleRenderer` + - 只用于 `createBundleRenderer` - Expects: `boolean | 'once'` (`'once'` only supported in 2.3.1+) - 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, the app code is isolated from the server process and we don't need to worry about the [stateful singleton problem](./structure.md#avoid-stateful-singletons) mentioned in the docs. However, this mode comes at some considerable performance cost because re-executing the bundle is expensive especially when the app gets bigger. + 默认情况下,对于每次渲染,bundle renderer 将创建一个新的 V8 上下文并重新执行整个 bundle。这具有一些好处 - 例如,应用程序代码与服务器进程隔离,我们无需担心文档中提到的[状态单例问题](./structure.md#avoid-stateful-singletons)。然而,这种模式有一些相当大的性能开销,因为重新执行 bundle 带来 高性能开销,特别是当应用程序很大时。 - This option defaults to `true` for backwards compatibility, but it is recommended to use `runInNewContext: false` or `runInNewContext: 'once'` whenever you can. + 此选项默认为 `true` 用于向后兼容,但建议你尽可能使用 `runInNewContext: false` 或 `runInNewContext: 'once'`。 -> In 2.3.0 this option has a bug where `runInNewContext: false` still executes the bundle using a separate global context. The following information assumes version 2.3.1+. +> 在 2.3.0 中,此选项有一个 bug,其中 `runInNewContext: false` 仍然使用独立的全局上下文(separate global context)执行 bundle。以下信息假定版本为 2.3.1+。 - With `runInNewContext: false`, the bundle code will run in the same `global` context with the server process, so be careful about code that modifies `global` in your application code. + 使用 `runInNewContext: false`,bundle 代码将与服务器进程在同一个 `global` 上下文中运行,所以请留意在应用程序代码中,会修改 `global` 的代码。 - With `runInNewContext: 'once'` (2.3.1+), the bundle is evaluated in a separate `global` context, however only once at startup. This provides better app code isolation since it prevents the bundle from accidentally polluting the server process' `global` object. The caveats are that: + 使用 `runInNewContext: 'once'` (2.3.1+),bundle 将在独立的`全局`上下文(separate global context)取值,然而只在启动时取值一次。这提供了更好的应用程序代码隔离,因为它防止 bundle 意外污染服务器进程的 `global` 对象。注意事项如下: -1. Dependencies that modifies `global` (e.g. polyfills) cannot be externalized in this mode; -2. Values returned from the bundle execution will be using different global constructors, e.g. an error caught inside the bundle will not be an instance of `Error` in the server process. +1. 在此模式下,修改 `global`(例如,polyfill)的依赖模块不能进行外部暴露; +2. 从 bundle 执行返回的值将使用不同的全局构造函数,例如,在服务器进程中捕获到 bundle 的内部错误,不会是 `Error` 的一个实例。 - See also: [Source Code Structure](./structure.md) + 参考:[源码结构](./structure.md) - #### `basedir` - 2.2.0+ - - only used in `createBundleRenderer` + - 只用于 `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. + 显式地声明 server bundle 的基本目录(base directory),以从 `node_modules` 解析依赖模块。只有在所生成的 bundle 文件与外部的 NPM 依赖模块放置在不同位置,或者 `vue-server-renderer` 是通过 npm-linked 链接当前项目中时,才需要配置。 - #### `cache` - Provide a [component cache](./caching.md#component-level-caching) implementation. The cache object must implement the following interface (using Flow notations): + 提供[组件缓存](./caching.md#component-level-caching)具体实现。缓存对象必须实现以下接口(使用 Flow 记号): ```js type RenderCache = { @@ -163,7 +163,7 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati }; ``` - A typical usage is passing in an [lru-cache](https://github.com/isaacs/node-lru-cache): + 典型用法是传入 [lru-cache](https://github.com/isaacs/node-lru-cache): ```js const LRU = require('lru-cache') @@ -174,14 +174,14 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati }) ``` - 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: + 请注意,缓存对象应至少要实现 `get` 和 `set`。此外,如果 `get` 和 `has` 接收第二个参数作为回调,那 `get` 和 `has` 也可以是可选的异步函数。这允许缓存使用异步 API,例如,一个 redis 客户端: ```js const renderer = createRenderer({ cache: { get: (key, cb) => { redisClient.get(key, (err, res) => { - // handle error if any + // 处理任何错误 cb(res) }) }, @@ -194,35 +194,35 @@ See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configurati - #### `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 + // 基于指令绑定元数据(metadata)转换 vnode } } }) ``` - 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). + 例如,请查看 [`v-show` 的服务器端实现](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js)。 -## webpack Plugins +## webpack 插件 -The webpack plugins are provided as standalone files and should be required directly: +webpack 插件作为独立文件提供,并且应当直接 require: ```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. +- `vue-ssr-server-bundle.json` 用于服务器端插件; +- `vue-ssr-client-manifest.json` 用于客户端插件。 -The filenames can be customized when creating the plugin instances: +创建插件实例时可以自定义文件名: ```js const plugin = new VueSSRServerPlugin({ @@ -230,4 +230,4 @@ const plugin = new VueSSRServerPlugin({ }) ``` -See [Build Configuration](./build-config.md) for more information. +更多信息请查看[构建配置](./build-config.md)。 From d7f0da72260a6aa7bec2663b23b55980dfd106ff Mon Sep 17 00:00:00 2001 From: Ryo Chikazawa Date: Thu, 15 Jun 2017 12:28:41 +0800 Subject: [PATCH 043/239] Translate api.md via GitLocalize --- zh/api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zh/api.md b/zh/api.md index 1125a36d..55efde91 100644 --- a/zh/api.md +++ b/zh/api.md @@ -50,7 +50,7 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - #### `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. + 为整个页面的 HTML 提供一个模板。此模板应包含注释 ``,作为渲染应用程序内容的占位符。 模板还支持使用渲染上下文(render context)进行基本插值: @@ -66,7 +66,7 @@ const renderer = createBundleRenderer(serverBundle, { ... }) 此外,当提供 `clientManifest` 时,模板会自动注入以下内容: - 渲染所需的客户端 JavaScript 和 CSS 资源(使用异步 chunk 自动推断); -- Optimal `` resource hints for the rendered page. +- 为要渲染页面提供最佳的 `` 资源提示(resource hints)。 你也可以通过将 `inject: false` 传递给 renderer,来禁用所有自动注入。 @@ -93,7 +93,7 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - 2.3.0+ - A function to control what files should have `` resource hints generated. + 一个函数,用来控制什么文件应该生成 `` 资源预加载提示(resource hints)。 默认情况下,只有 JavaScript 和 CSS 文件会被预加载,因为它们是引导应用程序所必需的。 From 43d0167bc1a97b43dce416fb461f0df8cad0b7f0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 15 Jun 2017 12:58:57 +0800 Subject: [PATCH 044/239] Translate api.md via GitLocalize --- zh/api.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/zh/api.md b/zh/api.md index 55efde91..af05ee57 100644 --- a/zh/api.md +++ b/zh/api.md @@ -30,42 +30,42 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - #### `renderer.renderToString(vm[, context], callback)` - 将 Vue 实例渲染为字符串。上下文对象(context object)可选。回调函数是典型的 Node.js 风格回调,其中第一个参数是 error,第二个参数是渲染的字符串。 + 将 Vue 实例渲染为字符串。上下文对象 (context object) 可选。回调函数是典型的 Node.js 风格回调,其中第一个参数是可能抛出的错误,第二个参数是渲染完毕的字符串。 - #### `renderer.renderToStream(vm[, context])` - 将 Vue 示例渲染为 Node.js 流(stream)。上下文对象(context object)可选。更多细节请查看[流式渲染](./streaming.md)。 + 将 Vue 实例渲染为一个 Node.js 流 (stream)。上下文对象 (context object) 可选。更多细节请查看[流式渲染](./streaming.md)。 ## `Class: BundleRenderer` - #### `bundleRenderer.renderToString([context, ]callback)` -将 bundle 渲染为字符串。上下文对象(context object)可选。回调是一个典型的Node.js样式回调,其中第一个参数是错误,第二个参数是呈现的字符串。 +将 bundle 渲染为字符串。上下文对象 (context object) 可选。回调是一个典型的 Node.js 风格回调,其中第一个参数是可能抛出的错误,第二个参数是渲染完毕的字符串。 - #### `bundleRenderer.renderToStream([context])` - 将 bundle 渲染为 Node.js 流(stream). 上下文对象(context object)可选。更多细节请查看[流式渲染](./streaming.md)。 + 将 bundle 渲染为一个 Node.js 流 (stream). 上下文对象 (context object) 可选。更多细节请查看[流式渲染](./streaming.md)。 ## Renderer 选项 - #### `template` - 为整个页面的 HTML 提供一个模板。此模板应包含注释 ``,作为渲染应用程序内容的占位符。 + 为整个页面的 HTML 提供一个模板。此模板应包含注释 ``,作为渲染应用内容的占位符。 -模板还支持使用渲染上下文(render context)进行基本插值: +模板还支持使用渲染上下文 (render context) 进行基本插值: -- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation); -- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation)。 +- 使用双花括号 (double-mustache) 进行 HTML 转义插值 (HTML-escaped interpolation); +- 使用三花括号 (triple-mustache) 进行 HTML 不转义插值 (non-HTML-escaped interpolation)。 -当在渲染上下文(render context)找到某些数据时,模板会自动注入合适的内容: +当在渲染上下文 (render context) 上存在一些特定属性时,模板会自动注入对应的内容: -- `context.head`:(字符串)任意 head 标记(markup),将注入到页面头部。 -- `context.styles`:(字符串)任意内联 CSS,将注入到页面头部。注意,如果对组件 CSS 使用 `vue-loader` + `vue-style-loader`,此属性将自动填充。 -- `context.state`:(对象)初始 Vuex store 状态,将作为 `window.__INITIAL_STATE__` 内联到页面。内联的 JSON 将使用 [serialize-javascript](https://github.com/yahoo/serialize-javascript) 自动清理,以防止 XSS 攻击。 +- `context.head`:(字符串)将会被作为 HTML 注入到页面的头部 (head) 里。 +- `context.styles`:(字符串)内联 CSS,将以 style 标签的形式注入到页面头部。注意,如过你使用了 `vue-loader` + `vue-style-loader` 来处理组件 CSS,此属性会在构建过程中被自动生成。 +- `context.state`:(对象)初始 Vuex store 状态,将以 `window.__INITIAL_STATE__` 的形式内联到页面。内联的 JSON 将使用 [serialize-javascript](https://github.com/yahoo/serialize-javascript) 自动清理,以防止 XSS 攻击。 此外,当提供 `clientManifest` 时,模板会自动注入以下内容: -- 渲染所需的客户端 JavaScript 和 CSS 资源(使用异步 chunk 自动推断); +- 渲染当前页面所需的最优客户端 JavaScript 和 CSS 资源(支持自动推导异步代码分割所需的文件) - 为要渲染页面提供最佳的 `` 资源提示(resource hints)。 你也可以通过将 `inject: false` 传递给 renderer,来禁用所有自动注入。 @@ -77,7 +77,7 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - #### `clientManifest` - 2.3.0+ - 生成由 `vue-server-renderer/client-plugin` 生成的客户端构建 manifest 对象(client build manifest object)。客户端 manifest 对象(client manifest)通过「向 HTML 模板自动资源注入」可以为 bundle renderer 提供合适信息。更多详细信息,请查看[生成 clientManifest](./build-config.md#generating-clientmanifest)。 + 通过此选项提供一个由 `vue-server-renderer/client-plugin` 生成的客户端构建 manifest 对象 (client build manifest object)。此对象包含了 webpack 整个构建过程的信息,从而可以让 bundle renderer 自动推导需要在 HTML 模板中注入的内容。更多详细信息,请查看[生成 clientManifest](./build-config.md#generating-clientmanifest)。 - #### `inject` @@ -93,9 +93,9 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - 2.3.0+ - 一个函数,用来控制什么文件应该生成 `` 资源预加载提示(resource hints)。 + 一个函数,用来控制什么文件应该生成 `` 资源预加载提示 (resource hints)。 -默认情况下,只有 JavaScript 和 CSS 文件会被预加载,因为它们是引导应用程序所必需的。 +默认情况下,只有 JavaScript 和 CSS 文件会被预加载,因为它们是启动应用时所必需的。 对于其他类型的资源(如图像或字体),预加载过多可能会浪费带宽,甚至损害性能,因此预加载什么资源具体依赖于场景。你可以使用 `shouldPreload` 选项精确控制预加载资源: @@ -126,20 +126,20 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - 2.3.0+ - 只用于 `createBundleRenderer` - - Expects: `boolean | 'once'` (`'once'` only supported in 2.3.1+) + - Expects: `boolean | 'once'` (`'once'` 仅在 2.3.1+ 中支持) - 默认情况下,对于每次渲染,bundle renderer 将创建一个新的 V8 上下文并重新执行整个 bundle。这具有一些好处 - 例如,应用程序代码与服务器进程隔离,我们无需担心文档中提到的[状态单例问题](./structure.md#avoid-stateful-singletons)。然而,这种模式有一些相当大的性能开销,因为重新执行 bundle 带来 高性能开销,特别是当应用程序很大时。 + 默认情况下,对于每次渲染,bundle renderer 将创建一个新的 V8 上下文并重新执行整个 bundle。这具有一些好处 - 例如,应用程序代码与服务器进程隔离,我们无需担心文档中提到的[状态单例问题](./structure.md#avoid-stateful-singletons)。然而,这种模式有一些相当大的性能开销,因为重新创建上下文并执行整个 bundle 还是相当昂贵的,特别是当应用很大的时候。 - 此选项默认为 `true` 用于向后兼容,但建议你尽可能使用 `runInNewContext: false` 或 `runInNewContext: 'once'`。 + 出于向后兼容的考虑,此选项默认为 `true`,但建议你尽可能使用 `runInNewContext: false` 或 `runInNewContext: 'once'`。 > 在 2.3.0 中,此选项有一个 bug,其中 `runInNewContext: false` 仍然使用独立的全局上下文(separate global context)执行 bundle。以下信息假定版本为 2.3.1+。 - 使用 `runInNewContext: false`,bundle 代码将与服务器进程在同一个 `global` 上下文中运行,所以请留意在应用程序代码中,会修改 `global` 的代码。 + 使用 `runInNewContext: false`,bundle 代码将与服务器进程在同一个 `global` 上下文中运行,所以请留意在应用程序代码中尽量避免修改 `global`。 - 使用 `runInNewContext: 'once'` (2.3.1+),bundle 将在独立的`全局`上下文(separate global context)取值,然而只在启动时取值一次。这提供了更好的应用程序代码隔离,因为它防止 bundle 意外污染服务器进程的 `global` 对象。注意事项如下: + 使用 `runInNewContext: 'once'` (2.3.1+),bundle 将在独立的`全局`上下文 (separate global context) 取值,然而只在启动时取值一次。这提供了一定程度的应用程序代码隔离,因为它能够防止 bundle 中的代码意外污染服务器进程的 `global` 对象。注意事项如下: -1. 在此模式下,修改 `global`(例如,polyfill)的依赖模块不能进行外部暴露; -2. 从 bundle 执行返回的值将使用不同的全局构造函数,例如,在服务器进程中捕获到 bundle 的内部错误,不会是 `Error` 的一个实例。 +1. 在此模式下,修改 `global`(例如,polyfill)的依赖模块必须被打包进 bundle,不能被外部化 (externalize); +2. 从 bundle 执行返回的值将使用不同的全局构造函数,例如,在服务器进程中捕获到 bundle 内部抛出的错误,使用的是 bundle 上下文中的 Error 构造函数,所以它不会是服务器进程中 `Error` 的一个实例。 参考:[源码结构](./structure.md) @@ -149,11 +149,11 @@ const renderer = createBundleRenderer(serverBundle, { ... }) - 2.2.0+ - 只用于 `createBundleRenderer` - 显式地声明 server bundle 的基本目录(base directory),以从 `node_modules` 解析依赖模块。只有在所生成的 bundle 文件与外部的 NPM 依赖模块放置在不同位置,或者 `vue-server-renderer` 是通过 npm-linked 链接当前项目中时,才需要配置。 + 显式地声明 server bundle 的运行目录。运行时将会以此目录为基准来解析 `node_modules` 中的依赖模块。只有在所生成的 bundle 文件与外部的 NPM 依赖模块放置在不同位置,或者 `vue-server-renderer` 是通过 npm link 链接到当前项目中时,才需要配置此选项。 - #### `cache` - 提供[组件缓存](./caching.md#component-level-caching)具体实现。缓存对象必须实现以下接口(使用 Flow 记号): + 提供[组件缓存](./caching.md#component-level-caching)具体实现。缓存对象必须实现以下接口(使用 Flow 语法表示): ```js type RenderCache = { @@ -174,7 +174,7 @@ const renderer = createBundleRenderer(serverBundle, { ... }) }) ``` - 请注意,缓存对象应至少要实现 `get` 和 `set`。此外,如果 `get` 和 `has` 接收第二个参数作为回调,那 `get` 和 `has` 也可以是可选的异步函数。这允许缓存使用异步 API,例如,一个 redis 客户端: + 请注意,缓存对象应至少要实现 `get` 和 `set`。此外,如果 `get` 和 `has` 接收第二个参数作为回调,那 gethas 也可以是可选的异步函数。这允许缓存使用异步 API,例如,一个 redis 客户端: ```js const renderer = createRenderer({ From 9afac950cffdfe3e66dd8632e6a05907ff1d6a76 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:18:48 +0900 Subject: [PATCH 045/239] Update routing.md --- ko/routing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ko/routing.md b/ko/routing.md index 51349679..b6d62a2d 100644 --- a/ko/routing.md +++ b/ko/routing.md @@ -68,7 +68,7 @@ export default context => { } ``` -서버 번들이 이미 빌드되었다고 가정하면 (빌드 설정은 이 단계에서는 무시합니다.) 서버 코드는 다음과 같습니다. +이미 서버 번들이 빌드되었다고 가정하면 (빌드 설정은 이 단계에서는 무시합니다.) 서버 코드는 다음과 같습니다. ```js // server.js @@ -104,7 +104,7 @@ import Foo from './Foo.vue' const Foo = () => import('./Foo.vue') ``` -이는 순수 클라이언트 측 Vue 앱을 만드는 어떠한 시나리오에서도 작동합니다. 그러나 SSR을 할 때 몇가지 제한 사항이 있습니다. 먼저 렌더링을 시작하기 전에 서버에서 모든 비동기 컴포넌트를 처리해야합니다. 그렇지 않으면 마크업에 미처 불러오지 못한 부분들이 생깁니다. 클라이언트에서 처리하기 전에 이를 마무리해야합니다. 그렇지 않으면 클라이언트와의 컨텐츠가 일치하지 않는 에러가 발생할 수 있습니다. +이는 순수 클라이언트 측 Vue 앱을 만드는 어떠한 시나리오에서도 작동합니다. 그러나 SSR을 사용할 때 몇가지 제한 사항이 있습니다. 먼저 렌더링을 시작하기 전에 서버에서 모든 비동기 컴포넌트를 처리해야합니다. 그렇지 않으면 마크업에 미처 불러오지 못한 부분들이 생깁니다. 클라이언트에서 처리하기 전에 이를 마무리해야합니다. 그렇지 않으면 클라이언트와의 컨텐츠가 일치하지 않는 에러가 발생할 수 있습니다. 이로 인해 앱의 임의의 위치에서 비동기 컴포넌트를 사용하는 것이 약간 까다로울 수 있습니다.(향후 이 기능이 향상됩니다.) 그러나 **라우트 레벨에서 수행하는 경우**(라우트 컴포넌트에서 비동기 컴포넌트 사용) 원할히 작동합니다. 라우트를 해석할 때 (vue-router와 일치하는 비동기 컴포넌트를 자동으로 분석할 때) 해야할 일은 서버와 클라이언트 모두에서 `router.onReady`를 사용해야 합니다. From 775e882b0aea4d5bddc25f1f297dbbffe36b2fc5 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:19:48 +0900 Subject: [PATCH 046/239] Update universal.md --- ko/universal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ko/universal.md b/ko/universal.md index 3013ed78..97a68b25 100644 --- a/ko/universal.md +++ b/ko/universal.md @@ -26,7 +26,7 @@ ## 사용자 정의 디렉티브 -대부분의 사용자 지정 디렉티브는 DOM을 직접 조작하므로 SSR중 오류가 발생합니다. 이 문제를 해결하는 방법은 두가지 입니다. +대부분의 사용자 정의 디렉티브는 DOM을 직접 조작하므로 SSR중 오류가 발생합니다. 이 문제를 해결하는 방법은 두가지 입니다. 1. 추상화 메커니즘으로 컴포넌트를 사용하고 가상 DOM 수준에서 작업해야합니다. (예 : 렌더링 기능 사용) 2. 컴포넌트로 쉽게 바꿀 수 없는 사용자 정의 디렉티브가 있는 경우 서버 렌더러를 만들 때 [`directives`](./api.md#directives)옵션을 사용해 "서버용 버전"을 제공할 수 있습니다. From d378e64e762899a9786212ad1bf88104525006b1 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:22:30 +0900 Subject: [PATCH 047/239] Update README.md --- ko/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ko/README.md b/ko/README.md index 6a86900f..99497a6c 100644 --- a/ko/README.md +++ b/ko/README.md @@ -3,13 +3,15 @@ > **참고사항:** 이 가이드는 아래에 표기된 라이브러리들의 최소 요구사항을 필요로 합니다. > - vue & vue-server-renderer >= 2.3.0 > - vue-router >= 2.5.0 -> - vue-loader >= 12.0.0 & vue-style-loader >= 3.0.0Vue 2.2 버전과 함께 SSR을 사용하고 있다면 권장하는 코드 구조가 [약간 다릅니다.](./structure.md) ([runInNewContext](./api.md#runinnewcontext)이 `false`로 설정됨) 기존 앱을 사용해야 하는 것이 맞지만 새로운 권장 방식으로 마이그레이션 하는 것이 좋습니다. +> - vue-loader >= 12.0.0 & vue-style-loader >= 3.0.0 + +Vue 2.2 버전과 함께 SSR을 사용하고 있다면 권장하는 코드 구조가 [약간 다릅니다.](./structure.md) ([runInNewContext](./api.md#runinnewcontext)이 `false`로 설정됨) 기존 앱을 사용해야 하는 것이 맞지만 새로운 권장 방식으로 마이그레이션 하는 것이 좋습니다. ## 서버사이드 렌더링(SSR)이란 무엇입니까? -Vue.js는 클라이언트 측 애플리케이션을 위한 프레임워크입니다. 기본적으로 Vue 컴포넌트는 브라우저에서 DOM을 생성 및 조작 후 출력합니다. 그러나 동일한 컴포넌트를 서버의 HTML 문자열로 렌더링하고 직접 브라우저로 보내고 마지막으로 정적 마크업을 클라이언트의 상호작용하는 애플리케이션으로 "hydrate" 하는 것도 가능합니다. +Vue.js는 클라이언트 측 애플리케이션을 위한 프레임워크입니다. 기본적으로 Vue 컴포넌트는 브라우저에서 DOM을 생성 및 조작 후 출력합니다. 그러나 동일한 컴포넌트를 서버의 HTML 문자열로 렌더링한 후 직접 브라우저로 보내고 마지막으로 정적 마크업을 클라이언트의 상호작용하는 애플리케이션으로 "hydrate" 하는 것도 가능합니다. -서버에서 렌더링된 Vue.js 앱은 코드 대부분이 서버와 클라이언트 모두에서 실행하는 점에서 "같은 형태" **이며** "범용적"으로 여겨질 수 있습니다. +서버에서 렌더링된 Vue.js 앱은 코드 대부분이 서버와 클라이언트 모두에서 실행하는 점에서 "같은 형태" **이며** "범용적"으로 여겨질 수 있습니다. ## 왜 SSR을 사용하나요? @@ -31,15 +33,15 @@ Vue.js는 클라이언트 측 애플리케이션을 위한 프레임워크입니 ## 서버 사이드 렌더링 vs 사전 렌더링 -몇 가지 마케팅 페이지 (예 : `/`, `/about`, `/contact` 등)의 검색 엔진 최적화를 개선하기 위해 SSR을 고려하는 중이라면 **사전 렌더링**이 더 좋습니다. HTML을 즉석에서 컴파일하기 위해 웹 서버를 사용하는 대신 사전 렌더링은 빌드시 특정 경로에 대한 정적 HTML 파일을 생성합니다. 장점은 미리 렌더링을 설정하는 것이 훨씬 간단하며 프론트 엔드를 완전히 정적 인 사이트로 유지할 수 있다는 것입니다. +몇 가지 마케팅 페이지 (예 : `/`, `/about`, `/contact` 등)의 검색 엔진 최적화를 개선하기 위해 SSR을 고려하는 중이라면 **사전 렌더링**이 더 좋습니다. HTML을 즉석에서 컴파일하기 위해 웹 서버를 사용하는 대신 사전 렌더링은 빌드시 특정 경로에 대한 정적 HTML 파일을 생성합니다. 장점은 미리 렌더링을 설정하는 것이 훨씬 간단하며 프론트 엔드를 완전히 정적인 사이트로 유지할 수 있다는 것입니다. -webpack을 사용하는 경우 p[prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin)을 사용하여 사전 렌더링을 쉽게 추가 할 수 있습니다. Vue 앱으로 광범위하게 테스트되었습니다. 실제로 [제작자](https://github.com/chrisvfritz)는 Vue 핵심 팀의 멤버입니다. +webpack을 사용하는 경우 [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin)을 사용하여 사전 렌더링을 쉽게 추가 할 수 있습니다. Vue 앱으로 광범위하게 테스트되었습니다. 실제로 [제작자](https://github.com/chrisvfritz)는 Vue 핵심 팀의 멤버입니다. ## 이 가이드에 관하여 이 안내서는 Node.js를 서버로 사용하는 서버 렌더링 싱글 페이지 애플리케이션에 중점을 둡니다. Vue SSR과 다른 백엔드 설정을 혼용하는 것은 다른 주제이므로 이 가이드에서는 다루지 않습니다. -이 가이드는 매우 깊이 있고 Vue.js 자체에 이미 익숙하고 Node.js와 webpack에 대한 실제 지식이 있다고 가정합니다. Next.js를 사용해 보세요. 즉시 원활하게 사용할 수있는 더 높은 수준의 솔루션을 원한다면 [Nuxt.js](http://nuxtjs.org/)를 사용해보십시오. 동일한 Vue 스택을 기반으로하지만 많은 상용구를 추상화하고 정적 사이트 생성과 같은 몇 가지 추가 기능을 제공합니다. 그러나 앱 구조를 직접 제어해야하는 경우 사용방식이 맞지 않을 수 있습니다. 그럼에도 불구하고 상황이 어떻게 작동하는지 더 잘 이해하기 위해 이 안내서를 읽는 것이 여전히 유용 할 것입니다. +이 가이드는 매우 깊이 있고 Vue.js 자체에 이미 익숙하고 Node.js와 webpack에 대한 실제 지식이 있다고 가정합니다. 즉시 원활하게 사용할 수있는 더 높은 수준의 솔루션을 원한다면 [Nuxt.js](http://nuxtjs.org/)를 사용해보십시오. 동일한 Vue 스택을 기반으로하지만 많은 상용구를 추상화하고 정적 사이트 생성과 같은 몇 가지 추가 기능을 제공합니다. 그러나 앱 구조를 직접 제어해야하는 경우 사용방식이 맞지 않을 수 있습니다. 그럼에도 불구하고 상황이 어떻게 작동하는지 더 잘 이해하기 위해 이 안내서를 읽는 것이 여전히 유용할 것입니다. 이 가이드를 읽으면서 가이드가 다루는 대부분의 기술을 사용하는 [HackerNews Demo](https://github.com/vuejs/vue-hackernews-2.0/)를 참조하는 것이 도움이 될 것입니다. From f2c6f339cba308877dc640422d18821c3d6432f5 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:24:22 +0900 Subject: [PATCH 048/239] Update head.md --- ko/head.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ko/head.md b/ko/head.md index 0ad9a4f9..dfc0ef6e 100644 --- a/ko/head.md +++ b/ko/head.md @@ -60,23 +60,23 @@ export default { } ``` -번들 렌더러에 전달된 `template` 내부 +번들 렌더러에 전달된 `template` 내부입니다 ```html - + {{ title }} - - + + ... - + ``` **참고** - 두개의 mustache(HTML 이스케이프된 보간)를 사용해 XSS 공격을 피해야 합니다. -- 렌더링하는 동안 컴포넌트에 title을 설정하지 않은 경우 `context` 개체를 만들 때 기본 title을 제공해야합니다. +- 렌더링하는 동안 컴포넌트에 title을 설정하지 않은 경우 `context` 객체를 만들 때 기본 title을 제공해야합니다. --- From dfb55d9f880277749b5193e13c3e5320ab50906f Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:24:28 +0900 Subject: [PATCH 049/239] Update README.md --- ko/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ko/README.md b/ko/README.md index 99497a6c..390fd83e 100644 --- a/ko/README.md +++ b/ko/README.md @@ -45,4 +45,4 @@ webpack을 사용하는 경우 [prerender-spa-plugin](https://github.com/chrisvf 이 가이드를 읽으면서 가이드가 다루는 대부분의 기술을 사용하는 [HackerNews Demo](https://github.com/vuejs/vue-hackernews-2.0/)를 참조하는 것이 도움이 될 것입니다. -마지막으로, 이 가이드는 완벽하지 않습니다. 잘 작동하고 있는 것을 알고 있지만 개선할 여지가 있습니다. 이 가이드는 미래에 개정 될 수 있습니다. 풀 리퀘스트를 해주세요 +마지막으로, 이 가이드는 완벽하지 않습니다. 잘 작동하고 있는 것을 알고 있지만 개선할 여지가 있습니다. 이 가이드는 앞으로 개정 될 수 있습니다. 풀 리퀘스트를 해주세요 From fe009687094299bb0eb91cbb4591de3b45449767 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:27:20 +0900 Subject: [PATCH 050/239] Update caching.md --- ko/caching.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ko/caching.md b/ko/caching.md index 2738e9b3..110bf82d 100644 --- a/ko/caching.md +++ b/ko/caching.md @@ -4,7 +4,7 @@ Vue의 SSR은 매우 빠르지만 컴포넌트 인스턴스 및 가상 DOM 노 ## 페이지 레벨 캐싱 -서버에서 렌더링된 애플리케이션은 외부 데이터를 사용하므로 본질적으로 내용은 동적이어서 오래동안 캐시할 수 없습니다. 그러나 컨텐츠가 사용자별로 다르면(즉, 동일한 URL의 경우 항상 모든 사용자에게 동일한 컨텐츠가 렌더링 됨) [micro-caching](https://www.nginx.com/blog/benefits-of-microcaching-nginx/)이라고 부르는 방식을 활용하여 높은 트래픽을 처리하는 앱의 성능을 대폭 향상시킬 수 있습니다. +서버에서 렌더링된 앱은 외부 데이터를 사용하므로 본질적인 내용은 동적이기 때문에 오랫동안 캐시할 수 없습니다. 그러나 컨텐츠가 사용자별로 다르면(즉, 동일한 URL의 경우 항상 모든 사용자에게 동일한 컨텐츠가 렌더링 됨) [micro-caching](https://www.nginx.com/blog/benefits-of-microcaching-nginx/)이라고 부르는 방식을 활용하여 높은 트래픽을 처리하는 앱의 성능을 대폭 향상시킬 수 있습니다. 이것은 일반적으로 Nginx 레이어에서 이루어지지만 Node.js에서도 구현할 수 있습니다. @@ -34,7 +34,7 @@ server.get('*', (req, res) => { }) ``` -컨텐츠는 단 1초 동안만 캐시되므로 사용자는 이전 컨텐츠는 보지 못합니다. 그러나 이는 서버가 각 캐시된 페이지에 대해 초당 최대 하나의 렌더링만 수행하면 된다는 것만 의미합니다. +컨텐츠는 단 1초 동안만 캐시되므로 사용자는 이전 컨텐츠를 볼 수 없습니다. 그러나 이는 서버가 각 캐시된 페이지에 대해 초당 최대 하나의 렌더링만 수행하면 된다는 것만 의미합니다. ## 컴포넌트 레벨 캐싱 @@ -76,7 +76,7 @@ export default { - 전역 상태에 의존하는 하위 컴포넌트를 가지고 있는 경우 - 렌더링 `context`에 사이드이펙트를 발생시키는 하위 컴포넌트가 있는 경우 -따라서 성능상의 병목을 해결하려면 컴포넌트 캐싱을 신중하게 적용해야합니다. 대부분의 경우 단일 인스턴스 컴포넌트를 캐시하지 않아도 됩니다. 캐싱에 적합한 가장 일반적인 유형의 컴포넌트는 큰`v-for` 목록에서 반복되는 컴포넌트입니다. 이러한 컴포넌트는 대개 데이터베이스 모음의 객체에 의해 구동되기 때문에 고유한 ID와 최종 업데이트된 타임스탬프를 사용하여 캐시키를 생성하는 간단한 캐싱 전략을 사용할 수 있습니다, +따라서 성능상의 병목을 해결하려면 컴포넌트 캐싱을 신중하게 적용해야합니다. 대부분의 경우 단일 인스턴스 컴포넌트를 캐시하지 않아도 됩니다. 캐싱에 적합한 가장 일반적인 유형의 컴포넌트는 거대한 `v-for` 리스트에서 반복되는 컴포넌트입니다. 이러한 컴포넌트는 대개 데이터베이스 모음의 객체에 의해 구동되기 때문에 고유한 ID와 최종 업데이트된 타임스탬프를 사용하여 캐시키를 생성하는 간단한 캐싱 전략을 사용할 수 있습니다, ```js serverCacheKey: props => props.item.id + '::' + props.item.last_updated From 8813c0c108343c9700fa7f25c8e6d17c051ab7f1 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:29:27 +0900 Subject: [PATCH 051/239] Update build-config.md --- ko/build-config.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ko/build-config.md b/ko/build-config.md index 276a4b97..ba4c3c49 100644 --- a/ko/build-config.md +++ b/ko/build-config.md @@ -52,13 +52,13 @@ const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { }) ``` -또는, 번들을 Object로 만들어 `createBundleRenderer`에 전달할 수 있습니다. 이는 개발중 핫 리로드를 사용할 때 유용합니다. HackerNews 데모의 [설정](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js)을 참조하세요. +또는, 번들을 객체로 만들어 `createBundleRenderer`에 전달할 수 있습니다. 이는 개발중 핫 리로드를 사용할 때 유용합니다. HackerNews 데모의 [설정](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js)을 참조하세요. ### Externals 주의사항 `externals`옵션에서는 CSS파일을 허용하는 목록에 추가합니다. 의존성에서 가져온 CSS는 여전히 webpack에서 처리해야합니다. webpack을 사용하는 다른 유형의 파일을 가져오는 경우(예: `*.vue`, `*.sass`) 파일을 허용 목록에 추가해야합니다. -`runInNewContext: 'once'` 또는 `runInNewContext: true`를 사용하는 경우 `전역 변수`를 수정하는 폴리필을 허용 목록에 추가해야합니다. 예를 들어 `babel-polyfill`이 있습니다. 새 컨텍스트 모드를 사용할 때 **서버 번들의 내부 코드가 자체적으로 `전역` 객체를 가지고 있기 때문입니다.** Node 7.6버전 이상을 사용할 때 서버에서는 실제로 필요하지 않으므로 클라이언트에서 가져오는 것이 더 쉽습니다. +`runInNewContext: 'once'` 또는 `runInNewContext: true`를 사용하는 경우 `전역 변수`를 수정하는 폴리필(Polyfill)을 허용 목록에 추가해야합니다. 예를 들어 `babel-polyfill`이 있습니다. 새 컨텍스트 모드를 사용할 때 **서버 번들의 내부 코드가 자체적으로 `전역` 객체를 가지고 있기 때문입니다.** Node 7.6 이상을 사용할 때 서버에서는 실제로 필요하지 않으므로 클라이언트에서 가져오는 것이 더 쉽습니다. ## 클라이언트 설정 @@ -72,7 +72,7 @@ const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { 두가지 장점이 있습니다. -1. 생성된 파일 이름에 해시가 있을 때 올바른 에셋 URL을 삽입하기 위해 `html-webpack-plugin`를 대체할 수 있습니다. +1. 생성된 파일 이름에 해시가 있을 때 올바른 에셋 URL을 삽입하기 위해 `html-webpack-plugin`을 대체할 수 있습니다. 2. webpack의 주문형 코드 분할 기능을 활용하는 번들을 렌더링할 때 최적의 청크를 프리로드/프리페치하고 클라이언트에 폭포수 요청을 피하기 위해 필요한 비동기 청크에 `` 태그를 지능적으로 삽입할 수 있습니다. 이는 TTI(첫 작동까지의 시간)을 개선합니다. 클라이언트 매니페스트를 사용하려면 클라이언트 설정은 아래와 같아야 합니다. @@ -116,15 +116,15 @@ const renderer = createBundleRenderer(serverBundle, { ```html - + - - + +
async
@@ -132,7 +132,7 @@ const renderer = createBundleRenderer(serverBundle, { - + ` ``` @@ -146,7 +146,7 @@ const renderer = createBundleRenderer(serverBundle, { 렌더링 중에 사용된 `*.vue` 컴포넌트에서 수집된 모든 CSS가 포함된 인라인 ``태그가 반환됩니다. 자세한 내용은 [CSS 관리](./css.md)를 참조하십시오. -`clientManifest`가 제공되면 반환되는 문자열에는 webpack에서 생성한 CSS파일 (예: `extract-text-webpack-plugin` 또는 imported with `file-loader`)에 대한 `` 태그가 포함됩니다. +`clientManifest`가 제공되면 반환되는 문자열에는 webpack에서 생성한 CSS파일 (예: `extract-text-webpack-plugin` 또는 `file-loader`로 추가된)에 대한 `` 태그가 포함됩니다. - `context.renderState(options?: Object)` @@ -180,22 +180,22 @@ const renderer = createBundleRenderer(serverBundle, { - `context.getPreloadFiles()` - `clientManifest`를 필요로 합니다. -이 메소드는 문자열을 반환하지 않고 대신 미리 로드해야 할 에셋을 나타내는 파일 객체의 배열을 반환합니다. 이는 프로그래밍 방식으로 HTTP/2 서버 푸시를 하는데 사용할 수 있습니다. +이 메소드는 문자열을 반환하지 않는 대신 미리 로드해야 할 에셋을 나타내는 파일 객체의 배열을 반환합니다. 이는 프로그래밍 방식으로 HTTP/2 서버 푸시를 하는데 사용할 수 있습니다. `createBundleRenderer`에 전달된 `template`은 `context`를 사용하여 보간되므로 템플릿안에서 이러한 메소드를 사용할 수 있습니다.(`inject: false` 옵션과 함께) ```html - + {{{ renderResourceHints() }}} {{{ renderStyles() }}} - - + + {{{ renderState() }}} {{{ renderScripts() }}} - + ``` From 652aa7e8299577fd6a9de98cc2a2ff591cf1f5cd Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:31:43 +0900 Subject: [PATCH 052/239] Update basic.md --- ko/basic.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ko/basic.md b/ko/basic.md index 51890d42..1ed887fa 100644 --- a/ko/basic.md +++ b/ko/basic.md @@ -10,7 +10,7 @@ npm install vue vue-server-renderer --save #### 참고 사항 -- Node.js 6 이상을 권장합니다. +- Node.js v6 이상을 권장합니다. - `vue-server-renderer` 와`vue`는 반드시 서로 맞는 버전을 사용해야합니다. - `vue-server-renderer`는 일부 Node.js 네이티브 모듈을 사용하므로 Node.js에서만 사용할 수 있습니다. 앞으로 다른 JavaScript 런타임에서 실행할 수 있는 보다 간단한 방법을 제공할 예정입니다. @@ -59,11 +59,11 @@ server.get('*', (req, res) => { return } res.end(` - - + + Hello ${html} - + `) }) }) @@ -74,15 +74,15 @@ server.listen(8080) Vue 앱을 렌더링할 때 렌더러는 앱의 마크업만 생성합니다. 이 예제에서 추가 HTML 페이지 쉘로 출력을 레핑해야합니다. -이를 간단히 하기 위해 렌더러를 만들 때 페이지 템플릿을 직접 제공할 수 있습니다. 대부분의 경우 페이지 템플릿을 자체 파일에 저장합니다. (예: `index.template.html`) +이를 간단히 하기 위해 렌더러를 만들 때 페이지 템플릿을 직접 제공할 수 있습니다. 대부분의 경우 페이지 템플릿을 자체 파일에 저장합니다. (예: `index.template.html`) ```html Hello - + - + ``` @@ -99,21 +99,21 @@ renderer.renderToString(app, (err, html) => { }) ``` -### 템플릿 인터폴레이션 +### 템플릿 인터폴레이션(Interpolation) 템플릿은 간단한 인터폴레이션(보간)도 지원합니다. 다음 템플릿을 확인하세요. ```html - + {{ title }} {{{ meta }}} - - + + - + ``` From 111421cf47053db2568023e5b3c7c09eec585779 Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:32:21 +0900 Subject: [PATCH 053/239] Update state/store --- ko/SUMMARY.md | 2 +- ko/api.md | 2 +- ko/basic.md | 2 +- ko/build-config.md | 4 ++-- ko/structure.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ko/SUMMARY.md b/ko/SUMMARY.md index 9e71e28b..3c71d83a 100644 --- a/ko/SUMMARY.md +++ b/ko/SUMMARY.md @@ -2,7 +2,7 @@ - [유니버설 코드 작성하기](universal.md) - [소스코드 구조](structure.md) - [라우팅과 코드 분할](routing.md) -- [데이터 프리페치와 스테이트](data.md) +- [데이터 프리페치와 state(상태)](data.md) - [클라이언트 사이드 하이드레이션](hydration.md) - [번들 렌더러 소개](bundle-renderer.md) - [빌드 설정](build-config.md) diff --git a/ko/api.md b/ko/api.md index e04d9496..e1344afd 100644 --- a/ko/api.md +++ b/ko/api.md @@ -61,7 +61,7 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 - `context.head`: (string) 페이지 head에 삽입되어야하는 마크업 - `context.styles`: (string) 페이지 head에 삽입되어야하는 모든 인라인 CSS. 컴포넌트 CSS에 `vue-loader` + `vue-style-loader`를 사용하는 경우 이 속성이 자동으로 채워집니다. -- `context.state`: (Object) `window.__INITIAL_STATE__`에서 반드시 인라인되어야하는 초기 Vuex 스토어 스테이트 인라인된 JSON은 XSS를 방지하기 위해 [serialize-javascript](https://github.com/yahoo/serialize-javascript)를 사용해 자동으로 삭제합니다. +- `context.state`: (Object) `window.__INITIAL_STATE__`에서 반드시 인라인되어야하는 초기 Vuex store(저장소) state(상태) 인라인된 JSON은 XSS를 방지하기 위해 [serialize-javascript](https://github.com/yahoo/serialize-javascript)를 사용해 자동으로 삭제합니다. `clientManifest`이 제공되면 템플릿은 자동으로 아래 내용을 주입합니다. diff --git a/ko/basic.md b/ko/basic.md index 1ed887fa..2b3b6b10 100644 --- a/ko/basic.md +++ b/ko/basic.md @@ -139,6 +139,6 @@ renderer.renderToString(app, context, (err, html) => { - `*.vue` 컴포넌트를 사용할 때 CSS를 자동으로 주입합니다. - `clientManifest`를 사용할 때 에셋 링크 및 리소스에 관련한 힌트를 자동으로 주입합니다. -- 클라이언트 측 하이드레이션을 위한 Vuex 스테이트 포함시 자동 주입 및 XSS를 예방을 지원합니다. +- 클라이언트 측 하이드레이션을 위한 Vuex state(상태) 포함시 자동 주입 및 XSS를 예방을 지원합니다. 나중에 이 가이드에서 관련 개념을 소개할 때 자세히 다룰 것 입니다. diff --git a/ko/build-config.md b/ko/build-config.md index ba4c3c49..ca167781 100644 --- a/ko/build-config.md +++ b/ko/build-config.md @@ -150,9 +150,9 @@ const renderer = createBundleRenderer(serverBundle, { - `context.renderState(options?: Object)` -이 메소드는 `context.state`를 직렬화하고 스테이트를 `window.__INITIAL_STATE__`로 포함하는 인라인 스크립트를 리턴합니다. +이 메소드는 `context.state`를 직렬화하고 state(상태)를 `window.__INITIAL_STATE__`로 포함하는 인라인 스크립트를 리턴합니다. -컨텍스트 스테이트 키와 윈도우 스테이트 키는 옵션 객체를 전달하여 사용자 정의할 수 있습니다. +컨텍스트 state(상태) 키와 윈도우 state(상태) 키는 옵션 객체를 전달하여 사용자 정의할 수 있습니다. ```js context.renderState({ diff --git a/ko/structure.md b/ko/structure.md index 276ad176..04a51d9d 100644 --- a/ko/structure.md +++ b/ko/structure.md @@ -36,7 +36,7 @@ server.get('*', (req, res) => { }) ``` -동일한 규칙이 라우터, 스토어 및 이벤트 버스 인스턴스에도 적용됩니다. 모듈에서 직접 export하고 앱에서 import 하는 대신 `createApp`에 새 인스턴스를 만들고 루트 Vue인스턴스에서 이를 주입해야합니다. +동일한 규칙이 라우터, store(저장소) 및 이벤트 버스 인스턴스에도 적용됩니다. 모듈에서 직접 export하고 앱에서 import 하는 대신 `createApp`에 새 인스턴스를 만들고 루트 Vue인스턴스에서 이를 주입해야합니다. > 이러한 제약 조건은 번들 렌더러를 `{ runInNewContext: true }`와 함께 사용할 때 제거할 수 있지만 각 요청에 대해 새로운 vm context를 만들어야하기 때문에 성능에 상당한 비용이 발생합니다. From 117c6e2bcedebff377db9e9e70a31942a58b3dff Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:36:50 +0900 Subject: [PATCH 054/239] Update api.md --- ko/api.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ko/api.md b/ko/api.md index e1344afd..03599514 100644 --- a/ko/api.md +++ b/ko/api.md @@ -50,12 +50,12 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 - #### `template` -전체 페이지 HTML에 대한 템플릿을 제공하십시오. 템플릿에는 렌더링된 앱 컨텐츠의 플레이스홀더 역할을 하는 주석 ``이 있어야 합니다. +전체 페이지 HTML에 대한 템플릿입니다. 템플릿에는 렌더링된 앱 컨텐츠의 플레이스홀더(placeholder) 역할을 하는 주석 ``이 있어야 합니다. 템플릿은 렌더링 컨텍스트를 사용하여 기본 인터폴레이션을 지원합니다. -- 이중 mustache를 이용해 HTML 이스케이프 인터폴레이션을 합니다. -- 삼중 mustache를 이용해 비 HTML 이스케이프 인터폴레이션을 합니다. +- 이중 mustache를 이용해 HTML 이스케이프 인터폴레이션(HTML-escaped-interpolation)을 합니다. +- 삼중 mustache를 이용해 비 HTML 이스케이프 인터폴레이션(Non HTML-escaped-interpolation)을 합니다. 템플릿은 렌더링 컨텍스트에서 특정 데이터가 발견되면 적절한 컨텐츠를 자동으로 주입합니다. @@ -79,7 +79,7 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 `vue-server-renderer/client-plugin`에 의해 생성된 클라이언트 매니페스트 객체를 제공합니다. 클라이언트 매니페스트는 번들 렌더러에게 HTML 템플릿으로 자동 에셋 주입을 위한 적절한 정보를 제공합니다. 자세한 내용은[Generating clientManifest](./build-config.md#generating-clientmanifest)을 참조하세요. -- +- #### `inject` - 2.3.0+ @@ -88,7 +88,7 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 [Manual Asset Injection](./build-config.md#manual-asset-injection)을 참조하세요 -- +- #### `shouldPreload` - 2.3.0+ @@ -121,7 +121,7 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 }) ``` -- +- #### `runInNewContext` - 2.3.0+ @@ -130,20 +130,20 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 기본적으로 각 렌더에 대해 번들 렌더러는 새로운 V8 컨텍스트를 만들고 전체 번들을 다시 실행합니다. 이는 몇가지 장점을 가집니다. 예를 들어 애플리케이션 코드는 서버 프로세스와 분리되어 있으며 문서에 언급된 [stateful singleton problem](./structure.md#avoid-stateful-singletons)에 대해 걱정할 필요가 없습니다. 그러나 번들을 다시 실행하는 것은 앱이 커지면 비용이 많이 들기 때문에 이 모드는 상당한 성능 비용을 발생시킵니다. -이 옵션은 하위 호환성을 위해 `true`가 기본값이지만 가능할 때마다 `runInNewContext: false` 또는 `runInNewContext: 'once'`를 사용하는 것이 좋습니다. +이 옵션은 하위 호환성을 위해 `true`가 기본값이지만 가능할 때마다 `runInNewContext: false` 또는 `runInNewContext: 'once'`를 사용하는 것이 좋습니다. -> 2.3.0에서 이 옵션은 `runInNewContext: false`가 별ㄷ로의 전역 컨텍스트를 사용하여 번들을 실행하는 버그가 있습니다. 2.3.1버전 이후 버전을 사용한다고 가정합니다. +> 2.3.0에서 이 옵션은 `runInNewContext: false`가 별도의 전역 컨텍스트를 사용하여 번들을 실행하는 버그가 있습니다. 2.3.1버전 이후 버전을 사용한다고 가정합니다. `runInNewContext: false`를 사용하면 번들 코드가 서버 프로세스와 동일한 `global` 컨텍스트에서 실행되므로 애플리케이션 코드에서 `global`을 수정하는 코드를 주의해야 합니다. -`runInNewContext: 'once'`(2.3.1+)를 사용하면 번들은 별도의 `global` 컨텍스트로 평가되지만 시작할 때 한번뿐입니다. 번들이 실수로 서버 프로세스의 `global` 객체를 오염시키는 것을 방지하므로 더 안전한 코드 관리를 할 수 있습니다. 주의사항은 다음과 같습니다. +`runInNewContext: 'once'`(2.3.1+)를 사용하면 번들은 별도의 `global` 컨텍스트로 평가되지만 시작할 때 한번 뿐입니다. 번들이 실수로 서버 프로세스의 `global` 객체를 오염시키는 것을 방지하므로 더 안전한 코드 관리를 할 수 있습니다. 주의사항은 다음과 같습니다. -1. 이 모드에서는 `global`(예: 폴리필)을 수정하는 종속성을 외부에 둘 수 없습니다. +1. 이 모드에서는 `global`(예: 폴리필)을 수정하는 의존성을 외부에 둘 수 없습니다. 2. 번들 실행에서 반환된 값은 다른 전역 생성자를 사용합니다. 번들 내부에서 발견된 오류는 서버 프로세스에서 `Error` 인스턴스가 되지 않습니다. [Source Code Structure](./structure.md)를 참조하세요 -- +- #### `basedir` - 2.2.0+ @@ -174,7 +174,7 @@ Vue 인스턴스를 Node.js 스트림으로 렌더링합니다. 컨텍스트 객 }) ``` -캐시 객체는 최소한 `get`과 `set`을 구현해야합니다. 또한 두번째 전달인자를 콜백으로 허용하면 `get`과 `has`는 선택적으로 비동기화 할수 있습니다. 이렇게하면 캐시에서 비동기 API를 사용할 수 있습니다. 예: redis 클라이언트 +캐시 객체는 최소한 `get`과 `set`을 구현해야합니다. 또한 두번째 전달인자를 콜백으로 허용하면 `get`과 `has`는 선택적으로 비동기화할 수 있습니다. 이렇게하면 캐시에서 비동기 API를 사용할 수 있습니다. 예: redis 클라이언트 ```js const renderer = createRenderer({ From 647e3affdcecfcca1f2fdeb0e5675dedcd03dfbe Mon Sep 17 00:00:00 2001 From: ChangJoo Park Date: Thu, 15 Jun 2017 20:40:20 +0900 Subject: [PATCH 055/239] Update hydration.md --- ko/hydration.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ko/hydration.md b/ko/hydration.md index 97fecf34..21d9021e 100644 --- a/ko/hydration.md +++ b/ko/hydration.md @@ -7,21 +7,25 @@ app.$mount('#app') ``` -서버가 이미 마크업을 렌더링 했으므로, 이를 버리고 모든 DOM 요소를 다시 만들필요는 없습니다. 대신 정적 마크 업을 "수화 (hydrate)"하여 상호작용하게 만들고 싶습니다. +서버가 이미 마크업을 렌더링 했으므로, 이를 버리고 모든 DOM 요소를 다시 만들 필요는 없습니다. 대신 정적 마크 업을 "수화 (hydrate)"하여 상호작용하게 만들고 싶습니다. 서버에서 렌더링 한 결과를 검사하면 앱의 루트 엘리먼트에 특수 속성을 확인할 수 있습니다. ```js
-

특수 속성 data-server-rendered은 클라이언트 측 Vue가 마크업이 서버에 의해 렌더링 되고 하이드레이션 모드로 마운트되어야 한다고 알립니다.

-

개발 모드에서 Vue는 클라이언트 측에서 만들어진 가상 DOM 트리가 서버에서 렌더링된 DOM구조와 일치함을 나타냅니다. 일치하지 않는 부분이 있으면 하이드레이션을 중단하고 기존 DOM을 삭제한 후 처음부터 렌더링 합니다. 배포 모드에서는 최대 성능을 위해 assert를 하지 않습니다.

-

하이드레이션 주의 사항

-

SSR + 클라이언트 하이드레이션을 사용할 때 주의해야하는 것들 중 하나는 브라우저에서 변경할 수 있는 특수한 HTML 구조입니다. 예를 들어 Vue 템플릿을 다음과 같이 작성한 경우입니다.

-

-  
-
hi
-

브라우저는 자동으로 안에 자동으로 주입합니다. 하지만, Vue가 생성한 가상 DOM에 을 주입하면 불일치가 발생합니다. 그러므로 동일하도록 만들기 위해 템플릿에 유효한 HTML을 작성해야합니다. -
-

-
``` + +특수 속성 `data-server-rendered`은 클라이언트 측 Vue가 마크업이 서버에 의해 렌더링 되고 하이드레이션 모드로 마운트되어야한다고 알립니다. +개발 모드에서 Vue는 클라이언트 측에서 만들어진 가상 DOM 트리가 서버에서 렌더링된 DOM구조와 일치함을 나타냅니다. 일치하지 않는 부분이 있으면 하이드레이션을 중단하고 기존 DOM을 삭제한 후 처음부터 렌더링 합니다. **배포 모드에서는 최대 성능을 위해 assert를 하지 않습니다.** + +### 하이드레이션 주의사항 + +SSR + 클라이언트 하이드레이션을 사용할 때 주의해야하는 것들 중 하나는 브라우저에서 변경할 수 있는 특수한 HTML 구조입니다. 예를 들어 Vue 템플릿을 다음과 같이 작성한 경우입니다. + +```html + + +
hi
+``` + +브라우저는 자동으로 ``를 `` 안에 자동으로 주입합니다. 하지만 Vue가 생성한 가상 DOM에 ``을 주입하면 불일치가 발생합니다. 그러므로 동일하도록 만들기 위해 템플릿에 유효한 HTML을 작성해야합니다. From 08e6d79b361d46374d9dfad5bfb80096f7d71ae4 Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Tue, 27 Jun 2017 18:14:05 +0300 Subject: [PATCH 056/239] [RU] Fixes (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Добавлены бэктики * routing.md исправлен пример * data.md исправлен пример * data.md добавлены бэктики * data.md исправление --- ru/data.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ru/data.md b/ru/data.md index 06ab5366..72971abf 100644 --- a/ru/data.md +++ b/ru/data.md @@ -138,7 +138,7 @@ export default context => { // заполнено состоянием, необходимым для рендеринга приложения. // Когда мы присоединяем состояние к контексту, и есть опция `template` // используемая для рендерера, состояние будет автоматически - // сериализовано и внедрено в HTML как window.__INITIAL_STATE__. + // сериализовано и внедрено в HTML как `window.__INITIAL_STATE__`. context.state = store.state resolve(app) @@ -215,7 +215,7 @@ if (window.__INITIAL_STATE__) { 2. **Загружать данные после отображения нового представления:** - Эта стратегия располагает логику загрузки данных на стороне клиента в функции компонента `beforeMount`. Это позволяет переключаться мгновенно при срабатывании навигации по маршруту, поэтому приложение ощущается более отзывчивым. Однако на момент отображения нового представления у него не будет полных данных. Поэтому необходимо иметь добавлять условие проверки загруженности состояния для каждого компонента, использующего эту стратегию. + Эта стратегия располагает логику загрузки данных на стороне клиента в функции компонента `beforeMount`. Это позволяет переключаться мгновенно при срабатывании навигации по маршруту, поэтому приложение ощущается более отзывчивым. Однако на момент отображения нового представления у него не будет полных данных. Поэтому необходимо добавлять условие проверки загруженности состояния для каждого компонента, использующего эту стратегию. Этого можно достичь с помощью глобальной примеси на клиенте: From c6dfb056787e1ef067fd3c9b01afd8554a0bd7bc Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 28 Jun 2017 13:01:03 +0800 Subject: [PATCH 057/239] Translate streaming.md via GitLocalize --- zh/streaming.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 zh/streaming.md diff --git a/zh/streaming.md b/zh/streaming.md new file mode 100644 index 00000000..43d623fa --- /dev/null +++ b/zh/streaming.md @@ -0,0 +1,30 @@ +# Streaming + +对于 `vue-server-renderer` 的基本 renderer 和 bundle renderer 都提供开箱即用的流式渲染功能。所有你需要做的就是,用 `renderToStream` 替代 `renderToString`: + +```js +const stream = renderer.renderToStream(context) +``` + +返回的值是 [Node.js stream](https://nodejs.org/api/stream.html): + +```js +let html = '' +stream.on('data', data => { + html += data.toString() +}) +stream.on('end', () => { + console.log(html) // 渲染完成 +}) +stream.on('error', err => { + // handle error... +}) +``` + +## 流式传输说明(Streaming Caveats) + +在流式渲染模式下,当 renderer 遍历虚拟 DOM 树(virtual DOM tree)时,会尽快发送数据。这意味着我们可以尽快获得"第一个 chunk",并开始更快地将其发送给客户端。 + +然而,当第一个数据 chunk 被发出时,子组件甚至可能不被实例化,它们的生命周期钩子也不会被调用。这意味着,如果子组件需要在其生命周期钩子函数中,将数据附加到渲染上下文(render context),当流(stream)启动时,这些数据将不可用。这是因为,大量上下文信息(context information)(如头信息(head information)或内联关键 CSS(inline critical CSS))需要在应用程序标记(markup)之前出现,我们基本上必须等待流(stream)完成后,才能开始使用这些上下文数据。 + +因此,如果你依赖由组件生命周期钩子函数填充的上下文数据,则**不建议**使用流式传输模式。 From 12e3881520da7a31988a1ba319d74bb64847f94c Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 28 Jun 2017 13:01:30 +0800 Subject: [PATCH 058/239] Translate structure.md via GitLocalize --- zh/structure.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 zh/structure.md diff --git a/zh/structure.md b/zh/structure.md new file mode 100644 index 00000000..88fe86ce --- /dev/null +++ b/zh/structure.md @@ -0,0 +1,114 @@ +# 源码结构 + +## 避免状态单例 + +当编写纯客户端(client-only)代码时,我们习惯于每次在新的上下文中对代码进行取值。但是,Node.js 服务器是一个长期运行的进程。当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享。 + +如基本示例所示,我们**为每个请求创建一个新的根 Vue 实例**。这与每个用户在自己的浏览器中使用新应用程序的实例类似。如果我们在多个请求之间使用一个共享的实例,很容易导致交叉请求状态污染(cross-request state pollution)。 + +因此,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例: + +```js +// app.js +const Vue = require('vue') +module.exports = function createApp (context) { + return new Vue({ + data: { + url: context.url + }, + template: `
访问的 URL 是: {{ url }}
` + }) +} +``` + +并且我们的服务器代码现在变为: + +```js +// server.js +const createApp = require('./app') +server.get('*', (req, res) => { + const context = { url: req.url } + const app = createApp(context) + renderer.renderToString(app, (err, html) => { + // 处理错误…… + res.end(html) + }) +}) +``` + +同样的规则也适用于 router、store 和 event bus 实例。你不应该直接从模块导出并将其导入到应用程序中,而是需要在 `createApp` 中创建一个新的实例,并从根 Vue 实例注入。 + +> 在使用带有 `{ runInNewContext: true }` 的 bundle renderer 时,可以消除此约束,但是由于需要为每个请求创建一个新的 vm 上下文,因此伴随有一些显著性能开销。 + +## 介绍构建步骤 + +到目前为止,我们还没有讨论过如何将相同的 Vue 应用程序提供给客户端。为了做到这一点,我们需要使用 webpack 来打包我们的 Vue 应用程序。事实上,我们可能需要在服务器上使用 webpack 打包 Vue 应用程序,因为: + +- 通常 Vue 应用程序是由 webpack 和 `vue-loader` 构建,并且许多 webpack 特定功能不能直接在 Node.js 中运行(例如通过 `file-loader` 导入文件,通过 `css-loader` 导入 CSS)。 +- 尽管 Node.js 最新版本能够完全支持 ES2015 特性,我们还是需要转译客户端代码以适应老版浏览器。这也会涉及到构建步骤。 + +所以基本看法是,对于客户端应用程序和服务器应用程序,我们都要使用 webpack 打包 - 服务器需要「服务器 bundle」然后用于服务器端渲染(SSR),而「客户端 bundle」会发送给浏览器,用于混合静态标记。 + +![architecture](https://cloud.githubusercontent.com/assets/499550/17607895/786a415a-5fee-11e6-9c11-45a2cfdf085c.png) + +我们将在后面的章节讨论规划结构的细节 - 现在,先假设我们已经将构建过程的规划都弄清楚了,我们可以在启用 webpack 的情况下编写我们的 Vue 应用程序代码。 + +## 使用 webpack 的源码结构 + +现在我们正在使用 webpack 来处理服务器和客户端的应用程序,大部分源码可以使用通用方式编写,可以使用 webpack 支持的所有功能。同时,在编写通用代码时,有一些[事项](./universal.md)要牢记在心。 + +一个基本项目可能像是这样: + +```bash +src +├── components +│   ├── Foo.vue +│   ├── Bar.vue +│   └── Baz.vue +├── App.vue +├── app.js # universal entry +├── entry-client.js # 仅运行于浏览器 +└── entry-server.js # 仅运行于服务器 +``` + +### `app.js` + +`app.js` 是我们应用程序的「通用 entry」。在纯客户端应用程序中,我们将在此文件中创建根 Vue 实例,并直接挂载到 DOM。但是,对于服务器端渲染(SSR),责任转移到纯客户端 entry 文件。`app.js` 简单地使用 export 导出一个 `createApp` 函数: + +```js +import Vue from 'vue' +import App from './App.vue' +// 导出一个工厂函数,用于创建新的 +// 应用程序、router 和 store 实例 +export function createApp () { + const app = new Vue({ + // 根实例简单的渲染应用程序组件。 + render: h => h(App) + }) + return { app } +} +``` + +### `entry-client.js`: + +客户端 entry 只需创建应用程序,并且将其挂载到 DOM 中: + +```js +import { createApp } from './app' +// 客户端特定引导逻辑…… +const { app } = createApp() +// 这里假定 App.vue 模板中根元素具有 `id="app"` +app.$mount('#app') +``` + +### `entry-server.js`: + +服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配(server-side route matching)和数据预取逻辑(data pre-fetching logic)。 + +```js +import { createApp } from './app' +export default context => { + const { app } = createApp() + return app +} +``` From a7c32cf3c8ab0a02d275f32867c0295fb8d7da99 Mon Sep 17 00:00:00 2001 From: Zhao Xiaoqiang Date: Wed, 28 Jun 2017 13:01:40 +0800 Subject: [PATCH 059/239] Translate SUMMARY.md via GitLocalize --- zh/SUMMARY.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 zh/SUMMARY.md diff --git a/zh/SUMMARY.md b/zh/SUMMARY.md new file mode 100644 index 00000000..5e394984 --- /dev/null +++ b/zh/SUMMARY.md @@ -0,0 +1,27 @@ +- [基本用法](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) From 36b3667282c23f7823e2ab17f9aaafd94d504bd6 Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 28 Jun 2017 13:01:41 +0800 Subject: [PATCH 060/239] Translate SUMMARY.md via GitLocalize --- zh/SUMMARY.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/zh/SUMMARY.md b/zh/SUMMARY.md index 5e394984..1a071fe2 100644 --- a/zh/SUMMARY.md +++ b/zh/SUMMARY.md @@ -1,21 +1,21 @@ - [基本用法](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) +- [编写通用代码](universal.md) +- [源码结构](structure.md) +- [路由和代码分割](routing.md) +- [数据预取和状态](data.md) +- [客户端混合](hydration.md) +- [Bundle Renderer 指引](bundle-renderer.md) +- [构建配置](build-config.md) +- [CSS 管理](css.md) +- [Head 管理](head.md) +- [缓存](caching.md) +- [流式渲染](streaming.md) +- [API 参考](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) + - [Renderer 选项](api.md#renderer-options) - [template](api.md#template) - [clientManifest](api.md#clientmanifest) - [inject](api.md#inject) @@ -24,4 +24,4 @@ - [basedir](api.md#basedir) - [cache](api.md#cache) - [directives](api.md#directives) - - [webpack Plugins](api.md#webpack-plugins) + - [webpack 插件](api.md#webpack-plugins) From 6bea720400c3995bf0d2d1d3b997fb60cf66b75e Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 28 Jun 2017 13:01:46 +0800 Subject: [PATCH 061/239] Translate universal.md via GitLocalize --- zh/universal.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 zh/universal.md diff --git a/zh/universal.md b/zh/universal.md new file mode 100644 index 00000000..2fbd9ee4 --- /dev/null +++ b/zh/universal.md @@ -0,0 +1,32 @@ +# 编写通用代码 + +在进一步介绍之前,让我们花点时间来讨论编写"通用"代码时的约束条件 - 即运行在服务器和客户端的代码。由于用例和平台 API 的差异,当运行在不同环境中时,我们的代码将不会完全相同。所以这里我们将会阐述你需要理解的关键事项。 + +## 服务器上的数据响应 + +在纯客户端应用程序(client-only app)中,每个用户会在他们各自的浏览器中使用新的应用程序实例。对于服务器端渲染,我们也希望如此:每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染(cross-request state pollution)。 + +因为实际的渲染过程需要确定性,所以我们也将在服务器上“预取”数据("pre-fetching" data) - 这意味着在我们开始渲染时,我们的应用程序就已经解析完成其状态。也就是说,将数据进行响应式的过程在服务器上是多余的,所以默认情况下禁用。禁用响应式数据,还可以避免将「数据」转换为「响应式对象」的性能开销。 + +## 组件声明周期钩子函数 + +由于没有动态更新,所有的生命周期钩子函数中,只有 `beforeCreate` 和 `created` 会在服务器端渲染(SSR)过程中被调用。这就是说任何其他声明周期钩子函数中的代码(例如 `beforeMount` 或 `mounted`),只会在客户端执行。 + +此外还需要注意的是,你应该避免在 `beforeCreate` 和 `created` 生命周期时产生全局副作用的代码,例如在其中使用 `setInterval` 设置 timer。在纯客户端(client-side only)的代码中,我们可以设置一个 timer,然后在 `beforeDestroy` 或 `destroyed` 生命周期时将其销毁。但是,由于在 SSR 期间并不会调用销毁钩子函数,所以 timer 将永远保留下来。为了避免这种情况,请将副作用代码移动到 `beforeMount` 或 `mounted` 生命周期中。 + +## 访问特定平台(Platform-Specific) API + +通用代码不可接受特定平台的 API,因此如果你的代码中,直接使用了像 `window` 或 `document`,这种仅浏览器可用的全局变量,则会在 Node.js 中执行时抛出错误,反之也是如此。 + +对于共享于服务器和客户端,但用于不同平台 API 的任务(task),建议将平台特定实现包含在通用 API 中,或者使用为你执行此操作的 library。例如,[axios](https://github.com/mzabriskie/axios) 是一个 HTTP 客户端,可以向服务器和客户端都暴露相同的 API。 + +对于仅浏览器可用的 API,通常方式是,在「纯客户端(client-only)」的生命周期钩子函数中惰性访问(lazily access)它们。 + +请注意,考虑到如果第三方 library 不是以上面的通用用法编写,则将其集成到服务器渲染的应用程序中,可能会很棘手。你*可能*要通过模拟(mock)一些全局变量来使其正常运行,但这只是 hack 的做法,并且可能会干扰到其他 library 的环境检测代码。 + +## 自定义指令 + +大多数自定义指令直接操作 DOM,因此会在服务器端渲染(SSR)过程中导致错误。有两种方法可以解决这个问题: + +1. 推荐使用组件作为抽象机制,并运行在「虚拟 DOM 层级(Virtual-DOM level)」(例如,使用渲染函数(render function))。 +2. 如果你有一个自定义指令,但是不是很容易替换为组件,则可以在创建服务器 renderer 时,使用 [`directives`](./api.md#directives) 选项所提供"服务器端版本(server-side version)"。 From fc96149ba34aead23dba2dea6d45ab1ca52ca999 Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 28 Jun 2017 13:02:25 +0800 Subject: [PATCH 062/239] Translate basic.md via GitLocalize --- zh/basic.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 zh/basic.md diff --git a/zh/basic.md b/zh/basic.md new file mode 100644 index 00000000..9a4dbcd0 --- /dev/null +++ b/zh/basic.md @@ -0,0 +1,144 @@ +# 基本用法 + +## 安装 + +```bash +npm install vue vue-server-renderer --save +``` + +我们将在整个指南中使用 NPM,但是还可以随意使用 [Yarn](https://yarnpkg.com/en/)。 + +#### 注意 + +- 推荐使用 Node.js 版本 6+。 +- `vue-server-renderer` 和 `vue` 必须匹配版本。 +- `vue-server-renderer` 依赖一些 Node.js 原生模块,因此只能在 Node.js 中使用。我们可能会提供一个更简单的构建,可以在将来在其他「JavaScript 运行时(runtime)」运行。 + +## 渲染一个 Vue 实例 + +```js +// 第 1 步:创建一个 Vue 实例 +const Vue = require('vue') +const app = new Vue({ + template: `
Hello World
` +}) +// 第 2 步:创建一个 renderer +const renderer = require('vue-server-renderer').createRenderer() +// 第 3 步:将 Vue 实例渲染为 HTML +renderer.renderToString(app, (err, html) => { + if (err) throw err + console.log(html) + // =>
Hello World
+}) +``` + +## 与服务器集成 + +在 Node.js 服务器中使用时相当简单直接,例如 [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: `
访问的 URL 是: {{ url }}
` + }) + renderer.renderToString(app, (err, html) => { + if (err) { + res.status(500).end('Internal Server Error') + return + } + res.end(` + + + Hello + ${html} + + `) + }) +}) +server.listen(8080) +``` + +## 使用一个页面模板 + +当你在渲染 Vue 应用程序时,renderer 只从应用程序生成 HTML 标记(markup)。在这个示例中,我们必须用一个额外的 HTML 页面包裹容器,来包裹生成的 HTML 标记。 + +为了简化这些,你可以直接在创建 renderer 时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中,例如 `index.template.html`: + +```html + + + Hello + + + + +``` + +Notice the `` comment -- this is where your app's markup will be injected. + +然后,我们可以读取和传输文件到 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. +}) +``` + +### 模板插值 + +模板还支持简单插值。给定如下模板: + +```html + + + + {{ title }} + + {{{ meta }}} + + + + + +``` + +我们可以通过传入一个"渲染上下文对象",作为 `renderToString` 函数的第二个参数,来提供插值数据: + +```js +const context = { + title: 'hello', + meta: ` + + + ` +} +renderer.renderToString(app, context, (err, html) => { + // page title will be "Hello" + // with meta tags injected +}) +``` + +也可以与 Vue 应用程序实例共享 `context` 对象,允许模板插值中的组件动态地注册数据。 + +此外,模板支持一些高级特性,例如: + +- 在使用 `*.vue` 组件时,自动注入「关键的 CSS(critical CSS)」; +- 在使用 `clientManifest` 时,自动注入「资源链接(asset links)和资源预加载提示(resource hints)」; +- 在嵌入 Vuex 状态进行客户端融合(client-side hydration)时,自动注入以及 XSS 防御。 + +在之后的指南中介绍相关概念时,我们将详细讨论这些。 From d6e199d521a0178a103d4bf9e56065e8d4cc865c Mon Sep 17 00:00:00 2001 From: Ryo Chikazawa Date: Wed, 28 Jun 2017 13:02:26 +0800 Subject: [PATCH 063/239] Translate basic.md via GitLocalize --- zh/basic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zh/basic.md b/zh/basic.md index 9a4dbcd0..cb624981 100644 --- a/zh/basic.md +++ b/zh/basic.md @@ -86,7 +86,7 @@ server.listen(8080) ``` -Notice the `` comment -- this is where your app's markup will be injected. +注意 `` 注释 -- 这里将是应用程序 HTML 标记注入的地方。 然后,我们可以读取和传输文件到 Vue renderer 中: From fca61a75a46bcd3bc6d175baf0fc8dc26e1538b5 Mon Sep 17 00:00:00 2001 From: lizhihua <275091674@qq.com> Date: Wed, 28 Jun 2017 13:02:31 +0800 Subject: [PATCH 064/239] Translate build-config.md via GitLocalize --- zh/build-config.md | 202 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 zh/build-config.md diff --git a/zh/build-config.md b/zh/build-config.md new file mode 100644 index 00000000..f3135aa7 --- /dev/null +++ b/zh/build-config.md @@ -0,0 +1,202 @@ +# 构建配置 + +我们假设你已经知道,如何为纯客户端(client-only)项目配置 webpack。服务器端渲染(SSR)项目的配置大体上与纯客户端项目类似,但是我们建议将配置分为三个文件:*base*, *client* 和 *server*。基本配置(base config)包含在两个环境共享的配置,例如,输出路径(output path),别名(alias)和 loader。服务器配置(server config)和客户端配置(client config),可以通过使用 [webpack-merge](https://github.com/survivejs/webpack-merge) 来简单地扩展基本配置。 + +## 服务器配置(Server Config) + +服务器配置,是用于生成传递给 `createBundleRenderer` 的 server bundle。它应该是这样的: + +```js +const merge = require('webpack-merge') +const nodeExternals = require('webpack-node-externals') +const baseConfig = require('./webpack.base.config.js') +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +module.exports = merge(baseConfig, { + // 将 entry 指向应用程序的 server entry 文件 + entry: '/path/to/entry-server.js', + // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import), + // 并且还会在编译 Vue 组件时, + // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。 + target: 'node', + // 对 bundle renderer 提供 source map 支持 + devtool: 'source-map', + // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports) + output: { + libraryTarget: 'commonjs2' + }, + // https://webpack.js.org/configuration/externals/#function + // https://github.com/liady/webpack-node-externals + // 外置化应用程序依赖模块。可以使服务器构建速度更快, + // 并生成较小的 bundle 文件。 + externals: nodeExternals({ + // 不要外置化 webpack 需要处理的依赖模块。 + // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件, + // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单 + whitelist: /\.css$/ + }), + // 这是将服务器的整个输出 + // 构建为单个 JSON 文件的插件。 + // 默认文件名为 `vue-ssr-server-bundle.json` + plugins: [ + new VueSSRServerPlugin() + ] +}) +``` + +在生成 `vue-ssr-server-bundle.json` 之后,只需将文件路径传递给 `createBundleRenderer`: + +```js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { + // ……renderer 的其他选项 +}) +``` + +又或者,你还可以将 bundle 作为对象传递给 `createBundleRenderer`。这对开发过程中的热重新是很有用的 - 具体请查看 HackerNews demo 的[参考设置](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js)。 + +### 扩展说明(Externals Caveats) + +请注意,在 `externals` 选项中,我们将 CSS 文件列入白名单。这是因为从依赖模块导入的 CSS 还应该由 webpack 处理。如果你导入依赖于 webpack 的任何其他类型的文件(例如 `*.vue`, `*.sass`),那么你也应该将它们添加到白名单中。 + +如果你使用 `runInNewContext: 'once'` 或 `runInNewContext: true`,那么你还应该将修改 `global` 的 polyfill 列入白名单,例如 `babel-polyfill`。这是因为当使用新的上下文模式时,**server bundle 中的代码具有自己的 `global` 对象。**由于在使用 Node 7.6+ 时,在服务器并不真正需要它,所以实际上只需在客户端 entry 导入它。 + +## 客户端配置(Client Config) + +客户端配置(client config)和基本配置(base config)大体上相同。显然你需要把 `entry` 指向你的客户端入口文件。除此之外,如果你使用 `CommonsChunkPlugin`,请确保仅在客户端配置(client config)中使用,因为服务器包需要单独的入口 chunk。 + +### 生成 `clientManifest` + +> 需要版本 2.3.0+ + +除了 server bundle 之外,我们还可以生成客户端构建清单(client build manifest)。使用客户端清单(client manifest)和服务器 bundle(server bundle),renderer 现在具有了服务器*和*客户端的构建信息,因此它可以自动推断和注入[资源预加载 / 数据预取指令(preload / prefetch directive)](https://css-tricks.com/prefetching-preloading-prebrowsing/),以及 css 链接 / script 标签到所渲染的 HTML。 + +好处是双重的: + +1. 在生成的文件名中有哈希时,可以取代 `html-webpack-plugin` 来注入正确的资源 URL。 +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) + +默认情况下,当提供 `template` 渲染选项时,资源注入是自动执行的。但是有时候,你可能需要对资源注入的模板进行更细粒度(finer-grained)的控制,或者你根本不使用模板。在这种情况下,你可以在创建 renderer 并手动执行资源注入时,传入 `inject: false`。 + +在 `renderToString` 回调函数中,你传入的 `context` 对象会暴露以下方法: + +- `context.renderStyles()` + + This will return inline `` タグを返します。詳細は [CSS Management](./css.md)の章を見てください。 + +もし `clientManifest` が提供されたら、返ってきたストリングはwebpackが放出したCSSファイルの``タグも含みます。 (例 : `extract-text-webpack-plugin`から抽出されたCSSや、`file-loader`でインポートされたCSS) + +- `context.renderState(options?: Object)` + + このメソッドは `context.state` をシリアライズし、 `window.__INITIAL_STATE__`ステートとして埋め込まれたインラインスクリプトを返します。 + +contextのステートキーとwindowのステートキーはどちらとも、オプションオブジェクトとして渡すことでカスタマイズできます。 + +```js + context.renderState({ + contextKey: 'myCustomState', + windowKey: '__MY_STATE__' + }) + // -> +``` + +- `context.renderScripts()` + - 必須 `clientManifest` + +このメソッドはクライアントアプリケーションを起動するのに必要な `` タグを返します。コードの中に非同期コード分割を使っている時、このメソッドは賢くも、インクルードされるべき正しい非同期チャンクを推論します。 + +- `context.renderResourceHints()` + - 必須 `clientManifest` + +このメソッドは、現在レンダリングされているページに必要な`` リソースヒントを返します。 デフォルト設定ではこのようになります: + +- ページに必要なJavaScriptやCSSファイルをプリロードする +- あとで必要な非同期JavaScriptチャンクをプリフェッチする + + ファイルのプリロードは[`shouldPreload`](./api.md#shouldpreload) オプションによってさらにカスタマイズが可能です。 + +- `context.getPreloadFiles()` + - 必須 `clientManifest` + +このメソッドは stringを返さない代わりに、プリロードされるべきアセットを表すファイルオブジェクトの配列を返します。これは HTTP/2サーバプッシュをプログラムで行うときに使えるでしょう。 + +`createBundleRenderer`に渡された`template`は`context`を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: + +```html + + + + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + + + + {{{ renderState() }}} + {{{ renderScripts() }}} + + +``` + +もし `template` を全く使っていないのなら、自分自身でstringを結合することができます。 diff --git a/ja/bundle-renderer.md b/ja/bundle-renderer.md new file mode 100644 index 00000000..54050e42 --- /dev/null +++ b/ja/bundle-renderer.md @@ -0,0 +1,55 @@ +# バンドルレンダラの紹介 + +## 根本的なサーバサイドレンダリングの問題 + +今までは、バンドルされたサーバサイドのコードが `require` によって直接使用されることを想定していました。 + +```js +const createApp = require('/path/to/built-server-bundle.js') +``` + +これはとても簡単です。しかしながらアプリケーションのソースコードを編集する度に、あなたはサーバを停止し再起動させる必要があるでしょう。この作業は開発の生産性を著しく損ないます。さらに、Node.js はソースマップをネイティブサポートしていません。 + +## バンドルレンダラの追加 + +`vue-server-renderer` はこの問題を解決するために、`createBundleRenderer` という API を提供しています。また、webpack の拡張プラグインを利用することで、サーババンドルはバンドルレンダラに渡すことができる特別な JSON ファイルとして生成されます。バンドルレンダラが1度生成されると、使用方法は通常のレンダラと一緒ですが、次のような利点があります。 + +- ビルトインソースマップのサポート ( webpack の設定オプションに `devtool: 'source-map'` を指定) +- 開発中、デプロイ中のホットリロード(更新されたバンドルや、再作成されたレンダラのインスタンスを読み込むだけです) +- クリティカル CSS の評価 (`*.vue` ファイルを利用しているとき): インライン CSS は、レンダリング中に利用されるコンポーネントによって必要とされます。詳細は [CSS](./css.md) をご覧ください。 +- [clientManifest](./api.md#clientmanifest) によるアセットの注入: 自動的に最適なディレクティブが推測され、プリロードとプリフェッチを実行します。また、初期レンダリング時にはコード分割チャンクを必要とします。 + +--- + +次のセクションで、バンドルレンダラと webpack のビルドの設定方法ついて説明します。今はすでに必要なものが準備できていると仮定しましょう。こちらはバンドルレンダラを生成し、使用する方法です: + +```js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer(serverBundle, { + runInNewContext: false, // recommended + template, // (optional) page template + clientManifest // (optional) client build manifest +}) +// inside a server handler... +server.get('*', (req, res) => { + const context = { url: req.url } + // No need to pass an app here because it is auto-created by the + // executing the bundle. Now our server is decoupled from our Vue app! + renderer.renderToString(context, (err, html) => { + // handle error... + res.end(html) + }) +}) +``` + +バンドルレンダラによって`rendertoString` が呼び出されると、バンドルによってエクスポートされた関数が自動的に実行され、(引数としてコンテキストを渡して)アプリケーションのインスタンスを生成し、レンダリングを実行します。 + +--- + +### `runInNewContext` オプション + +通常では、それぞれのレンダリング毎にバンドルレンダラは新しい V8 コンテキストを生成し、バンドル全体を再実行します。これにはいくつかの利点があります。例えば、私たちが以前に言及した "ステートフルシングルトン" 問題について心配する必要がなくなります。しかし、このモードにはかなりのパフォーマンスコストがかかります。なぜなら、アプリケーションを大きくなるにつれ、バンドル全体を再実行することは特にコストとなるからです。 + +`vue-server-renderer >= 2.3.0` では、このオプションは下位互換性のために依然として初期値が `true` にされています。しかし、できる限り `runInNewContext: false` にしておくことをオススメします。 + +`runInNewContext: false`の場合は、バンドルは引き続き**別なグローバルコンテキスト**として1度だけ評価されます。これにより、バンドルが誤ってサーバプロセスのグローバルオブジェクトを汚染してしまうことを防ぎます。通常との動作の違いは、それぞれのレンダリングの実行時に**新しい**コンテキストを生成しないことです。 diff --git a/ja/caching.md b/ja/caching.md new file mode 100644 index 00000000..91f9e6e2 --- /dev/null +++ b/ja/caching.md @@ -0,0 +1,83 @@ +# キャッシュ + +Vue の SSR は非常に高速ですが、コンポーネントインスタンスや仮想 DOM ノードの作成コストのため純粋な文字列ベースのテンプレートのパフォーマンスにはかないません。 SSR のパフォーマンスが重大である場合、キャッシュ戦略を賢く活用することで、応答時間が大幅に改善され、サーバーの負荷が軽減されます。 + +## ページレベルでのキャッシュ + +ほとんどの場合、サーバーレンダリングされたアプリケーションは外部データに依存するため、コンテンツは本質的には動的であり長期間キャッシュすることはできません。しかしながら、コンテンツがユーザー固有のものでない場合、(すなわち、同一URLが常にすべてのユーザに対して同じコンテンツをレンダリングする場合)、 [マイクロキャッシング](https://www.nginx.com/blog/benefits-of-microcaching-nginx/) という戦略を活用してアプリケーションのトラフィック処理能力を劇的に改善します。 + +これは通常 Nginx レイヤーで行われますが、 Node.js で実装することも可能です。 + +```js +const microCache = LRU({ + max: 100, + maxAge: 1000 // 重要: コンテンツの登録内容は1秒後に期限切れになります。 +}) +const isCacheable = req => { + // リクエストがユーザー固有のものかどうかチェックするロジックを実装します。 + // ユーザー固有でないページのみがキャッシュ可能です。 +} +server.get('*', (req, res) => { + const cacheable = isCacheable(req) + if (cacheable) { + const hit = microCache.get(req.url) + if (hit) { + return res.end(hit) + } + } + renderer.renderToString((err, html) => { + res.end(html) + if (cacheable) { + microCache.set(req.url, html) + } + }) +}) +``` + +コンテンツは1秒間だけキャッシュされるため、ユーザーに古いコンテンツが表示されることはありません。ただし、これはサーバーがキャッシュされたページごとに秒間最大1回のフルレンダリングを実行するだけであることを意味します。 + +## コンポーネントレベルでのキャッシュ + +`vue-server-renderer` には、コンポーネントレベルのキャッシュ機能が組み込まれています。それを有効にするにはレンダラーを作成する際に[キャッシュ実装](./api.md#cache)を有効にする必要があります。代表的な使用例は [lru-cache](https://github.com/isaacs/node-lru-cache) を渡すことです。 + +```js +const LRU = require('lru-cache') +const renderer = createRenderer({ + cache: LRU({ + max: 10000, + maxAge: ... + }) +}) +``` + +次に `serverCacheKey` 関数を実装してコンポーネントをキャッシュすることが出来ます。 + +```js +export default { + name: 'item', // required + props: ['item'], + serverCacheKey: props => props.item.id, + render (h) { + return h('div', this.item.id) + } +} +``` + +キャッシュ可能なコンポーネントは **一意の "name" オプションも定義しなければいけない**ことに注意してください。一意の名前を持つと、キャッシュキーがコンポーネントごとに異なるため、同一キーを返す2つのコンポーネントについて心配する必要はありません。 + +`serverCacheKey` から返されるキーはレンダリング結果を表すのに十分な情報を含んでいる必要があります。レンダリング結果が単に `props.item.id` によって決定される場合、上記は良い実装でしょう。しかしながら、同じIDを持つアイテムが時間の経過とともに変わる場合やレンダリング結果が他のプロパティに依存する場合、 他の変数を考慮して `getCacheKey` の実装を修正する必要があります。 + +定数を返すと、コンポーネントは常にキャッシュされ、単なる静的なコンポーネントには効果的です。 + +### いつコンポーネントキャッシュを使うか + +レンダリング中にレンダラーがコンポーネントのキャッシュにヒットした場合、キャッシュされた結果をサブツリー全体で直接再利用します。 つまり、次の場合にコンポーネントをキャッシュ**しない**でください。 + +- グローバルな状態に依存する子コンポーネントがあります。 +- レンダリング `context` に副作用をもたらす子コンポーネントがあります。 + +したがって、コンポーネントのキャッシングは、パフォーマンスのボトルネックに取り組むために慎重に適用する必要があります。 ほとんどの場合、単一インスタンスのコンポーネントをキャッシュする必要はなく、すべきではありません。キャッシングに適した最も一般的なコンポーネントのタイプは、大きな `v-for` リストで繰り返されるコンポーネントです。 これらのコンポーネントは通常、データベースコレクション内のオブジェクトを元にするため、一意のIDと最後に更新されたタイムスタンプを合わせて使用してキャッシュキーを生成するという単純なキャッシュ戦略を使用できます。 + +```js +serverCacheKey: props => props.item.id + '::' + props.item.last_updated +``` diff --git a/ja/css.md b/ja/css.md new file mode 100644 index 00000000..d51b7eb1 --- /dev/null +++ b/ja/css.md @@ -0,0 +1,110 @@ +# CSS の管理 + +CSS を管理するためのおすすめの方法は、シンプルに単一ファイルコンポーネントである `*.vue` の中で `` を使うことができます。 + +もし `import 'foo.css'` のように JavaScriptからCSSをインポートしたいならば、適切な loader の設定が必要です: + +```js +module.exports = { + // ... + module: { + rules: [ + { + test: /\.css$/, + // important: use vue-style-loader instead of style-loader + use: isProduction + ? ExtractTextPlugin.extract({ + use: 'css-loader', + fallback: 'vue-style-loader' + }) + : ['vue-style-loader', 'css-loader'] + } + ] + }, + // ... +} +``` + +## 依存関係からのスタイルのインポート + +NPM 依存で CSS をインポートするときに気を付けることがいくつかあります: + +1. サーバービルドで外部化しないでください。 +2. もし CSS抽出 + `CommonsChunkPlugin` でベンダー抽出を使用している場合、抽出されたベンダーのチャンクに抽出された CSS があれば、`extract-text-webpack-plugin` に問題が発生します。この問題を解決するためには、ベンダーのチャンクに CSS ファイルを含めないでください。クライアント側の webpack の設定例です: + +```js + module.exports = { + // ... + plugins: [ + // it is common to extract deps into a vendor chunk for better caching. + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function (module) { + // a module is extracted into the vendor chunk when... + return ( + // if it's inside node_modules + /node_modules/.test(module.context) && + // do not externalize if the request is a CSS file + !/\.css$/.test(module.request) + ) + } + }), + // extract webpack runtime & manifest + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest' + }), + // ... + ] + } +``` diff --git a/ja/data.md b/ja/data.md new file mode 100644 index 00000000..69c59dae --- /dev/null +++ b/ja/data.md @@ -0,0 +1,231 @@ +# データのプリフェッチとステート + +## データストア + +SSR をしているとき、基本的にはアプリケーションの「スナップショット」をレンダリングしています、したがって、アプリケーションがいくつかの非同期データに依存している場合においては、**それらのデータを、レンダリング処理を開始する前にプリフェッチして解決する必要があります**。 + +もうひとつの重要なことは、クライアントサイドでアプリケーションがマウントされる前に、クライアントサイドで同じデータを利用可能である必要があるということです。そうしないと、クライアントサイドが異なるステートを用いてレンダリングしてしまい、ハイドレーションが失敗してしまいます。 + +この問題に対応するため、フェッチされたデータはビューコンポーネントの外でも存続している必要があります。つまり特定の用途のデータストアもしくは "ステート・コンテナ" に入っている必要があります。サーバーサイドではレンダリングする前にデータをプリフェッチしてストアの中に入れることができます。さらにシリアライズして HTML にステートを埋め込みます。クライアントサイドのストアは、アプリケーションをマウントする前に、埋め込まれたステートを直接取得できます。 + +このような用途として、公式のステート管理ライブラリである [Vuex](https://github.com/vuejs/vuex/) を使っています。では `store.js` ファイルをつくって、そこに id に基づく item を取得するコードを書いてみましょう: + +```js +// store.js +import Vue from 'vue' +import Vuex from 'vuex' +Vue.use(Vuex) +// Promise を返すユニバーサルなアプリケーションを想定しています +// また、実装の詳細は割愛します +import { fetchItem } from './api' +export function createStore () { + return new Vuex.Store({ + state: { + items: {} + }, + actions: { + fetchItem ({ commit }, id) { + // store.dispatch() 経由でデータがフェッチされたときにそれを知るために、Promise を返します + return fetchItem(id).then(item => { + commit('setItem', { id, item }) + }) + } + }, + mutations: { + setItem (state, { id, item }) { + Vue.set(state.items, id, item) + } + } + }) +} +``` + +そして `app.js` を更新します: + +```js +// app.js +import Vue from 'vue' +import App from './App.vue' +import { createRouter } from './router' +import { createStore } from './store' +import { sync } from 'vuex-router-sync' +export function createApp () { + // ルーターとストアのインスタンスを作成します + const router = createRouter() + const store = createStore() + // ルートのステートをストアの一部として利用できるよう同期します + sync(store, router) + // アプリケーションのインスタンスを作成し、ルーターとストアの両方を挿入します + const app = new Vue({ + router, + store, + render: h => h(App) + }) + // アプリケーション、ルーター、ストアを公開します + return { app, router, store } +} +``` + +## ロジックとコンポーネントとの結び付き + +ではデータをプリフェッチするアクションをディスパッチするコードはどこに置けばよいでしょうか? + +フェッチする必要があるデータはアクセスしたルートによって決まります。またそのルートによってどのコンポーネントがレンダリングされるかも決まります。実のところ、与えられたルートに必要とされるデータは、そのルートでレンダリングされるコンポーネントに必要とされるデータでもあるのです。したがって、データをフェッチするロジックはルートコンポーネントの中に置くのが自然でしょう。 + +ルートコンポーネントではカスタム静的関数 `asyncData` が利用可能です。この関数はそのルートコンポーネントがインスタンス化される前に呼び出されるため `this` にアクセスできないことを覚えておいてください。ストアとルートの情報は引数として渡される必要があります: + +```html + + + +``` + +## サーバーサイドのデータ取得 + +`entry-server.js` において `router.getMatchedComponents()` を使ってルートにマッチしたコンポーネントを取得できます。そしてコンポーネントが `asyncData` を利用可能にしていればそれを呼び出すことができます。そしてレンダリングのコンテキストに解決したステートを付属させる必要があります。 + +```js +// entry-server.js +import { createApp } from './app' +export default context => { + return new Promise((resolve, reject) => { + const { app, router, store } = createApp() + router.push(context.url) + router.onReady(() => { + const matchedComponents = router.getMatchedComponents() + if (!matchedComponents.length) { + reject({ code: 404 }) + } + // マッチしたルートコンポーネントすべての asyncData() を呼び出します + Promise.all(matchedComponents.map(Component => { + if (Component.asyncData) { + return Component.asyncData({ + store, + route: router.currentRoute + }) + } + })).then(() => { + // すべてのプリフェッチのフックが解決されると、ストアには、 + // アプリケーションをレンダリングするために必要とされるステートが入っています。 + // ステートを context に付随させ、`template` オプションがレンダラーに利用されると、 + // ステートは自動的にシリアライズされ、HTML 内に `window.__INITIAL_STATE__` として埋め込まれます + context.state = store.state + resolve(app) + }).catch(reject) + }, reject) + }) +} +``` + +`template` を使うと `context.state` は自動的に最終的な HTML に `window.__INITIAL__` というかたちのステートとして埋め込まれます。クライアントサイドでは、アプリケーションがマウントされる前に、ストアがそのステートを取得します: + +```js +// entry-client.js +const { app, router, store } = createApp() +if (window.__INITIAL_STATE__) { + store.replaceState(window.__INITIAL_STATE__) +} +``` + +## クライアントサイドのデータ取得 + +クライアントサイドではデータ取得について 2つの異なるアプローチがあります: + +1. **ルートのナビゲーションの前にデータを解決する:** + +この方法では、アプリケーションは、遷移先のビューが必要とするデータが解決されるまで、現在のビューを保ちます。良い点は遷移先のビューがデータの準備が整い次第、フルの内容をダイレクトにレンダリングできることです。しかしながら、データの取得に時間がかかるときは、ユーザーは現在のビューで「固まってしまった」と感じてしまうでしょう。そのため、この方法を用いるときにはローディング・インジケーターを表示させることが推奨されます。 + +この方法は、クライアントサイドでマッチするコンポーネントをチェックし、グローバルなルートのフック内で `asyncData` 関数を実行することにより実装できます。重要なことは、このフックは初期ルートが ready になった後に登録するということです。そうすれば、サーバーサイドで取得したデータをもう一度無駄に取得せずに済みます。 + +```js + // entry-client.js + // ...関係のないコードは除外します + router.onReady(() => { + // asyncData を扱うためにルーターのフックを追加します。これは初期ルートが解決された後に実行します + // そうすれば(訳注: サーバーサイドで取得したために)既に持っているデータを冗長に取得しなくて済みます + // すべての非同期なコンポーネントが解決されるように router.beforeResolve() を使います + router.beforeResolve((to, from, next) => { + const matched = router.getMatchedComponents(to) + const prevMatched = router.getMatchedComponents(from) + // まだレンダリングされていないコンポーネントにのみ関心を払うため、 + // 2つのマッチしたリストに差分が表れるまで、コンポーネントを比較します + let diffed = false + const activated = matched.filter((c, i) => { + return diffed || (diffed = (prevMatched[i] !== c)) + }) + if (!activated.length) { + return next() + } + // もしローディングインジケーターがあるならば、 + // この箇所がローディングインジケーターを発火させるべき箇所です + Promise.all(activated.map(c => { + if (c.asyncData) { + return c.asyncData({ store, route: to }) + } + })).then(() => { + // ローディングインジケーターを停止させます + next() + }).catch(next) + }) + app.$mount('#app') + }) +``` + +1. **マッチするビューがレンダリングされた後にデータを取得する:** + +この方法ではビューコンポーネントの `beforeMount` 関数内にクライアントサイドでデータを取得するロジックを置きます。こうすればルートのナビゲーションが発火したらすぐにビューを切り替えられます。そうすればアプリケーションはよりレスポンスが良いと感じられるでしょう。しかしながら、遷移先のビューはレンダリングした時点ではフルのデータを持っていません。したがって、この方法を使うコンポーネントの各々がローディング中か否かの状態を持つ必要があります。 + +この方法はクライアントサイド限定のグローバルな mixin で実装できます: + +```js + Vue.mixin({ + beforeMount () { + const { asyncData } = this.$options + if (asyncData) { + // データが準備できた後に、コンポーネント内で `this.dataPromise.then(...)` して + // 他のタスクを実行できるようにするため、Promise にフェッチ処理を割り当てます + this.dataPromise = asyncData({ + store: this.$store, + route: this.$route + }) + } + } + }) +``` + +これら 2つの方法のどちらを選ぶかは、究極的には異なる UX のどちらを選ぶかの判断であり、構築しようとしているアプリケーションの実際のシナリオに基づいて選択されるべきものです。しかし、どちらの方法を選択したかにかかわらず、ルートコンポーネントが再利用されたとき(つまりルートは同じだがパラメーターやクエリが変わったとき。例えば `user/1` から `user/2`) へ変わったとき)には `asyncData` 関数は呼び出されるようにすべきです。これはクライアントサイド限定のグローバルな mixin でハンドリングできます: + +```js +Vue.mixin({ + beforeRouteUpdate (to, from, next) { + const { asyncData } = this.$options + if (asyncData) { + asyncData({ + store: this.$store, + route: to + }).then(next).catch(next) + } else { + next() + } + } +}) +``` + +--- + +ふぅ、コードが長いですね。これはどうしてかというと、ユニバーサルなデータ取得は、大抵の場合、サーバーサイドでレンダリングするアプリケーションの最も複雑な問題であり、また、今後、スムーズに開発を進めていくための下準備をしているためです。一旦ひな形が準備できてしまえば、あとは、それぞれのコンポーネントを記述していく作業は、実際のところ実に楽しいものになるはずです。 diff --git a/ja/head.md b/ja/head.md new file mode 100644 index 00000000..3bd25eea --- /dev/null +++ b/ja/head.md @@ -0,0 +1,100 @@ +# ヘッドの管理 + +アセットの挿入と同様に、ヘッドの管理も同じ考えに追従しています。つまり、コンポーネントのライフサイクルのレンダリング `context` に動的にデータを付随させ、そして `template` 内にデータを挿入できるという考えです。 + +そうするためには、ネストしたコンポーネントの内側で SSR コンテキストへアクセスできる必要があります。単純に `context` を `createApp()` へ渡し、これをルートインスタンスの `$options` で公開することができます。 + +```js +// app.js +export function createApp (ssrContext) { + // ... + const app = new Vue({ + router, + store, + // this.$root.$options.ssrContext というように、すべての子コンポーネントは this にアクセスできます + ssrContext, + render: h => h(App) + }) + // ... +} +``` + +これと同様のことが `provide/inject` 経由でも可能ですが、そうすると context が `$root` 上に存在することになるため、インジェクションを解決するコストを避けたほうが良いでしょう。 + +インジェクトされた context を用いて、タイトルを管理する単純な mixin を書くことができます: + +```js +// title-mixin.js +function getTitle (vm) { + // コンポーネントはシンプルに `title` オプションを提供し、 + // これには文字列または関数を入れることができます + const { title } = vm.$options + if (title) { + return typeof title === 'function' + ? title.call(vm) + : title + } +} +const serverTitleMixin = { + created () { + const title = getTitle(this) + if (title) { + this.$root.$options.ssrContext.title = title + } + } +} +const clientTitleMixin = { + mounted () { + const title = getTitle(this) + if (title) { + document.title = title + } + } +} +// VUE_ENV は webpack.DefinePlugin を使って挿入できます +export default process.env.VUE_ENV === 'server' + ? serverTitleMixin + : clientTitleMixin +``` + +このようにすれば、ルートコンポーネントはドキュメントのタイトルをコントロールするために context を利用することができます。 + +```js +// Item.vue +export default { + mixins: [titleMixin], + title () { + return this.item.title + } + asyncData ({ store, route }) { + return store.dispatch('fetchItem', route.params.id) + }, + computed: { + item () { + return this.$store.state.items[this.$route.params.id] + } + } +} +``` + +そしてタイトルは `template` 内でバンドルレンダラーに渡されます: + +```html + + + {{ title }} + + + ... + + +``` + +**メモ:** + +- XSS 攻撃を防ぐために double-mustache(HTML エスケープした挿入)を使うこと。 +- レンダリング中にタイトルをセットするコンポーネントがない場合に備えて、`context` オブジェクトを作成する際にはデフォルトのタイトルをセットするようにすべきです。 + +--- + +同様のやり方で、この mixin を包括的にヘッドを管理するユーティリティに容易に拡張できます。 diff --git a/ja/hydration.md b/ja/hydration.md new file mode 100644 index 00000000..4fe4e0e3 --- /dev/null +++ b/ja/hydration.md @@ -0,0 +1,32 @@ +# クライアントサイドでのハイドレーション + +`entry-client.js` において、以下の記述で私たちは簡単にアプリケーションをマウントします。 + +```js +// これは、ルート要素に id="app" をもつ App.vue テンプレートを想定します。 +app.$mount('#app') +``` + +サーバがマークアップを描画後に、この処理を実行し、すべての DOM を再生成することを私たちは当然したくありません。代わりに、静的なマークアップの"ハイドレート"とそれをインタラクティブに生成したいです。 + +もしあなたがサーバレンダリングの出力を調べたら、アプリケーションのルート要素が以下のような特別な属性を持っていることに気づくでしょう。 + +```js +
+``` + +この `data-server-rendered` という特別な属性は、クライアントサイドの Vue に、これがサーバ上で描画されたことを知らせ、この要素はハイドレーションモードでマウントされるはずです。 + +開発モードでは、Vue はクラインアントサイドで生成された仮想 DOM が、サーバで描画された DOM の構成とマッチしているか検証を行います。もしこれがマッチしていない場合、ハイドレーションを取りやめ、元の DOM を無視しスクラッチから描画を行います。**プロダクションモードでは、パフォーマンスの最大化のため、このアサーションは無効になります。** + +### ハイドレーション時の注意 + +サーバサイドレンダリングとクライアントサイドでのハイドレーションを行なった場合、ある特定の HTML の構造はブラウザによって変換されるかもしれないことがわかっています。例えば、あなたが Vueのテンプレート内に、以下のような記述をした場合です。 + +```html +
+ +
hi
+``` + +ブラウザは、自動で `` を `` に挿入します。しかし、Vue によって生成された仮想 DOM は、`` を含みません。そのため、ミスマッチが起こります。正しいマッチングを保証するために、あなたのテンプレート内では、必ず有効な HTML を記述してください。 diff --git a/ja/routing.md b/ja/routing.md new file mode 100644 index 00000000..41c71415 --- /dev/null +++ b/ja/routing.md @@ -0,0 +1,136 @@ +# ルーティングとコード分割 + +## `vue-router` によるルーティング + +サーバーコードが任意の URL を受け入れる `*` ハンドラを使用していることに気付いたかもしれません。これにより訪れた URL を Vue アプリケーションに渡し、クライアントとサーバーの両方に同一のルーティング設定を再利用することが可能になります! + +この目的のために公式の `vue-router` を使用することが推奨されています。まずはルーターを作成するファイルを作成しましょう。 `createApp` に似ていますが、 リクエストごとに新たなルーターインスタンスも必要となるため、 `createRouter` 関数をエクスポートします。 + +```js +// router.js +import Vue from 'vue' +import Router from 'vue-router' +Vue.use(Router) +export function createRouter () { + return new Router({ + mode: 'history', + routes: [ + // ... + ] + }) +} +``` + +そして `app.js` を更新します。 + +```js +// app.js +import Vue from 'vue' +import App from './App.vue' +import { createRouter } from './router' +export function createApp () { + // ルーターインスタンスを作成します + const router = createRouter() + const app new Vue({ + // ルーターをルートVueインスタンスに注入します + router, + render: h => h(App) + }) + // アプリケーションとルーターの両方を返します + return { app, router } +} +``` + +`entry-server.js` にサーバー側のルーティングロジックを実装する必要があります。 + +```js +// entry-server.js +import { createApp } from './app' +export default context => { + // 非同期のルートフックまたはコンポーネントが存在する可能性があるため、 + // レンダリングする前にすべての準備が整うまでサーバーが待機できるように + // プロミスを返します。 + return new Promise((resolve, reject) => { + const { app, router } = createApp() + // サーバーサイドのルーターの場所を設定します + router.push(context.url) + // ルーターが非同期コンポーネントとフックを解決するまで待機します + router.onReady(() => { + const matchedComponents = router.getMatchedComponents() + // 一致するルートがない場合、404で拒否します + if (!matchedComponents.length) { + reject({ code: 404 }) + } + // プロミスはレンダリングできるようにアプリケーションインスタンスを解決するべきです + resolve(app) + }, reject) + }) +} +``` + +サーバーバンドルがすでにビルドされていると仮定すると(再度になりますが、今はビルド設定は無視します)、サーバーでの使用方法は次のようになります。 + +```js +// server.js +const createApp = require('/path/to/built-server-bundle.js') +server.get('*', (req, res) => { + const context = { url: req.url } + createApp(context).then(app => { + renderer.renderToString(app, (err, html) => { + if (err) { + if (err.code === 404) { + res.status(404).end('Page not found') + } else { + res.status(500).end('Internal Server Error') + } + } else { + res.end(html) + } + }) + }) +}) +``` + +## コード分割 + +コード分割やアプリケーションの部分的な遅延ローディングは初期レンダリングのためにブラウザがダウンロードする必要のあるアセットの量を減らすのに役立ち、巨大なバンドルを持つアプリケーションの TTI (操作可能になるまでの時間)を大幅に改善します。重要なことは初期画面では"必要なものだけを読み込む"ということです。 + +Vue は非同期コンポーネントを最重要コンセプトとして提供しており、 [webpack 2の動的インポートをコード分割点として使用することへのサポート](https://webpack.js.org/guides/code-splitting-async/) と組み合わせることも可能です。そのためにすべきことは以下です。 + +```js +// これを... +import Foo from './Foo.vue' +// このように変えます。 +const Foo = () => import('./Foo.vue') +``` + +純粋なクライアントサイドの Vue アプリケーションを構築する場合、これはどんなシナリオでも機能するでしょう。ただし、これをサーバーサイドレンダリングで使用する場合はいくつかの制限があります。まず、レンダリングを開始する前にサーバー上のすべての非同期コンポーネントを先に解決する必要があります。そうしなければ、マークアップ内に空のプレースホルダが表示されます。クライアント側では、ハイドレーションを開始する前にこれを行う必要があります。そうしなければ、クライアントはコンテンツの不一致エラーに陥ります。 + +アプリケーション内の任意の場所で非同期コンポーネントを使用するのは少し難解です(これは将来的に改善される可能性があります)。 ただし、**ルートレベルで行うとシームレスに動作します**(すなわち、ルート設定で非同期コンポーネントを使用する)。ルートを解決する際に、 `vue-router` は一致した非同期コンポーネントを自動的に解決するためです。 必要なことは、サーバーとクライアントの両方で `router.onReady` を使用することです。すでにサーバーのエントリーで行ったので、クライアントのエントリーを更新するだけです。 + +```js +// entry-client.js +import { createApp } from './app' +const { app, router } = createApp() +router.onReady(() => { + app.$mount('#app') +}) +``` + +非同期ルートコンポーネントを使用したルート設定の例: + +```js +// router.js +import Vue from 'vue' +import Router from 'vue-router' +Vue.use(Router) +export function createRouter () { + return new Router({ + mode: 'history', + routes: [ + { path: '/', component: () => import('./components/Home.vue') }, + { path: '/item/:id', component: () => import('./components/Item.vue') } + ] + }) +} +``` diff --git a/ja/streaming.md b/ja/streaming.md new file mode 100644 index 00000000..91cd9354 --- /dev/null +++ b/ja/streaming.md @@ -0,0 +1,30 @@ +# ストリーミング + +`vue-server-renderer` は、基本的なレンダラーとバンドルレンダラーの両方のためにストリームレンダリングをサポートします。`renderToString` の代わりに`renderToStream`を使用するだけです: + +```js +const stream = renderer.renderToStream(context) +``` + +返り値は [Node.js stream](https://nodejs.org/api/stream.html) です: + +```js +let html = '' +stream.on('data', data => { + html += data.toString() +}) +stream.on('end', () => { + console.log(html) // render complete +}) +stream.on('error', err => { + // handle error... +}) +``` + +## ストリーミングに関する注意事項 + +ストリームレンダリングモードでは、レンダラーが仮想 DOM ツリーを横断するときに、できるだけ早くデータを出力します。つまり、より早く「最初のチャンク」を取得し、それをクライアントにすばやく出力し始めることを意味します。 + +しかし、最初のデータチャンクが発行したときに子コンポーネントがまだインスタンス化されていないと、ライフサイクルフックが呼び出されることはありません。つまり、子コンポーネントがライフサイクルフック内のレンダリングコンテキストにデータを添付する必要がある場合、これらのデータはストリームの開始時に使用できません。アプリケーションは全体の HTML の表示の前に多くのコンテキスト情報(ヘッド情報やインラインに書かれたクリティカル CSS など)を表示する必要があるため、これらのコンテキストデータを使用する前にストリームの完了を待つ必要があります。 + +したがって、コンポーネントライフサイクルフックによって読み込まれたコンテキストデータに依存する場合は、ストリーミングモードを使用することは**推奨されません**。 diff --git a/ja/structure.md b/ja/structure.md new file mode 100644 index 00000000..a156b862 --- /dev/null +++ b/ja/structure.md @@ -0,0 +1,114 @@ +# ソースコードの構造 + +## ステートフルなシングルトンは避ける + +クライアントのみのコードを書くとき、私たちはコードが毎回新しいコンテキストで評価されるという事実に慣れています。しかし、 Node.js サーバーは長時間実行されるプロセスです。私たちのコードがプロセスに要求されるとき、それは一度評価されメモリにとどまります。つまりシングルトンのオブジェクトを作成したとき、それは全ての受信リクエスト間でシェアされると言うことです。 + +基本的な例としては、私たちは **リクエストごとに新しいルート Vue インスタンスを作成します**。それは各ユーザがそれぞれのブラウザでアプリケーションの新しいインスタンスを使用することに似ています。もし私たちが複数のリクエストをまたいでインスタンスを共有すると、それは容易にクロスリクエスト状態の汚染につながるでしょう。 + +そのため、直接アプリケーションのインスタンスを作成するのではなく、各リクエストで繰り返し実行される新しいアプリケーションのインスタンスを作成するファクトリ関数を公開する必要があります: + +```js +// app.js +const Vue = require('vue') +module.exports = function createApp (context) { + return new Vue({ + data: { + url: context.url + }, + template: `
The visited URL is: {{ url }}
` + }) +} +``` + +そして私たちのサーバーコードはこうなります: + +```js +// server.js +const createApp = require('./app') +server.get('*', (req, res) => { + const context = { url: req.url } + const app = createApp(context) + renderer.renderToString(app, (err, html) => { + // エラーをハンドリングします + res.end(html) + }) +}) +``` + +同じルールがルータ、ストア、イベントバスのインスタンスに適用されます。モジュールから直接エクスポートしアプリケーションにインポートするのでは無く、 `createApp` で新しいインスタンスを作成し、ルート Vue インスタンスから注入する必要があります。 + +> `{ runInNewContext: true }` でバンドルレンダラを使用するとき、その制約を取り除くことが可能です。しかし各リクエストに対して新しい VM コンテキストを作成する必要があるため、いくらか重大なパフォーマンスコストがかかります。 + +## ビルドステップの紹介 + +これまでは、同じ Vue アプリケーションをクライアントへ配信する方法を論じてはいませんでした。これを行うには、webpack を使用して Vue アプリケーションをバンドルする必要があります。実際、webpack を使用して Vue アプリケーションをサーバーにバンドルしたいと思っているのはおそらく次の理由によるものです。 + +- 典型的な Vue アプリケーションは webpack と `vue-loader` によってビルドされ、 `file-loader` 経由でのファイルのインポートや`css-loader` 経由でCSSをインポートなどの多くの webpack 固有の機能は Node.jsで直接動作しません。 +- Node.jsの最新バージョンはES2015の機能を完全にサポートしていますが、古いブラウザに対応するためにクライアントサイドのコードをトランスパイルする必要があります。これはビルドステップにも再び関係します。 + +従って基本的な考え方は webpack を使用してクライアントとサーバー両方をバンドルすることです。サーバーバンドルはサーバーによって SSR のために要求され、クライアントバンドルは静的なマークアップのためにブラウザに送信されます。 + +![architecture](https://cloud.githubusercontent.com/assets/499550/17607895/786a415a-5fee-11e6-9c11-45a2cfdf085c.png) + +セットアップの詳細については次のセクションで議論されます。今のところ、ビルドのセットアップが分かっていると仮定すると、webpack を有効にして Vue アプリケーションコードを書くことが可能になっています。 + +## Webpackによるコード構造 + +webpack を使用してサーバーとクライアントのアプリケーションを処理しているので、ソースコードの大部分はユニバーサルに書かれており、すべての webpack の機能にアクセスできます。 同時に、ユニバーサルなコードを書くときに留意すべき[いくつかあります。](./universal.md) + +シンプルなプロジェクトは以下のようになります: + +```bash +src +├── components +│   ├── Foo.vue +│   ├── Bar.vue +│   └── Baz.vue +├── App.vue +├── app.js # 共通のエントリ +├── entry-client.js # ブラウザでのみ実行されます +└── entry-server.js # サーバでのみ実行されます +``` + +### `app.js` + +`app.js` はアプリケーションのユニバーサルエントリーです。クライアントアプリケーションでは、このファイルにルート Vue インスタンスを作成し、DOM に直接マウントします。しかし、SSRの場合は責務はクライアント専用のエントリファイルに映されます。`app.js` はシンプルに `createApp` 関数をエクスポートします: + +```js +import Vue from 'vue' +import App from './App.vue' +// 新しいアプリケーション・ルータ・ストアを作成するためのファクトリ関数をエクスポートします +// インスタンス +export function createApp () { + const app = new Vue({ + // ルートインスタンスは単にAppコンポーネントをレンダリングします + render: h => h(App) + }) + return { app } +} +``` + +### `entry-client.js`: + +クライアントエントリは単にアプリケーションを作成しそれをDOMにマウントします: + +```js +import { createApp } from './app' +// クライアント固有の初期化ロジック +const { app } = createApp() +// これは App.vue テンプレートのルート要素が id="app" だからです。 +app.$mount('#app') +``` + +### `entry-server.js`: + +サーバーエントリはレンダリングごとに繰り返し呼び出すことができる関数をデフォルトでエクスポートします。現時点では、アプリケーションインスタンスを作成して返す以外のことはほとんど行いませんが、後でサーバーサイドのルートマッチングとデータプリフェッチロジックを実行します。 + +```js +import { createApp } from './app' +export default context => { + const { app } = createApp() + return app +} +``` diff --git a/ja/universal.md b/ja/universal.md new file mode 100644 index 00000000..f2bd9713 --- /dev/null +++ b/ja/universal.md @@ -0,0 +1,30 @@ +# ユニバーサルなコードを書く + +サーバサイドレンダリングについて、さらに見ていく前に、"ユニバーサル"なコード(サーバーとクライアントの両方で動作するコード)を記述するときの制約について考えてみましょう。ユースケースとプラットフォームの API の相違により、異なる環境で実行したコードの動作は全く同じものにはなりません。ここでは、サーバーサイドレンダリングを行う上で、知っておく必要がある重要な項目について説明します。 + +## サーバー上でのデータリアクティビテイ + +クライアントだけで実行するアプリでは、全てのユーザーがブラウザでアプリケーションの新しいインスタンスを使用します。サーバーサイドレンダリングでも、同じ振る舞いが必要とされます: すなわち、複数のリクエストに跨った状態の汚染がないよう各リクエストは新しく独立したアプリケーションのインスタンスが必要になります。 + +実際のレンダリングプロセスは決定的であることが求められるので、サーバー上でデータを"プリフェッチ"することもあります。これはレンダリングを開始する時、アプリケーションの状態は既に解決済みであることを意味します。つまり、サーバー上では、データがリアクティブである必要はないので、デフォルトで無効になっています。データをリアクティブにしないことで、データをリアクティブなオブジェクトに変換する際のパフォーマンスコストを無視できます。 + +## コンポーネントライフサイクルフック + +動的な更新がないので、ライフサイクルフックのうち、`beforeCreate` と `created` のみが SSR 中に呼び出されます。つまり、 `beforeMount` や `mounted` などの他のコンポーネントサイクルフックは、クライアントでのみ実行されます。 + +## プラットフォーム固有の API にアクセスする + +ユニバーサルコードでは、プラットフォーム固有の API へのアクセスは想定されていないので、`window` や `document` といったブラウザ環境のグローバル変数を直接使用すると、Node.js ではエラーが発生します。 + +サーバーとクライアントでコードを共有するものの、タスクが使用する API がプラットフォームによって異なる場合は、プラットフォーム固有の実装を ユニバーサル な API の内部でラップするか、それを行うライブラリを使用することをお勧めします。例えば、[axios](https://github.com/mzabriskie/axios) は、サーバーとクライアントの両方に同じ API を提供する HTTP クライアントです。 + +ブラウザ API を利用する際の一般的なアプローチは、クライアントだけで実行されるライフサイクルの中で遅延的にアクセスすることです。 + +サードパーティライブラリがユニバーサルに使用することを考慮していない場合、それをサーバーサイドレンダリングするアプリケーションに統合することは難しいので注意してください。グローバル変数のいくつかをモックすることで動かすことができるようになる *かも*しれませんが、それはハックであり、他のライブラリの環境検出のコードを妨げる恐れがあります。 + +## カスタムディレクティブ + +ほとんどの カスタムディレクティブ は直接 DOM を操作するため、SSR 中にエラーが発生します。これを回避するには、2つの方法があります: + +1. 抽象化の仕組みとしてコンポーネントを使用し、カスタムディレクティブの代わりに仮想 DOM レベル(例えば、render 関数を使用すること)で実装することをお勧めします。 +2. コンポーネントに簡単に置き換えができないカスタムディレクティブの場合、サーバーレンダラーを生成する際の [`directives`](./api.md#directives) オプションを使用して、そのオプションの "サーバーサイドのバージョン" を用意することで回避できます。 From d325de4ccffda9b144db2c907eda271d2f8f0bdd Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 02:04:40 +0900 Subject: [PATCH 078/239] Translate build-config.md via GitLocalize (#79) --- ja/build-config.md | 121 ++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/ja/build-config.md b/ja/build-config.md index e5445436..9c593e8c 100644 --- a/ja/build-config.md +++ b/ja/build-config.md @@ -1,10 +1,10 @@ # ビルド設定 -クライアントサイドで完結するプロジェクトのwebpack設定は既に知っての通りでしょう。 SSRプロジェクトにおいても大枠は似たようなものですが、設定ファイルを3つのファイル(*base*,*client*,*server*)に分けることを提案しています。base設定は出力パス、エイリアス、ローダーのような、clientとserver両方の環境に共有される設定を含み、server設定とclient設定は単純に、 [webpack-merge](https://github.com/survivejs/webpack-merge)を使って、base設定を拡張することができるものです。 +クライアントサイドで完結するプロジェクトの webpack 設定は既に知っての通りでしょう。 SSR プロジェクトにおいても大枠は似たようなものですが、設定ファイルを 3 つのファイル(*base*,*client*,*server*)に分けることを提案しています。base 設定は出力パス、エイリアス、ローダーのような、client と server 両方の環境に共有される設定を含み、server 設定と client 設定は単純に、 [webpack-merge](https://github.com/survivejs/webpack-merge) を使って、base 設定を拡張することができるものです。 -## server設定 +## server 設定 -server設定は`createBundleRenderer`に渡されるサーババンドルを生成するために作られるもので、次のようになります: +server 設定は`createBundleRenderer`に渡されるサーババンドルを生成するために作られるもので、次のようになります: ```js const merge = require('webpack-merge') @@ -12,70 +12,70 @@ const nodeExternals = require('webpack-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. + // これにより、webpack は Node に適した方法で動的なインポートを処理でき、 + // Vue コンポーネントをコンパイルするときにサーバー指向のコードを出力するよう + // `vue-loader`に指示する target: 'node', - // For bundle renderer source map support + // バンドルレンダラーのソースマップのサポート devtool: 'source-map', - // This tells the server bundle to use Node-style exports + // Node スタイルのエクスポートを使用するようにサーバーバンドルに指示する 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 - // you should also whitelist deps that modifies `global` (e.g. polyfills) + // webpack で処理する必要がある依存関係を外部化しない + // ここに例として、生の * .vue ファイルのようなファイルタイプを追加できる + // `グローバル` (例 ポリフィル) を変更する deps もホワイトリストに登録する必要がある 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` + // これはサーバービルドの出力全体を + // 1つの JSON ファイルに変換するプラグイン。 + // デフォルトのファイル名は `vue-ssr-server-bundle.json` plugins: [ new VueSSRServerPlugin() ] }) ``` - `vue-ssr-server-bundle.json`が生成されたら、ファイルパスを `createBundleRenderer`に渡します: + `vue-ssr-server-bundle.json`が生成された後、ファイルパスを単純に `createBundleRenderer`に渡します: ```js const { createBundleRenderer } = require('vue-server-renderer') const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { - // ...other renderer options + // 他の描画オプション... }) ``` -別の方法として、 バンドルをオブジェクトとして`createBundleRenderer`に渡すことも可能で、これは開発中のホットリロードに対して便利です。 参考として [HackerNewsの設定](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js) を見てみてください。 +別の方法として、 バンドルをオブジェクトとして`createBundleRenderer`に渡すことも可能です。これは開発中のホットリロードに対して便利で、[参考のセットアップとして](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js) HackerNews の設定を見てみてください。 -### externalsの注意 +### externals の注意 - CSSファイルを`externals`オプションにホワイトリスト登録していることに注目してください。その理由は、依存関係からインポートされるCSS はwebpackによって処理されないといけないからです。 もし同じようにwebpackに依存する他のタイプのファイルをインポートしているなら、 (例: `*.vue`, `*.sass`)、 それらも同じようにホワイトリストに加えなければいけません。 + CSS ファイルを`externals` オプションにホワイトリスト登録していることに注目してください。その理由は、依存関係からインポートされる CSS は webpack によって処理されないといけないからです。 もし同じように webpack に依存する他のタイプのファイルをインポートしているなら、 (例: `*.vue`, `*.sass`)、 それらも同じようにホワイトリストに加えなければいけません。 ホワイトリスト登録する他のタイプのモジュールは、例えば `babel-polyfill`のような`global`を修正するポリフィルです。なぜなら、サーババンドルの中のコードは独自の ** `global` **オブジェクトを持っているからです。Node7.6以降を使っていればサーバに`babel-polyfill`はあまり必要ないので、単純にクライアントエントリーにインポートする方が簡単です。 -## client設定 +## client 設定 -client設定はbase設定とほぼ同じままです。言うまでもなく、クライアント側のエントリーファイルに`entry`を示す必要があります。またそれとは別に、もし`CommonsChunkPlugin`使っていたら、それがclient設定だけで使われていることを確認しておかないといけません。なぜなら、サーババンドルは単一のエントリーチャンクを要求するからです。 +client 設定は base 設定とほぼ同じままです。言うまでもなく、クライアント側のエントリファイルに `entry` を示す必要があります。またそれとは別に、 `CommonsChunkPlugin` 使っていたら、それが client 設定だけで使われていることを確認しておかないといけません。なぜなら、サーババンドルは単一のエントリーチャンクを要求するからです。 -### `clientManifest` の作成 +### `clientManifest` を生成する -> 必須 version 2.3.0以降 +> version 2.3.0 以降必須 -サーババンドルに加えて、クライアントビルドマニフェストを作成することもできます。レンダラーは、クライアントマニフェストとサーババンドルでサーバ側*と*クライアント側の両方のビルド情報を持つことになり、 レンダリングされたHTMLに[preload / prefetch directives](https://css-tricks.com/prefetching-preloading-prebrowsing/)やCSSのlinkやscriptタグを自動的に挿入することができます。 +サーババンドルに加えて、クライアントビルドマニフェストを作成することもできます。レンダラーは、クライアントマニフェストとサーババンドルでサーバ側*と*クライアント側の両方のビルド情報を持つことになり、 レンダリングされた HTML に [preload / prefetch directives](https://css-tricks.com/prefetching-preloading-prebrowsing/) や CSS の link や script タグを自動的に挿入することができます。 これには2重の恩恵があります: -1. 生成されたファイル名にハッシュがある時に、正しいURLを注入する`html-webpack-plugin` の代替になります。 -2. webpackのオンデマンドコード分割機能(code spliting)を利用するバンドルをレンダリングする時に、最適なチャンクがpreloaded / prefetchedされるのを保証でき、かつ、クライアントに対するウォーターフォールリクエストを避けるために、必要な非同期チャンクに``タグを挿入することができます。そのようにしてTTI (time-to-interactive)が改善します。 +1. 生成されたファイル名にハッシュがある時に、正しい URL を注入する `html-webpack-plugin` の代替になります。 +2. webpack のオンデマンドコード分割機能(code spliting)を利用するバンドルを描画する時に、最適なチャンクが preloaded / prefetched されるのを保証でき、かつ、クライアントに対するウォーターフォールリクエストを避けるために、必要な非同期チャンクに``タグを挿入することができます。そのようにして TTI (time-to-interactive) が改善します。 -クライアントマニフェストを利用するためには、client設定はこのようになります: +クライアントマニフェストを利用するためには、client 設定はこのようになります: ```js const webpack = require('webpack') @@ -85,15 +85,15 @@ const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') module.exports = merge(baseConfig, { entry: '/path/to/entry-client.js', plugins: [ - // Important: this splits the webpack runtime into a leading chunk - // so that async chunks can be injected right after it. - // this also enables better caching for your app/vendor code. + // 重要: webpack ランタイムを先頭のチャンクに分割して、 + // 直後に非同期チャンクを挿入できるようにする + // これにより、アプリ/ベンダーコードのキャッシングも改善される new webpack.optimize.CommonsChunkPlugin({ name: "manifest", minChunks: Infinity }), - // This plugins generates `vue-ssr-client-manifest.json` in the - // output directory. + // このプラグインは、出力ディレクトリに + // `vue-ssr-client-manifest.json` を生成する new VueSSRClientPlugin() ] }) @@ -112,47 +112,47 @@ const renderer = createBundleRenderer(serverBundle, { }) ``` -この設定で、コード分割されたビルドのためにサーバ側でレンダリングされるHTMLはこのようになります(すべて自動でインジェクトされます)。 +この設定で、コード分割されたビルドのためにサーバ側で描画される HTML はこのようになります(すべて自動でインジェクトされます): ```html - + - + - -
async
- - - - - +
+
async
+
+ ``` ### 手動でのアセットインジェクション -デフォルト設定で、アセットインジェクションはあなたが作成した`template`レンダリングオプションで自動に行われます。 しかし、アセットがどのようにテンプレートにインジェクトされるかをより細かくコントロールしたい時もあるでしょうし、あるいはテンプレートを使わない時もあるかもしれません。そのような場合にはレンダラーを作る時に`inject: false`を渡せば、手動でアセットインジェクションを行うことができます。 +デフォルト設定で、アセットインジェクションはあなたが作成した`template`描画オプションで自動に行われます。 しかし、アセットがどのようにテンプレートにインジェクトされるかをより細かくコントロールしたい時もあるでしょうし、あるいはテンプレートを使わない時もあるかもしれません。そのような場合にはレンダラを作る時に `inject: false` を渡せば、手動でアセットインジェクションを行うことができます。 -渡した`context`オブジェクトは`renderToString`コールバックで、 次のメソッドを持ちます: +渡した `context` オブジェクトは `renderToString` コールバックで、 次のメソッドを持ちます: - `context.renderStyles()` -これは、レンダリング中に使われた`*.vue`コンポーネントから集めた全てのクリティカルCSSを含んだ`` タグを返します。詳細は [CSS Management](./css.md)の章を見てください。 +これは、描画中に使われた `*.vue` コンポーネントから集めた全てのクリティカル CSS を含んだ`` タグを返します。詳細は [ CSS Management](./css.md)の章を見てください。 -もし `clientManifest` が提供されたら、返ってきたストリングはwebpackが放出したCSSファイルの``タグも含みます。 (例 : `extract-text-webpack-plugin`から抽出されたCSSや、`file-loader`でインポートされたCSS) +`clientManifest` が提供されたら、返ってきた文字列は webpack が放出した CSS ファイルの``タグも含みます。 (例 : `extract-text-webpack-plugin` から抽出された CSS や、 `file-loader` でインポートされた CSS) - `context.renderState(options?: Object)` このメソッドは `context.state` をシリアライズし、 `window.__INITIAL_STATE__`ステートとして埋め込まれたインラインスクリプトを返します。 -contextのステートキーとwindowのステートキーはどちらとも、オプションオブジェクトとして渡すことでカスタマイズできます。 +context のステートキーと window のステートキーはどちらとも、オプションオブジェクトとして渡すことでカスタマイズできます。 ```js context.renderState({ @@ -170,33 +170,28 @@ contextのステートキーとwindowのステートキーはどちらとも、 - `context.renderResourceHints()` - 必須 `clientManifest` -このメソッドは、現在レンダリングされているページに必要な`` リソースヒントを返します。 デフォルト設定ではこのようになります: +このメソッドは、現在描画されているページに必要な `` リソースヒントを返します。 デフォルト設定ではこのようになります: -- ページに必要なJavaScriptやCSSファイルをプリロードする -- あとで必要な非同期JavaScriptチャンクをプリフェッチする +- ページに必要な JavaScript や CSS ファイルをプリロードする +- あとで必要な非同期 JavaScript チャンクをプリフェッチする - ファイルのプリロードは[`shouldPreload`](./api.md#shouldpreload) オプションによってさらにカスタマイズが可能です。 + ファイルのプリロードは [`shouldPreload` ](./api.md#shouldpreload) オプションによってさらにカスタマイズが可能です。 - `context.getPreloadFiles()` - 必須 `clientManifest` -このメソッドは stringを返さない代わりに、プリロードされるべきアセットを表すファイルオブジェクトの配列を返します。これは HTTP/2サーバプッシュをプログラムで行うときに使えるでしょう。 +このメソッドは文字列を返さない代わりに、プリロードされるべきアセットを表すファイルオブジェクトの配列を返します。これは HTTP/2 サーバプッシュをプログラムで行うときに使えるでしょう。 -`createBundleRenderer`に渡された`template`は`context`を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: +`createBundleRenderer` に渡された`template`は`context`を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: ```html - {{{ renderResourceHints() }}} {{{ renderStyles() }}} - - {{{ renderState() }}} {{{ renderScripts() }}} - - ``` -もし `template` を全く使っていないのなら、自分自身でstringを結合することができます。 +もし `template` を全く使っていないのなら、自分自身で文字列を結合することができます。 From 0a75d3409ff46d3d03c80d22e131c02dc7b35989 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 02:23:45 +0900 Subject: [PATCH 079/239] tweak build-config for ja (#80) --- ja/build-config.md | 69 +++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/ja/build-config.md b/ja/build-config.md index 9c593e8c..b6417cb0 100644 --- a/ja/build-config.md +++ b/ja/build-config.md @@ -1,10 +1,10 @@ # ビルド設定 -クライアントサイドで完結するプロジェクトの webpack 設定は既に知っての通りでしょう。 SSR プロジェクトにおいても大枠は似たようなものですが、設定ファイルを 3 つのファイル(*base*,*client*,*server*)に分けることを提案しています。base 設定は出力パス、エイリアス、ローダーのような、client と server 両方の環境に共有される設定を含み、server 設定と client 設定は単純に、 [webpack-merge](https://github.com/survivejs/webpack-merge) を使って、base 設定を拡張することができるものです。 +クライアントサイドで完結するプロジェクトの webpack 設定は既に知っての通りでしょう。SSR プロジェクトにおいても大枠は似たようなものですが、設定ファイルを 3 つのファイル(*base*、*client*、*server*)に分けることを提案しています。base 設定は出力パス、エイリアス、ローダーのような、client と server 両方の環境に共有される設定を含み、server 設定と client 設定は単純に、[webpack-merge](https://github.com/survivejs/webpack-merge) を使って、base 設定を拡張することができるものです。 ## server 設定 -server 設定は`createBundleRenderer`に渡されるサーババンドルを生成するために作られるもので、次のようになります: +server 設定は `createBundleRenderer` に渡されるサーババンドルを生成するために作られるもので、次のようになります: ```js const merge = require('webpack-merge') @@ -43,7 +43,7 @@ module.exports = merge(baseConfig, { }) ``` - `vue-ssr-server-bundle.json`が生成された後、ファイルパスを単純に `createBundleRenderer`に渡します: +`vue-ssr-server-bundle.json` が生成された後、ファイルパスを単純に `createBundleRenderer` に渡します: ```js const { createBundleRenderer } = require('vue-server-renderer') @@ -52,28 +52,28 @@ const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { }) ``` -別の方法として、 バンドルをオブジェクトとして`createBundleRenderer`に渡すことも可能です。これは開発中のホットリロードに対して便利で、[参考のセットアップとして](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js) HackerNews の設定を見てみてください。 +別の方法として、バンドルをオブジェクトとして `createBundleRenderer` に渡すことも可能です。これは開発中のホットリロードに対して便利で、[参考のセットアップとして](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js) HackerNews の設定を見てみてください。 ### externals の注意 - CSS ファイルを`externals` オプションにホワイトリスト登録していることに注目してください。その理由は、依存関係からインポートされる CSS は webpack によって処理されないといけないからです。 もし同じように webpack に依存する他のタイプのファイルをインポートしているなら、 (例: `*.vue`, `*.sass`)、 それらも同じようにホワイトリストに加えなければいけません。 +CSS ファイルを `externals` オプションにホワイトリスト登録していることに注目してください。その理由は、依存関係からインポートされる CSS は webpack によって処理されないといけないからです。 もし同じように webpack に依存する他のタイプのファイルをインポートしているなら、 (例: `*.vue`, `*.sass`)、 それらも同じようにホワイトリストに加えなければいけません。 -ホワイトリスト登録する他のタイプのモジュールは、例えば `babel-polyfill`のような`global`を修正するポリフィルです。なぜなら、サーババンドルの中のコードは独自の ** `global` **オブジェクトを持っているからです。Node7.6以降を使っていればサーバに`babel-polyfill`はあまり必要ないので、単純にクライアントエントリーにインポートする方が簡単です。 +`runInNewContext: 'once'` または `runInNewContext: true` を使用する場合、例えば `babel-polyfill` のような `global` のように変更するポリフィルがホワイトリスト登録するために必要です。これは、新しいコンテキストモードを使用するときに、サーババンドルの内部コードは独自の `global` オブジェクトを持っているからです。Node 7.6 以降を使っていればサーバに `babel-polyfill` はあまり必要ないので、単純にクライアントエントリーにインポートする方が簡単です。 ## client 設定 -client 設定は base 設定とほぼ同じままです。言うまでもなく、クライアント側のエントリファイルに `entry` を示す必要があります。またそれとは別に、 `CommonsChunkPlugin` 使っていたら、それが client 設定だけで使われていることを確認しておかないといけません。なぜなら、サーババンドルは単一のエントリーチャンクを要求するからです。 +client 設定は base 設定とほぼ同じままです。言うまでもなく、クライアント側のエントリファイルに `entry` を示す必要があります。またそれとは別に、`CommonsChunkPlugin` 使っていたら、それが client 設定だけで使われていることを確認しておかないといけません。なぜなら、サーババンドルは単一のエントリーチャンクを要求するからです。 ### `clientManifest` を生成する > version 2.3.0 以降必須 -サーババンドルに加えて、クライアントビルドマニフェストを作成することもできます。レンダラーは、クライアントマニフェストとサーババンドルでサーバ側*と*クライアント側の両方のビルド情報を持つことになり、 レンダリングされた HTML に [preload / prefetch directives](https://css-tricks.com/prefetching-preloading-prebrowsing/) や CSS の link や script タグを自動的に挿入することができます。 +サーババンドルに加えて、クライアントビルドマニフェストを作成することもできます。レンダラーは、クライアントマニフェストとサーババンドルでサーバ側*と*クライアント側の両方のビルド情報を持つことになり、 描画された HTML に [preload / prefetch directives](https://css-tricks.com/prefetching-preloading-prebrowsing/) や CSS の link や script タグを自動的に挿入することができます。 これには2重の恩恵があります: 1. 生成されたファイル名にハッシュがある時に、正しい URL を注入する `html-webpack-plugin` の代替になります。 -2. webpack のオンデマンドコード分割機能(code spliting)を利用するバンドルを描画する時に、最適なチャンクが preloaded / prefetched されるのを保証でき、かつ、クライアントに対するウォーターフォールリクエストを避けるために、必要な非同期チャンクに``タグを挿入することができます。そのようにして TTI (time-to-interactive) が改善します。 +2. webpack のオンデマンドコード分割機能(code spliting)を利用するバンドルを描画する時に、最適なチャンクが preloaded / prefetched されるのを保証でき、かつ、クライアントに対するウォーターフォールリクエストを避けるために、必要な非同期チャンクに `` タグを挿入することができます。そのようにして TTI (time-to-interactive) が改善します。 クライアントマニフェストを利用するためには、client 設定はこのようになります: @@ -115,42 +115,42 @@ const renderer = createBundleRenderer(serverBundle, { この設定で、コード分割されたビルドのためにサーバ側で描画される HTML はこのようになります(すべて自動でインジェクトされます): ```html - - - + + + - + - - - + + +
async
-
- + + + + + + + ``` ### 手動でのアセットインジェクション -デフォルト設定で、アセットインジェクションはあなたが作成した`template`描画オプションで自動に行われます。 しかし、アセットがどのようにテンプレートにインジェクトされるかをより細かくコントロールしたい時もあるでしょうし、あるいはテンプレートを使わない時もあるかもしれません。そのような場合にはレンダラを作る時に `inject: false` を渡せば、手動でアセットインジェクションを行うことができます。 +デフォルト設定で、アセットインジェクションはあなたが作成した `template` 描画オプションで自動に行われます。しかし、アセットがどのようにテンプレートにインジェクトされるかをより細かくコントロールしたい時もあるでしょうし、あるいはテンプレートを使わない時もあるかもしれません。そのような場合にはレンダラを作る時に `inject: false` を渡せば、手動でアセットインジェクションを行うことができます。 -渡した `context` オブジェクトは `renderToString` コールバックで、 次のメソッドを持ちます: +渡した `context` オブジェクトは `renderToString` コールバックで、次のメソッドを持ちます: - `context.renderStyles()` -これは、描画中に使われた `*.vue` コンポーネントから集めた全てのクリティカル CSS を含んだ`` タグを返します。詳細は [ CSS Management](./css.md)の章を見てください。 +これは、描画中に使われた `*.vue` コンポーネントから集めた全てのクリティカル CSS を含んだ `` タグを返します。詳細は [CSS の管理](./css.md)の章を見てください。 -`clientManifest` が提供されたら、返ってきた文字列は webpack が放出した CSS ファイルの``タグも含みます。 (例 : `extract-text-webpack-plugin` から抽出された CSS や、 `file-loader` でインポートされた CSS) +`clientManifest` が提供されたら、返ってきた文字列は webpack が放出した CSS ファイルの `` タグも含みます。(例: `extract-text-webpack-plugin` から抽出された CSS や、`file-loader` でインポートされた CSS) - `context.renderState(options?: Object)` - このメソッドは `context.state` をシリアライズし、 `window.__INITIAL_STATE__`ステートとして埋め込まれたインラインスクリプトを返します。 +このメソッドは `context.state` をシリアライズし、`window.__INITIAL_STATE__` ステートとして埋め込まれたインラインスクリプトを返します。 context のステートキーと window のステートキーはどちらとも、オプションオブジェクトとして渡すことでカスタマイズできます。 @@ -175,23 +175,28 @@ context のステートキーと window のステートキーはどちらとも - ページに必要な JavaScript や CSS ファイルをプリロードする - あとで必要な非同期 JavaScript チャンクをプリフェッチする - ファイルのプリロードは [`shouldPreload` ](./api.md#shouldpreload) オプションによってさらにカスタマイズが可能です。 + ファイルのプリロードは [`shouldPreload`](./api.md#shouldpreload) オプションによってさらにカスタマイズが可能です。 - `context.getPreloadFiles()` - 必須 `clientManifest` このメソッドは文字列を返さない代わりに、プリロードされるべきアセットを表すファイルオブジェクトの配列を返します。これは HTTP/2 サーバプッシュをプログラムで行うときに使えるでしょう。 -`createBundleRenderer` に渡された`template`は`context`を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: +`createBundleRenderer` に渡された `template` は `context` を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: ```html - - + + + {{{ renderResourceHints() }}} {{{ renderStyles() }}} + + {{{ renderState() }}} {{{ renderScripts() }}} + + ``` もし `template` を全く使っていないのなら、自分自身で文字列を結合することができます。 From 225fdf8d767ca42b92b658b2dfc42f5a4dbf6ff9 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 02:50:53 +0900 Subject: [PATCH 080/239] ja/bundle-renderer.md (#81) * Translate bundle-renderer.md via GitLocalize * Translate bundle-renderer.md via GitLocalize --- ja/bundle-renderer.md | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/ja/bundle-renderer.md b/ja/bundle-renderer.md index 54050e42..bc1f5e26 100644 --- a/ja/bundle-renderer.md +++ b/ja/bundle-renderer.md @@ -16,8 +16,8 @@ const createApp = require('/path/to/built-server-bundle.js') - ビルトインソースマップのサポート ( webpack の設定オプションに `devtool: 'source-map'` を指定) - 開発中、デプロイ中のホットリロード(更新されたバンドルや、再作成されたレンダラのインスタンスを読み込むだけです) -- クリティカル CSS の評価 (`*.vue` ファイルを利用しているとき): インライン CSS は、レンダリング中に利用されるコンポーネントによって必要とされます。詳細は [CSS](./css.md) をご覧ください。 -- [clientManifest](./api.md#clientmanifest) によるアセットの注入: 自動的に最適なディレクティブが推測され、プリロードとプリフェッチを実行します。また、初期レンダリング時にはコード分割チャンクを必要とします。 +- クリティカル CSS の評価 (`*.vue` ファイルを利用しているとき): インライン CSS は、描画中に利用されるコンポーネントによって必要とされます。詳細は [CSS](./css.md) を参照してください。 +- [clientManifest](./api.md#clientmanifest) によるアセットの注入: 自動的に最適なディレクティブが推測され、プリロードとプリフェッチを実行します。また、初期描画時にはコード分割チャンクを必要とします。 --- @@ -26,30 +26,23 @@ const createApp = require('/path/to/built-server-bundle.js') ```js const { createBundleRenderer } = require('vue-server-renderer') const renderer = createBundleRenderer(serverBundle, { - runInNewContext: false, // recommended - template, // (optional) page template - clientManifest // (optional) client build manifest + runInNewContext: false, // 推奨 + template, // (任意) ページテンプレート + clientManifest // (任意) クライアントビルドマニフェスト }) -// inside a server handler... +// 内部のサーバ処理 ... server.get('*', (req, res) => { const context = { url: req.url } - // No need to pass an app here because it is auto-created by the - // executing the bundle. Now our server is decoupled from our Vue app! + // バンドルを実行することで自動作成されるため、ここでアプリケーションを渡す必要はありません。 + // 今、私たちのサーバーはVueアプリから切り離されています! +app! renderer.renderToString(context, (err, html) => { - // handle error... + // ハンドリングエラー ... res.end(html) }) }) ``` -バンドルレンダラによって`rendertoString` が呼び出されると、バンドルによってエクスポートされた関数が自動的に実行され、(引数としてコンテキストを渡して)アプリケーションのインスタンスを生成し、レンダリングを実行します。 +バンドルレンダラによって`rendertoString` が呼び出されると、バンドルによってエクスポートされた関数が自動的に実行され、(引数として`context`を渡して)アプリケーションのインスタンスを生成し、描画処理を実行します。 ---- - -### `runInNewContext` オプション - -通常では、それぞれのレンダリング毎にバンドルレンダラは新しい V8 コンテキストを生成し、バンドル全体を再実行します。これにはいくつかの利点があります。例えば、私たちが以前に言及した "ステートフルシングルトン" 問題について心配する必要がなくなります。しかし、このモードにはかなりのパフォーマンスコストがかかります。なぜなら、アプリケーションを大きくなるにつれ、バンドル全体を再実行することは特にコストとなるからです。 - -`vue-server-renderer >= 2.3.0` では、このオプションは下位互換性のために依然として初期値が `true` にされています。しかし、できる限り `runInNewContext: false` にしておくことをオススメします。 - -`runInNewContext: false`の場合は、バンドルは引き続き**別なグローバルコンテキスト**として1度だけ評価されます。これにより、バンドルが誤ってサーバプロセスのグローバルオブジェクトを汚染してしまうことを防ぎます。通常との動作の違いは、それぞれのレンダリングの実行時に**新しい**コンテキストを生成しないことです。 +`runInNewContext` オプションを `false` または `'once'` に設定することをお勧めします。詳細は [API リファレンス](./api.md#runinnewcontext)を参照してください。 From 512ef53cbbb56c869209cdfe9e7ecd527d9944aa Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 02:57:10 +0900 Subject: [PATCH 081/239] Translate caching.md via GitLocalize (#82) --- ja/caching.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ja/caching.md b/ja/caching.md index 91f9e6e2..a4565269 100644 --- a/ja/caching.md +++ b/ja/caching.md @@ -34,11 +34,11 @@ server.get('*', (req, res) => { }) ``` -コンテンツは1秒間だけキャッシュされるため、ユーザーに古いコンテンツが表示されることはありません。ただし、これはサーバーがキャッシュされたページごとに秒間最大1回のフルレンダリングを実行するだけであることを意味します。 +コンテンツは1秒間だけキャッシュされるため、ユーザーに古いコンテンツが表示されることはありません。ただし、これはサーバーがキャッシュされたページごとに秒間最大1回の完全描画を実行するだけであることを意味します。 ## コンポーネントレベルでのキャッシュ -`vue-server-renderer` には、コンポーネントレベルのキャッシュ機能が組み込まれています。それを有効にするにはレンダラーを作成する際に[キャッシュ実装](./api.md#cache)を有効にする必要があります。代表的な使用例は [lru-cache](https://github.com/isaacs/node-lru-cache) を渡すことです。 +`vue-server-renderer` には、コンポーネントレベルのキャッシュ機能が組み込まれています。それを有効にするにはレンダラを作成する際に[キャッシュ実装](./api.md#cache)を有効にする必要があります。代表的な使用例は [lru-cache](https://github.com/isaacs/node-lru-cache) を渡すことです。 ```js const LRU = require('lru-cache') @@ -65,16 +65,16 @@ export default { キャッシュ可能なコンポーネントは **一意の "name" オプションも定義しなければいけない**ことに注意してください。一意の名前を持つと、キャッシュキーがコンポーネントごとに異なるため、同一キーを返す2つのコンポーネントについて心配する必要はありません。 -`serverCacheKey` から返されるキーはレンダリング結果を表すのに十分な情報を含んでいる必要があります。レンダリング結果が単に `props.item.id` によって決定される場合、上記は良い実装でしょう。しかしながら、同じIDを持つアイテムが時間の経過とともに変わる場合やレンダリング結果が他のプロパティに依存する場合、 他の変数を考慮して `getCacheKey` の実装を修正する必要があります。 +`serverCacheKey` から返されるキーは描画結果を表すのに十分な情報を含んでいる必要があります。描画結果が単に `props.item.id` によって決定される場合、上記は良い実装でしょう。しかしながら、同じIDを持つアイテムが時間の経過とともに変わる場合や描画結果が他のプロパティに依存する場合、 他の変数を考慮して `getCacheKey` の実装を修正する必要があります。 定数を返すと、コンポーネントは常にキャッシュされ、単なる静的なコンポーネントには効果的です。 ### いつコンポーネントキャッシュを使うか -レンダリング中にレンダラーがコンポーネントのキャッシュにヒットした場合、キャッシュされた結果をサブツリー全体で直接再利用します。 つまり、次の場合にコンポーネントをキャッシュ**しない**でください。 +描画中にレンダラがコンポーネントのキャッシュにヒットした場合、キャッシュされた結果をサブツリー全体で直接再利用します。 つまり、次の場合にコンポーネントをキャッシュ**しない**でください。 - グローバルな状態に依存する子コンポーネントがあります。 -- レンダリング `context` に副作用をもたらす子コンポーネントがあります。 +- 描画 `context` に副作用をもたらす子コンポーネントがあります。 したがって、コンポーネントのキャッシングは、パフォーマンスのボトルネックに取り組むために慎重に適用する必要があります。 ほとんどの場合、単一インスタンスのコンポーネントをキャッシュする必要はなく、すべきではありません。キャッシングに適した最も一般的なコンポーネントのタイプは、大きな `v-for` リストで繰り返されるコンポーネントです。 これらのコンポーネントは通常、データベースコレクション内のオブジェクトを元にするため、一意のIDと最後に更新されたタイムスタンプを合わせて使用してキャッシュキーを生成するという単純なキャッシュ戦略を使用できます。 From 29b5efb95662c4c8ee3d415dd841df3b917e329f Mon Sep 17 00:00:00 2001 From: Kenneth Lombardi Date: Sun, 9 Jul 2017 17:20:45 -0700 Subject: [PATCH 082/239] Update bundle-renderer.md (#83) --- en/bundle-renderer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/bundle-renderer.md b/en/bundle-renderer.md index a6f475e3..8107be24 100644 --- a/en/bundle-renderer.md +++ b/en/bundle-renderer.md @@ -24,7 +24,7 @@ This is straightforward, however whenever you edit your app source code, you wou --- -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: +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 and use a bundle renderer: ``` js const { createBundleRenderer } = require('vue-server-renderer') From 238ad12c0840fe74e4943c125b7040db8be9b639 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 10:31:06 +0900 Subject: [PATCH 083/239] Translate bundle-renderer.md via GitLocalize (#84) --- ja/bundle-renderer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ja/bundle-renderer.md b/ja/bundle-renderer.md index bc1f5e26..436399a0 100644 --- a/ja/bundle-renderer.md +++ b/ja/bundle-renderer.md @@ -21,7 +21,7 @@ const createApp = require('/path/to/built-server-bundle.js') --- -次のセクションで、バンドルレンダラと webpack のビルドの設定方法ついて説明します。今はすでに必要なものが準備できていると仮定しましょう。こちらはバンドルレンダラを生成し、使用する方法です: +次のセクションでバンドルレンダラによって必要とされるビルドされたモノを生成するために、webpack 設定する方法について説明しますが、今既に必要なものがあるものとしましょう。これは、バンドルレンダラを使用する方法です: ```js const { createBundleRenderer } = require('vue-server-renderer') From b9ca82696cd462c6df8c55aa882a3594840985d1 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 10:56:26 +0900 Subject: [PATCH 084/239] Translate data.md via GitLocalize (#85) --- ja/data.md | 95 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/ja/data.md b/ja/data.md index 69c59dae..69cf3316 100644 --- a/ja/data.md +++ b/ja/data.md @@ -2,13 +2,13 @@ ## データストア -SSR をしているとき、基本的にはアプリケーションの「スナップショット」をレンダリングしています、したがって、アプリケーションがいくつかの非同期データに依存している場合においては、**それらのデータを、レンダリング処理を開始する前にプリフェッチして解決する必要があります**。 +SSR をしているとき、基本的にはアプリケーションの"スナップショット"を描画しています、したがって、アプリケーションがいくつかの非同期データに依存している場合においては、**それらのデータを、描画処理を開始する前にプリフェッチして解決する必要があります**。 -もうひとつの重要なことは、クライアントサイドでアプリケーションがマウントされる前に、クライアントサイドで同じデータを利用可能である必要があるということです。そうしないと、クライアントサイドが異なるステートを用いてレンダリングしてしまい、ハイドレーションが失敗してしまいます。 +もうひとつの重要なことは、クライアントサイドでアプリケーションがマウントされる前に、クライアントサイドで同じデータを利用可能である必要があるということです。そうしないと、クライアントサイドが異なる状態 (state) を用いて描画してしまい、ハイドレーションが失敗してしまいます。 -この問題に対応するため、フェッチされたデータはビューコンポーネントの外でも存続している必要があります。つまり特定の用途のデータストアもしくは "ステート・コンテナ" に入っている必要があります。サーバーサイドではレンダリングする前にデータをプリフェッチしてストアの中に入れることができます。さらにシリアライズして HTML にステートを埋め込みます。クライアントサイドのストアは、アプリケーションをマウントする前に、埋め込まれたステートを直接取得できます。 +この問題に対応するため、フェッチされたデータはビューコンポーネントの外でも存続している必要があります。つまり特定の用途のデータストアもしくは "ステートコンテナ" に入っている必要があります。サーバーサイドでは描画する前にデータをプリフェッチしてストアの中に入れることができます。さらにシリアライズして HTML に状態を埋め込みます。クライアントサイドのストアは、アプリケーションをマウントする前に、埋め込まれた状態を直接取得できます。 -このような用途として、公式のステート管理ライブラリである [Vuex](https://github.com/vuejs/vuex/) を使っています。では `store.js` ファイルをつくって、そこに id に基づく item を取得するコードを書いてみましょう: +このような用途として、公式の状態管理ライブラリである [Vuex](https://github.com/vuejs/vuex/) を使っています。では `store.js` ファイルをつくって、そこに id に基づく item を取得するコードを書いてみましょう: ```js // store.js @@ -53,7 +53,7 @@ export function createApp () { // ルーターとストアのインスタンスを作成します const router = createRouter() const store = createStore() - // ルートのステートをストアの一部として利用できるよう同期します + // ルートの状態をストアの一部として利用できるよう同期します sync(store, router) // アプリケーションのインスタンスを作成し、ルーターとストアの両方を挿入します const app = new Vue({ @@ -70,7 +70,7 @@ export function createApp () { ではデータをプリフェッチするアクションをディスパッチするコードはどこに置けばよいでしょうか? -フェッチする必要があるデータはアクセスしたルートによって決まります。またそのルートによってどのコンポーネントがレンダリングされるかも決まります。実のところ、与えられたルートに必要とされるデータは、そのルートでレンダリングされるコンポーネントに必要とされるデータでもあるのです。したがって、データをフェッチするロジックはルートコンポーネントの中に置くのが自然でしょう。 +フェッチする必要があるデータはアクセスしたルートによって決まります。またそのルートによってどのコンポーネントが描画されるかも決まります。実のところ、与えられたルートに必要とされるデータは、そのルートで描画されるコンポーネントに必要とされるデータでもあるのです。したがって、データをフェッチするロジックはルートコンポーネントの中に置くのが自然でしょう。 ルートコンポーネントではカスタム静的関数 `asyncData` が利用可能です。この関数はそのルートコンポーネントがインスタンス化される前に呼び出されるため `this` にアクセスできないことを覚えておいてください。ストアとルートの情報は引数として渡される必要があります: @@ -86,7 +86,7 @@ export default { return store.dispatch('fetchItem', route.params.id) }, computed: { - // ストアのステートから item を表示します + // ストアの状態から item を表示します items () { return this.$store.state.items[this.$route.params.id] } @@ -97,7 +97,7 @@ export default { ## サーバーサイドのデータ取得 -`entry-server.js` において `router.getMatchedComponents()` を使ってルートにマッチしたコンポーネントを取得できます。そしてコンポーネントが `asyncData` を利用可能にしていればそれを呼び出すことができます。そしてレンダリングのコンテキストに解決したステートを付属させる必要があります。 +`entry-server.js` において `router.getMatchedComponents()` を使ってルートにマ一致したコンポーネントを取得できます。そしてコンポーネントが `asyncData` を利用可能にしていればそれを呼び出すことができます。そして描画のコンテキストに解決した状態を付属させる必要があります。 ```js // entry-server.js @@ -111,7 +111,7 @@ export default context => { if (!matchedComponents.length) { reject({ code: 404 }) } - // マッチしたルートコンポーネントすべての asyncData() を呼び出します + // 一致したルートコンポーネントすべての asyncData() を呼び出します Promise.all(matchedComponents.map(Component => { if (Component.asyncData) { return Component.asyncData({ @@ -121,9 +121,9 @@ export default context => { } })).then(() => { // すべてのプリフェッチのフックが解決されると、ストアには、 - // アプリケーションをレンダリングするために必要とされるステートが入っています。 - // ステートを context に付随させ、`template` オプションがレンダラーに利用されると、 - // ステートは自動的にシリアライズされ、HTML 内に `window.__INITIAL_STATE__` として埋め込まれます + // アプリケーションを描画するために必要とされる状態が入っています。 + // 状態を context に付随させ、`template` オプションがレンダラに利用されると、 + // 状態は自動的にシリアライズされ、HTML 内に `window.__INITIAL_STATE__` として埋め込まれます context.state = store.state resolve(app) }).catch(reject) @@ -132,7 +132,7 @@ export default context => { } ``` -`template` を使うと `context.state` は自動的に最終的な HTML に `window.__INITIAL__` というかたちのステートとして埋め込まれます。クライアントサイドでは、アプリケーションがマウントされる前に、ストアがそのステートを取得します: +`template` を使うと `context.state` は自動的に最終的な HTML に `window.__INITIAL__` という形の状態として埋め込まれます。クライアントサイドでは、アプリケーションがマウントされる前に、ストアがその状態を取得します: ```js // entry-client.js @@ -148,9 +148,9 @@ if (window.__INITIAL_STATE__) { 1. **ルートのナビゲーションの前にデータを解決する:** -この方法では、アプリケーションは、遷移先のビューが必要とするデータが解決されるまで、現在のビューを保ちます。良い点は遷移先のビューがデータの準備が整い次第、フルの内容をダイレクトにレンダリングできることです。しかしながら、データの取得に時間がかかるときは、ユーザーは現在のビューで「固まってしまった」と感じてしまうでしょう。そのため、この方法を用いるときにはローディング・インジケーターを表示させることが推奨されます。 +この方法では、アプリケーションは、遷移先のビューが必要とするデータが解決されるまで、現在のビューを保ちます。良い点は遷移先のビューがデータの準備が整い次第、フルの内容を直接描画できることです。しかしながら、データの取得に時間がかかるときは、ユーザーは現在のビューで「固まってしまった」と感じてしまうでしょう。そのため、この方法を用いるときにはローディングインジケーターを表示させることが推奨されます。 -この方法は、クライアントサイドでマッチするコンポーネントをチェックし、グローバルなルートのフック内で `asyncData` 関数を実行することにより実装できます。重要なことは、このフックは初期ルートが ready になった後に登録するということです。そうすれば、サーバーサイドで取得したデータをもう一度無駄に取得せずに済みます。 +この方法は、クライアントサイドで一致するコンポーネントをチェックし、グローバルなルートのフック内で `asyncData` 関数を実行することにより実装できます。重要なことは、このフックは初期ルートが ready になった後に登録するということです。そうすれば、サーバーサイドで取得したデータをもう一度無駄に取得せずに済みます。 ```js // entry-client.js @@ -162,8 +162,8 @@ if (window.__INITIAL_STATE__) { router.beforeResolve((to, from, next) => { const matched = router.getMatchedComponents(to) const prevMatched = router.getMatchedComponents(from) - // まだレンダリングされていないコンポーネントにのみ関心を払うため、 - // 2つのマッチしたリストに差分が表れるまで、コンポーネントを比較します + // まだ描画されていないコンポーネントにのみ関心を払うため、 + // 2つの一致したリストに差分が表れるまで、コンポーネントを比較します let diffed = false const activated = matched.filter((c, i) => { return diffed || (diffed = (prevMatched[i] !== c)) @@ -186,9 +186,9 @@ if (window.__INITIAL_STATE__) { }) ``` -1. **マッチするビューがレンダリングされた後にデータを取得する:** +1. **マッチするビューが描画された後にデータを取得する:** -この方法ではビューコンポーネントの `beforeMount` 関数内にクライアントサイドでデータを取得するロジックを置きます。こうすればルートのナビゲーションが発火したらすぐにビューを切り替えられます。そうすればアプリケーションはよりレスポンスが良いと感じられるでしょう。しかしながら、遷移先のビューはレンダリングした時点ではフルのデータを持っていません。したがって、この方法を使うコンポーネントの各々がローディング中か否かの状態を持つ必要があります。 +この方法ではビューコンポーネントの `beforeMount` 関数内にクライアントサイドでデータを取得するロジックを置きます。こうすればルートのナビゲーションが発火したらすぐにビューを切り替えられます。そうすればアプリケーションはよりレスポンスが良いと感じられるでしょう。しかしながら、遷移先のビューは描画した時点では完全なデータを持っていません。したがって、この方法を使うコンポーネントの各々がローディング中か否かの状態を持つ必要があります。 この方法はクライアントサイド限定のグローバルな mixin で実装できます: @@ -208,7 +208,7 @@ if (window.__INITIAL_STATE__) { }) ``` -これら 2つの方法のどちらを選ぶかは、究極的には異なる UX のどちらを選ぶかの判断であり、構築しようとしているアプリケーションの実際のシナリオに基づいて選択されるべきものです。しかし、どちらの方法を選択したかにかかわらず、ルートコンポーネントが再利用されたとき(つまりルートは同じだがパラメーターやクエリが変わったとき。例えば `user/1` から `user/2`) へ変わったとき)には `asyncData` 関数は呼び出されるようにすべきです。これはクライアントサイド限定のグローバルな mixin でハンドリングできます: +これら 2つの方法のどちらを選ぶかは、究極的には異なる UX のどちらを選ぶかの判断であり、構築しようとしているアプリケーションの実際のシナリオに基づいて選択されるべきものです。しかし、どちらの方法を選択したかにかかわらず、ルートコンポーネントが再利用されたとき(つまりルートは同じだがパラメーターやクエリが変わったとき。例えば `user/1` から `user/2`) へ変わったとき)には `asyncData` 関数は呼び出されるようにすべきです。これはクライアントサイド限定のグローバルな mixin で処理できます: ```js Vue.mixin({ @@ -226,6 +226,59 @@ Vue.mixin({ }) ``` +## ストアコードの分割 + +大規模なアプリケーションでは、Vuexストアは複数のモジュールに分割される可能性があります。もちろん、これらのモジュールを対応するルートコンポーネントチャンクにコード分割することもできます。次のストアモジュールがあるとします: + +```js +// store/modules/foo.js +export default { + namespaced: true, + // 重要: 状態は関数でなければならないため、 + // モジュールを複数回インスタン化できます + state: () => ({ + count: 0 + }), + actions: { + inc: ({ commit }) => commit('inc') + }, + mutations: { + inc: state => state.count++ + } +} +``` + +`store.registerModule` を使用して、ルートコンポーネントの `asyncData` フックにこのモジュールを遅延登録することができます: + +```html +// ルートコンポーネントの内部 + + +``` + +モジュールはルートコンポーネントの依存関係になっているので、webpack によってルートコンポーネントの非同期チャンクに移動されます。 + --- -ふぅ、コードが長いですね。これはどうしてかというと、ユニバーサルなデータ取得は、大抵の場合、サーバーサイドでレンダリングするアプリケーションの最も複雑な問題であり、また、今後、スムーズに開発を進めていくための下準備をしているためです。一旦ひな形が準備できてしまえば、あとは、それぞれのコンポーネントを記述していく作業は、実際のところ実に楽しいものになるはずです。 +ふぅ、コードが長いですね。これはどうしてかというと、ユニバーサルなデータ取得は、大抵の場合、サーバーサイドで描画するアプリケーションの最も複雑な問題であり、また、今後、スムーズに開発を進めていくための下準備をしているためです。一旦ひな形が準備できてしまえば、あとは、それぞれのコンポーネントを記述していく作業は、実際のところ実に楽しいものになるはずです。 From efdc16782d0e5b06042a41bdc718fd6304c21bdc Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Mon, 10 Jul 2017 11:40:31 +0900 Subject: [PATCH 085/239] Translate api.md via GitLocalize (#86) --- ja/api.md | 57 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/ja/api.md b/ja/api.md index df578179..48332534 100644 --- a/ja/api.md +++ b/ja/api.md @@ -18,9 +18,9 @@ const { createBundleRenderer } = require('vue-server-renderer') const renderer = createBundleRenderer(serverBundle, { ... }) ``` -引数 `serverBundle` には次のいずれか1つを指定できます。 +引数 `serverBundle` には次のいずれか1つを指定できます: -- 生成されたバンドルファイル (`.js` または `.json`) への絶対パス。 ファイルパスは、 `/` で始めなければいけません。 +- 生成されたバンドルファイル (`.js` または `.json`)への絶対パス。ファイルパスとして扱われるために `/` で開始されなければなりません。 - webpack と `vue-server-renderer/server-plugin` によって生成されたバンドルオブジェクト。 - JavaScript コードの文字列 (非推奨)。 @@ -46,7 +46,7 @@ Vue インスタンスを Node.js のストリームへ描画します。context サーババンドルを Node.js のストリームへ描画します。context オブジェクトの指定は任意です。より詳しい情報は、[ストリーミング](./streaming.md) の項目を参照してください。 -## Renderer 生成時のオプション +## レンダラオプション - #### `template` @@ -66,9 +66,9 @@ Vue インスタンスを Node.js のストリームへ描画します。context 加えて、`clientManifest` も渡された場合、テンプレートは自動で以下を挿入します。 - (自動で受信される非同期のデータを含んだ)描画対象が必要とするクライアントサイドの JavaScript と CSS アセット -- 描画済みのページに対する最適な `` Resource Hints +- 描画済みのページに対する最適な `` リソースヒント -Renderer に `inject: false` も渡すことで、すべての自動挿入を無効にすることができます。 +レンダラに `inject: false` も渡すことで、すべての自動挿入を無効にすることができます。 参照: @@ -76,9 +76,8 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 - [手動によるアセットインジェクション](./build-config.md#manual-asset-injection) - #### `clientManifest` - 2.3.0以上 -- `createBundleRenderer` メソッド内でのみ使用可能 -`vue-server-renderer/server-plugin` によって生成されたクライアントビルドマニフェストオブジェクトを提供します。clientManifest は、HTML テンプレートへの自動アセット挿入に適した情報とともに、BundleRenderer を提供します。より詳しい情報は [clientManifest の生成](./build-config.md#generating-clientmanifest) の項目を参照してください。 +`vue-server-renderer/server-plugin` によって生成されたクライアントビルドマニフェストオブジェクトを提供します。クライアントマニフェストは、HTML テンプレートへの自動アセット挿入に適した情報とともに、バンドルレンダラを提供します。より詳しい情報は [クライアントマニフェストの生成](./build-config.md#generating-clientmanifest) の項目を参照してください。 - #### `inject` @@ -94,7 +93,7 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 - 2.3.0以上 -どのファイルが `` 生成済みの Resource Hints を持つべきか制御するための関数を指定します。 +どのファイルが `` 生成済みのリソースヒント持つべきか制御するための関数を指定します。 デフォルトでは、JavaScript と CSS ファイルのみがプリロードされます。これらはアプリケーション起動時に必須なためです。 @@ -105,17 +104,17 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 template, clientManifest, shouldPreload: (file, type) => { - // type is inferred based on the file extension. + // type はファイル拡張子に基づいて推論されます // https://fetch.spec.whatwg.org/#concept-request-destination if (type === 'script' || type === 'style') { return true } if (type === 'font') { - // only preload woff2 fonts + // woff2 フォントのプリロードのみ return /\.woff2$/.test(file) } if (type === 'image') { - // only preload important images + // 重要な画像のプリロードのみ return file === 'hero.jpg' } } @@ -127,12 +126,22 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 - 2.3.0以上 - `createBundleRenderer` メソッド内でのみ使用可能 + - 要求事項: `boolean | 'once'` (`'once'` 2.3.1 以降でのみサポートされる) デフォルトでは、BundleRenderer の描画ごとに未使用の V8 コンテキストを生成し、バンドル全体を再実行するでしょう。これにはいくつかのメリットがあります。例えば、私たちが以前から言及してきた「ステートフルでシングルトン」なデータを管理することの問題点について心配する必要がありません。しかしながら、このモードはいくつかの無視できないパフォーマンスの問題が起こります。 なぜなら、アプリケーションが大きくなるとき、バンドルの再実行は著しくコストがかかるためです。 -このオプションは、下位互換のためデフォルトは `true` です。しかし、可能ならば常に `runInNewContext: false` を使用することが推奨されます。 +このオプションは、下位互換のためデフォルトは `true` です。しかし、可能ならば常に `runInNewContext: false` または、`runInNewContext: 'once'`を使用することが推奨されます。 -参考:[ソースコードの構造](./structure.md) +> 2.3.0 では、このオプションは `runInNewContext: false` が個別のグローバルコンテキストを使ってバンドルを実行するバグを持っています。以下の情報は、バージョン2.3.1以降を前提としています。 + +`runInNewContext: false` の場合は、バンドルコードはサーバープロセスと同じ `global` コンテキストで実行されるので、アプリケーションコード内で `global` を変更するコードには注意してください。 + +`runInNewContext: 'once'` (2.3.1 以降)の場合は、バンドルは別々の `global` コンテキストで評価されますが、起動時には一度だけ評価されます。これにより、バンドルがサーバープロセスの `global` オブジェクトを誤って汚染するのを防ぐので、より良いアプリケーションコードの分離が可能になります。注意点は次のとおりです: + +1. このモードでは、 `global` (例: ポリフィル) を変更する依存関係を外部化することはできません; +2. バンドル実行から返される値は、異なるグローバルコンストラクタを使用します。バンドルの内部で捕捉されたエラーはサーバプロセスの `Error` のインスタンスにはなりません。 + +参考: [ソースコードの構造](./structure.md) - #### `basedir` @@ -144,7 +153,7 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 - #### `cache` -[コンポーネントキャッシュ](./caching.md#component-level-caching) の実装を提供します。 キャッシュオブジェクトは以下のインタフェースで実装しなければいけません(以下のような記法を用いる)。 +[コンポーネントキャッシュ](./caching.md#component-level-caching) の実装を提供します。 キャッシュオブジェクトは以下のインタフェースで実装しなければいけません(以下のような記法を用いる): ```js type RenderCache = { @@ -154,7 +163,7 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 }; ``` -代表的な使用方法は、次の [lru-cache](https://github.com/isaacs/node-lru-cache) のような流れになります。 +代表的な使用方法は、次の [lru-cache](https://github.com/isaacs/node-lru-cache) のような流れになります: ```js const LRU = require('lru-cache') @@ -165,14 +174,14 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 }) ``` -キャッシュオブジェクトは、少なくても `get` と `set` を実装すべき点に注意してください。加えて、`get` と `has` は、もし第二引数に callback が指定された場合、必要に応じてこれを非同期処理にできます。これは、非同期 API を使用したキャッシュの利用を可能にします。 例)以下のような redis クライアント使用する場合 +キャッシュオブジェクトは、少なくても `get` と `set` を実装すべき点に注意してください。加えて、`get` と `has` は、もし第 2 引数に callback が指定された場合、必要に応じてこれを非同期処理にできます。これは、非同期 API を使用したキャッシュの利用を可能にします。 例)以下のような redis クライアント使用する場合: ```js const renderer = createRenderer({ cache: { get: (key, cb) => { redisClient.get(key, (err, res) => { - // handle error if any + // 任意のエラーがあれば処理 cb(res) }) }, @@ -185,35 +194,35 @@ Renderer に `inject: false` も渡すことで、すべての自動挿入を無 - #### `directives` -以下のように、カスタムディレクティブをサーバサイドの実装で使用可能にします。 +以下のように、カスタムディレクティブをサーバサイドの実装で使用可能にします: ```js const renderer = createRenderer({ directives: { example (vnode, directiveMeta) { - // transform vnode based on directive binding metadata + // ディレクティブのバインディングメタデータに基づいて vnode を変換する } } }) ``` -一例として、[`v-show` のサーバサイド実装はこちら](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js) +例として、[`v-show` のサーバサイド実装はこちら](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js) です。 -## Webpack プラグイン +## webpack プラグイン -webpack プラグインは、スタンドアロンのファイルとして提供され、次の値を必要とします。 +webpack プラグインは、スタンドアロンのファイルとして提供され、次の値を必要とします: ```js const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') ``` -デフォルトで生成されるファイルは以下のものです。 +デフォルトで生成されるファイルは以下のものです: - サーバサイドプラグインのための `vue-ssr-server-bundle.json` - クライアントサイドプラグインのための `vue-ssr-client-manifest.json` -プラグインのインスタンス生成時、これらのファイル名は以下のようにカスタマイズ可能です。 +プラグインのインスタンス生成時、これらのファイル名は以下のようにカスタマイズ可能です: ```js const plugin = new VueSSRServerPlugin({ From f755a76bfb8baa3d2eafe65a5f1b04c9c3dca560 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Tue, 11 Jul 2017 00:16:55 +0900 Subject: [PATCH 086/239] Translate css.md via GitLocalize (#89) --- ja/css.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ja/css.md b/ja/css.md index d51b7eb1..c37e1dda 100644 --- a/ja/css.md +++ b/ja/css.md @@ -6,12 +6,12 @@ CSS を管理するためのおすすめの方法は、シンプルに単一フ - プリプロセッサや PostCSS を活用する機能 - 開発時のホットリロード -さらに重要なことは、`vue-loader` によって内部的に使われている `vue-style-loader` はサーバーレンダリングのためのいくつかの特別な機能を持っています: +さらに重要なことは、`vue-loader` によって内部的に使われている `vue-style-loader` はサーバーによる描画のためのいくつかの特別な機能を持っています: - クライアントとサーバーのためのユニバーサル変換処理の体験 - `bundleRenderer` を使用した時の自動的なCSS評価 -もしサーバレンダリングで使用するなら、コンポーネントの CSS はHTMLに集められてインライン化されます ( `template` オプションを使用していれば自動で扱われます ) 。クライアント上で、コンポーネントが初めて使用されたとき、`vue-style-loader` は既にそのコンポーネントにサーバーインラインCSSがあるかチェックします。もし存在しない場合、そのCSSは動的に `` タグを返します。詳細は [CSS の管理](./css.md)の章を見てください。 + + `clientManifest` が提供されたら、返ってきた文字列は webpack が放出した CSS ファイルの `` タグも含みます。(例: `extract-text-webpack-plugin` から抽出された CSS や、`file-loader` でインポートされた CSS) + +- `context.renderState(options?: Object)` + + このメソッドは `context.state` をシリアライズし、`window.__INITIAL_STATE__` ステートとして埋め込まれたインラインスクリプトを返します。 + + context のステートキーと window のステートキーはどちらとも、オプションオブジェクトとして渡すことでカスタマイズできます。 + +```js + context.renderState({ + contextKey: 'myCustomState', + windowKey: '__MY_STATE__' + }) + + // -> +``` + +- `context.renderScripts()` + + - 必須 `clientManifest` + +このメソッドはクライアントアプリケーションを起動するのに必要な `` タグを返します。コードの中に非同期コード分割を使っている時、このメソッドは賢くも、インクルードされるべき正しい非同期チャンクを推論します。 + +- `context.renderResourceHints()` + + - 必須 `clientManifest` + +このメソッドは、現在描画されているページに必要な `` リソースヒントを返します。 デフォルト設定ではこのようになります: + +- ページに必要な JavaScript や CSS ファイルをプリロードする +- あとで必要な非同期 JavaScript チャンクをプリフェッチする + + ファイルのプリロードは [`shouldPreload`](../api/#shouldpreload) オプションによってさらにカスタマイズが可能です。 + +- `context.getPreloadFiles()` + + - 必須 `clientManifest` + + このメソッドは文字列を返さない代わりに、プリロードされるべきアセットを表すファイルオブジェクトの配列を返します。これは HTTP/2 サーバプッシュをプログラムで行うときに使えるでしょう。 + +`createBundleRenderer` に渡された `template` は `context` を使って挿入されるので、これらのメソッドをテンプレート内で(`inject: false`で)使用することができます: + +```html + + + + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + + + + {{{ renderState() }}} + {{{ renderScripts() }}} + + +``` + +もし `template` を全く使っていないのなら、自分自身で文字列を結合することができます。 diff --git a/docs/ja/guide/bundle-renderer.md b/docs/ja/guide/bundle-renderer.md new file mode 100644 index 00000000..e8c7248f --- /dev/null +++ b/docs/ja/guide/bundle-renderer.md @@ -0,0 +1,52 @@ +# バンドルレンダラの紹介 + +## 根本的なサーバサイドレンダリングの問題 + +今までは、バンドルされたサーバサイドのコードが `require` によって直接使用されることを想定していました: + +```js +const createApp = require('/path/to/built-server-bundle.js') +``` + +これはとても簡単です。しかしながらアプリケーションのソースコードを編集する度に、あなたはサーバを停止し再起動させる必要があるでしょう。この作業は開発の生産性を著しく損ないます。さらに、Node.js はソースマップをネイティブサポートしていません。 + +## バンドルレンダラの追加 + +`vue-server-renderer` はこの問題を解決するために、`createBundleRenderer` という API を提供しています。また、webpack の拡張プラグインを利用することで、サーババンドルはバンドルレンダラに渡すことができる特別な JSON ファイルとして生成されます。バンドルレンダラが1度生成されると、使用方法は通常のレンダラと一緒ですが、次のような利点があります。 + +- ビルトインソースマップのサポート ( webpack の設定オプションに `devtool: 'source-map'` を指定) + +- 開発中、デプロイ中のホットリロード(更新されたバンドルや、再作成されたレンダラのインスタンスを読み込むだけです) + +- クリティカル CSS の評価 (`*.vue` ファイルを利用しているとき): インライン CSS は、描画中に利用されるコンポーネントによって必要とされます。詳細は [CSS](./css.md) を参照してください。 + +- [clientManifest](../api/#clientmanifest) によるアセットの注入: 自動的に最適なディレクティブが推測され、プリロードとプリフェッチを実行します。また、初期描画時にはコード分割チャンクを必要とします。 + +--- + +次のセクションでバンドルレンダラによって必要とされるビルドされたモノを生成するために、webpack 設定する方法について説明しますが、今既に必要なものがあるものとしましょう。これは、バンドルレンダラを使用する方法です: + +```js +const { createBundleRenderer } = require('vue-server-renderer') + +const renderer = createBundleRenderer(serverBundle, { + runInNewContext: false, // 推奨 + template, // (任意) ページテンプレート + clientManifest // (任意) クライアントビルドマニフェスト +}) + +// 内部のサーバ処理 ... +server.get('*', (req, res) => { + const context = { url: req.url } + // バンドルを実行することで自動作成されるため、ここでアプリケーションを渡す必要はありません + // 今、私たちのサーバーはVueアプリから切り離されています! + renderer.renderToString(context, (err, html) => { + // ハンドリングエラー ... + res.end(html) + }) +}) +``` + +バンドルレンダラによって`rendertoString` が呼び出されると、バンドルによってエクスポートされた関数が自動的に実行され、(引数として`context`を渡して)アプリケーションのインスタンスを生成し、描画処理を実行します。 + +`runInNewContext` オプションを `false` または `'once'` に設定することをお勧めします。詳細は [API リファレンス](../api/#runinnewcontext)を参照してください。 diff --git a/docs/ja/guide/caching.md b/docs/ja/guide/caching.md new file mode 100644 index 00000000..7877e12e --- /dev/null +++ b/docs/ja/guide/caching.md @@ -0,0 +1,87 @@ +# キャッシュ + +Vue の SSR は非常に高速ですが、コンポーネントインスタンスや仮想 DOM ノードの作成コストのため純粋な文字列ベースのテンプレートのパフォーマンスにはかないません。 SSR のパフォーマンスが重大である場合、キャッシュ戦略を賢く活用することで、応答時間が大幅に改善され、サーバーの負荷が軽減されます。 + +## ページレベルでのキャッシュ + +ほとんどの場合、サーバで描画されたアプリケーションは外部データに依存するため、コンテンツは本質的には動的であり長期間キャッシュすることはできません。しかしながら、コンテンツがユーザー固有のものでない場合、(すなわち、同一URLが常にすべてのユーザに対して同じコンテンツを描画する場合)、 [マイクロキャッシング](https://www.nginx.com/blog/benefits-of-microcaching-nginx/) という戦略を活用してアプリケーションのトラフィック処理能力を劇的に改善します。 + +これは通常 Nginx レイヤーで行われますが、 Node.js で実装することも可能です: + +```js +const microCache = LRU({ + max: 100, + maxAge: 1000 // 重要: コンテンツの登録内容は1秒後に期限切れになります +}) + +const isCacheable = req => { + // リクエストがユーザー固有のものかどうかチェックするロジックを実装します + // ユーザー固有でないページのみがキャッシュ可能です +} + +server.get('*', (req, res) => { + const cacheable = isCacheable(req) + if (cacheable) { + const hit = microCache.get(req.url) + if (hit) { + return res.end(hit) + } + } + + renderer.renderToString((err, html) => { + res.end(html) + if (cacheable) { + microCache.set(req.url, html) + } + }) +}) +``` + +コンテンツは1秒間だけキャッシュされるため、ユーザーに古いコンテンツが表示されることはありません。ただし、これはサーバーがキャッシュされたページごとに秒間最大1回の完全描画を実行するだけであることを意味します。 + +## コンポーネントレベルでのキャッシュ + +`vue-server-renderer` には、コンポーネントレベルのキャッシュ機能が組み込まれています。それを有効にするにはレンダラを作成する際に[キャッシュ実装](../api/#cache)を有効にする必要があります。代表的な使用例は [lru-cache](https://github.com/isaacs/node-lru-cache) を渡すことです: + +```js +const LRU = require('lru-cache') + +const renderer = createRenderer({ + cache: LRU({ + max: 10000, + maxAge: ... + }) +}) +``` + +次に `serverCacheKey` 関数を実装してコンポーネントをキャッシュすることが出来ます: + +```js +export default { + name: 'item', // required + props: ['item'], + serverCacheKey: props => props.item.id, + render (h) { + return h('div', this.item.id) + } +} +``` + +キャッシュ可能なコンポーネントは **一意の `name` オプションも定義しなければいけない**ことに注意してください。一意の名前を持つと、キャッシュキーがコンポーネントごとに異なるため、同一キーを返す2つのコンポーネントについて心配する必要はありません。 + +`serverCacheKey` から返されるキーは描画結果を表すのに十分な情報を含んでいる必要があります。描画結果が単に `props.item.id` によって決定される場合、上記は良い実装でしょう。しかしながら、同じIDを持つアイテムが時間の経過とともに変わる場合や描画結果が他のプロパティに依存する場合、 他の変数を考慮して `getCacheKey` の実装を修正する必要があります。 + +定数を返すと、コンポーネントは常にキャッシュされ、単なる静的なコンポーネントには効果的です。 + +### いつコンポーネントキャッシュを使うか + +描画中にレンダラがコンポーネントのキャッシュにヒットした場合、キャッシュされた結果をサブツリー全体で直接再利用します。 つまり、次の場合にコンポーネントをキャッシュ **しない** でください。 + +- グローバルな状態に依存する子コンポーネントがあります。 +- 描画 `context` に副作用をもたらす子コンポーネントがあります。 + +したがって、コンポーネントのキャッシングは、パフォーマンスのボトルネックに取り組むために慎重に適用する必要があります。 ほとんどの場合、単一インスタンスのコンポーネントをキャッシュする必要はなく、すべきではありません。キャッシングに適した最も一般的なコンポーネントのタイプは、大きな `v-for` リストで繰り返されるコンポーネントです。 これらのコンポーネントは通常、データベースコレクション内のオブジェクトを元にするため、一意のIDと最後に更新されたタイムスタンプを合わせて使用してキャッシュキーを生成するという単純なキャッシュ戦略を使用できます: + +```js +serverCacheKey: props => props.item.id + '::' + props.item.last_updated +``` diff --git a/docs/ja/guide/css.md b/docs/ja/guide/css.md new file mode 100644 index 00000000..a7ae4c58 --- /dev/null +++ b/docs/ja/guide/css.md @@ -0,0 +1,114 @@ +# CSS の管理 + +CSS を管理するためのおすすめの方法は、シンプルに単一ファイルコンポーネントである `*.vue` の中で `` を使うことができます。 + +もし `import 'foo.css'` のように JavaScriptからCSSをインポートしたいならば、適切な loader の設定が必要です: + +```js +module.exports = { + // ... + module: { + rules: [ + { + test: /\.css$/, + // 重要: style-loader の代わりに vue-style-loader を使用します + use: isProduction + ? ExtractTextPlugin.extract({ + use: 'css-loader', + fallback: 'vue-style-loader' + }) + : ['vue-style-loader', 'css-loader'] + } + ] + }, + // ... +} +``` + +## 依存関係からのスタイルのインポート + +NPM 依存で CSS をインポートするときに気を付けることがいくつかあります: + +1. サーバービルドで外部化しないでください。 + +2. もし CSS抽出 + `CommonsChunkPlugin` でベンダー抽出を使用している場合、抽出されたベンダーのチャンクに抽出された CSS があれば、`extract-text-webpack-plugin` に問題が発生します。この問題を解決するためには、ベンダーのチャンクに CSS ファイルを含めないでください。クライアント側の webpack の設定例です: + +```js + module.exports = { + // ... + plugins: [ + // deps をベンダーのチャンクに抽出してキャッシュを改善するのが一般的です + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function (module) { + // モジュールはベンダーチャンクに抽出されます... + return ( + // node_modules 内部な場合 + /node_modules/.test(module.context) && + // リクエストが CSS ファイルの場合、抽出しない + !/\.css$/.test(module.request) + ) + } + }), + // webpack ランタイム & マニフェストを抽出する + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest' + }), + // ... + ] + } +``` diff --git a/docs/ja/guide/data.md b/docs/ja/guide/data.md new file mode 100644 index 00000000..304abb21 --- /dev/null +++ b/docs/ja/guide/data.md @@ -0,0 +1,311 @@ +# データのプリフェッチと状態 + +## データストア + +SSR をしているとき、基本的にはアプリケーションの"スナップショット"を描画しています、したがって、アプリケーションがいくつかの非同期データに依存している場合においては、**それらのデータを、描画処理を開始する前にプリフェッチして解決する必要があります**。 + +もうひとつの重要なことは、クライアントサイドでアプリケーションがマウントされる前に、クライアントサイドで同じデータを利用可能である必要があるということです。そうしないと、クライアントサイドが異なる状態 (state) を用いて描画してしまい、ハイドレーションが失敗してしまいます。 + +この問題に対応するため、フェッチされたデータはビューコンポーネントの外でも存続している必要があります。つまり特定の用途のデータストア (data store) もしくは "状態コンテナ (state container)" に入っている必要があります。サーバーサイドでは描画する前にデータをプリフェッチしてストアの中に入れることができます。さらにシリアライズして HTML に状態を埋め込みます。クライアントサイドのストアは、アプリケーションをマウントする前に、埋め込まれた状態を直接取得できます。 + +このような用途として、公式の状態管理ライブラリである [Vuex](https://github.com/vuejs/vuex/) を使っています。では `store.js` ファイルをつくって、そこに id に基づく item を取得するコードを書いてみましょう: + +```js +// store.js +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.use(Vuex) + +// Promise を返すユニバーサルなアプリケーションを想定しています +// また、実装の詳細は割愛します +import { fetchItem } from './api' + +export function createStore () { + return new Vuex.Store({ + state: { + items: {} + }, + actions: { + fetchItem ({ commit }, id) { + // store.dispatch() 経由でデータがフェッチされたときにそれを知るために、Promise を返します + return fetchItem(id).then(item => { + commit('setItem', { id, item }) + }) + } + }, + mutations: { + setItem (state, { id, item }) { + Vue.set(state.items, id, item) + } + } + }) +} +``` + +そして `app.js` を更新します: + +```js +// app.js +import Vue from 'vue' +import App from './App.vue' +import { createRouter } from './router' +import { createStore } from './store' +import { sync } from 'vuex-router-sync' + +export function createApp () { + // ルーターとストアのインスタンスを作成します + const router = createRouter() + const store = createStore() + + // ルートの状態をストアの一部として利用できるよう同期します + sync(store, router) + + // アプリケーションのインスタンスを作成し、ルーターとストアの両方を挿入します + const app = new Vue({ + router, + store, + render: h => h(App) + }) + + // アプリケーション、ルーター、ストアを公開します + return { app, router, store } +} +``` + +## ロジックとコンポーネントとの結び付き + +ではデータをプリフェッチするアクションをディスパッチするコードはどこに置けばよいでしょうか? + +フェッチする必要があるデータはアクセスしたルート (route) によって決まります。またそのルートによってどのコンポーネントが描画されるかも決まります。実のところ、与えられたルートに必要とされるデータは、そのルートで描画されるコンポーネントに必要とされるデータでもあるのです。したがって、データをフェッチするロジックはルートコンポーネントの中に置くのが自然でしょう。 + +ルートコンポーネントではカスタム静的関数 `asyncData` が利用可能です。この関数はそのルートコンポーネントがインスタンス化される前に呼び出されるため `this` にアクセスできないことを覚えておいてください。ストアとルートの情報は引数として渡される必要があります: + +```html + + + + +``` + +## サーバーサイドのデータ取得 + +`entry-server.js` において `router.getMatchedComponents()` を使ってルートに一致したコンポーネントを取得できます。そしてコンポーネントが `asyncData` を利用可能にしていればそれを呼び出すことができます。そして描画のコンテキストに解決した状態を付属させる必要があります。 + +```js +// entry-server.js +import { createApp } from './app' + +export default context => { + return new Promise((resolve, reject) => { + const { app, router, store } = createApp() + + router.push(context.url) + + router.onReady(() => { + const matchedComponents = router.getMatchedComponents() + if (!matchedComponents.length) { + reject({ code: 404 }) + } + + // 一致したルートコンポーネントすべての asyncData() を呼び出します + Promise.all(matchedComponents.map(Component => { + if (Component.asyncData) { + return Component.asyncData({ + store, + route: router.currentRoute + }) + } + })).then(() => { + // すべてのプリフェッチのフックが解決されると、ストアには、 + // アプリケーションを描画するために必要とされる状態が入っています。 + // 状態を context に付随させ、`template` オプションがレンダラに利用されると、 + // 状態は自動的にシリアライズされ、HTML 内に `window.__INITIAL_STATE__` として埋め込まれます + context.state = store.state + resolve(app) + }).catch(reject) + }, reject) + }) +} +``` + +`template` を使うと `context.state` は自動的に最終的な HTML に `window.__INITIAL__` という形の状態として埋め込まれます。クライアントサイドでは、アプリケーションがマウントされる前に、ストアがその状態を取得します: + +```js +// entry-client.js + +const { app, router, store } = createApp() + +if (window.__INITIAL_STATE__) { + store.replaceState(window.__INITIAL_STATE__) +} +``` + +## クライアントサイドのデータ取得 + +クライアントサイドではデータ取得について 2つの異なるアプローチがあります: + +1. **ルートのナビゲーションの前にデータを解決する:** + +この方法では、アプリケーションは、遷移先のビューが必要とするデータが解決されるまで、現在のビューを保ちます。良い点は遷移先のビューがデータの準備が整い次第、フルの内容を直接描画できることです。しかしながら、データの取得に時間がかかるときは、ユーザーは現在のビューで「固まってしまった」と感じてしまうでしょう。そのため、この方法を用いるときにはローディングインジケーターを表示させることが推奨されます。 + +この方法は、クライアントサイドで一致するコンポーネントをチェックし、グローバルなルートのフック内で `asyncData` 関数を実行することにより実装できます。重要なことは、このフックは初期ルートが ready になった後に登録するということです。そうすれば、サーバーサイドで取得したデータをもう一度無駄に取得せずに済みます。 + +```js + // entry-client.js + + // ...関係のないコードは除外します + + router.onReady(() => { + // asyncData を扱うためにルーターのフックを追加します。これは初期ルートが解決された後に実行します + // そうすれば(訳注: サーバーサイドで取得したために)既に持っているデータを冗長に取得しなくて済みます + // すべての非同期なコンポーネントが解決されるように router.beforeResolve() を使います + router.beforeResolve((to, from, next) => { + const matched = router.getMatchedComponents(to) + const prevMatched = router.getMatchedComponents(from) + + // まだ描画されていないコンポーネントにのみ関心を払うため、 + // 2つの一致したリストに差分が表れるまで、コンポーネントを比較します + let diffed = false + const activated = matched.filter((c, i) => { + return diffed || (diffed = (prevMatched[i] !== c)) + }) + + if (!activated.length) { + return next() + } + + // もしローディングインジケーターがあるならば、 + // この箇所がローディングインジケーターを発火させるべき箇所です + + Promise.all(activated.map(c => { + if (c.asyncData) { + return c.asyncData({ store, route: to }) + } + })).then(() => { + + // ローディングインジケーターを停止させます + + next() + }).catch(next) + }) + + app.$mount('#app') + }) +``` + +1. **一致するビューが描画された後にデータを取得する:** + +この方法ではビューコンポーネントの `beforeMount` 関数内にクライアントサイドでデータを取得するロジックを置きます。こうすればルートのナビゲーションが発火したらすぐにビューを切り替えられます。そうすればアプリケーションはよりレスポンスが良いと感じられるでしょう。しかしながら、遷移先のビューは描画した時点では完全なデータを持っていません。したがって、この方法を使うコンポーネントの各々がローディング中か否かの状態を持つ必要があります。 + +この方法はクライアントサイド限定のグローバルな mixin で実装できます: + +```js + Vue.mixin({ + beforeMount () { + const { asyncData } = this.$options + if (asyncData) { + // データが準備できた後に、コンポーネント内で `this.dataPromise.then(...)` して + // 他のタスクを実行できるようにするため、Promise にフェッチ処理を割り当てます + this.dataPromise = asyncData({ + store: this.$store, + route: this.$route + }) + } + } + }) +``` + +これら 2つの方法のどちらを選ぶかは、究極的には異なる UX のどちらを選ぶかの判断であり、構築しようとしているアプリケーションの実際のシナリオに基づいて選択されるべきものです。しかし、どちらの方法を選択したかにかかわらず、ルートコンポーネントが再利用されたとき(つまりルートは同じだがパラメーターやクエリが変わったとき。例えば `user/1` から `user/2`) へ変わったとき)には `asyncData` 関数は呼び出されるようにすべきです。これはクライアントサイド限定のグローバルな mixin で処理できます: + +```js +Vue.mixin({ + beforeRouteUpdate (to, from, next) { + const { asyncData } = this.$options + if (asyncData) { + asyncData({ + store: this.$store, + route: to + }).then(next).catch(next) + } else { + next() + } + } +}) +``` + +## ストアコードの分割 + +大規模なアプリケーションでは、Vuex ストアは複数のモジュールに分割される可能性があります。もちろん、これらのモジュールを対応するルートコンポーネントチャンクにコード分割することもできます。次のストアモジュールがあるとします: + +```js +// store/modules/foo.js +export default { + namespaced: true, + // 重要: 状態は関数でなければならないため、 + // モジュールを複数回インスタン化できます + state: () => ({ + count: 0 + }), + actions: { + inc: ({ commit }) => commit('inc') + }, + mutations: { + inc: state => state.count++ + } +} +``` + +`store.registerModule` を使用して、ルートコンポーネントの `asyncData` フックにこのモジュールを遅延登録することができます: + +```html +// ルートコンポーネントの内部 + + +``` + +モジュールはルートコンポーネントの依存関係になっているので、webpack によってルートコンポーネントの非同期チャンクに移動されます。 + +--- + +ふぅ、コードが長いですね。これはどうしてかというと、ユニバーサルなデータ取得は、大抵の場合、サーバーサイドで描画するアプリケーションの最も複雑な問題であり、また、今後、スムーズに開発を進めていくための下準備をしているためです。一旦ひな形が準備できてしまえば、あとは、それぞれのコンポーネントを記述していく作業は、実際のところ実に楽しいものになるはずです。 diff --git a/docs/ja/guide/head.md b/docs/ja/guide/head.md new file mode 100644 index 00000000..5161b4d0 --- /dev/null +++ b/docs/ja/guide/head.md @@ -0,0 +1,90 @@ +# ヘッドの管理 + +アセットの挿入と同様に、ヘッド (Head) の管理も同じ考えに追従しています。つまり、コンポーネントのライフサイクルの描画 `context` に動的にデータを付随させ、そして `template` 内にデータを展開 (interpolate) できるという考えです。 + +> バージョン >=2.3.2 では、`this.$ssrContext` としてコンポーネントにおいて SSR コンテキストに直接アクセスできます。古いバージョンでは、`createApp()` によって手動で SSR コンテキストを渡して注入し、ルート (root) インスタンスの `$options` に公開する必要があります。子は、`this.$root.$options.ssrContext` を介してそれにアクセスすることができます。 + +タイトルを管理する単純な mixin を書くことができます: + +```js +// title-mixin.js + +function getTitle (vm) { + // コンポーネントはシンプルに `title` オプションを提供し、 + // これには文字列または関数を入れることができます + const { title } = vm.$options + if (title) { + return typeof title === 'function' + ? title.call(vm) + : title + } +} + +const serverTitleMixin = { + created () { + const title = getTitle(this) + if (title) { + this.$root.$options.ssrContext.title = title + } + } +} + +const clientTitleMixin = { + mounted () { + const title = getTitle(this) + if (title) { + document.title = title + } + } +} + +// VUE_ENV は webpack.DefinePlugin を使って注入できます +export default process.env.VUE_ENV === 'server' + ? serverTitleMixin + : clientTitleMixin +``` + +このようにすれば、ルート (route) コンポーネントはドキュメントのタイトルを制御するために context を利用することができます。 + +```js +// Item.vue +export default { + mixins: [titleMixin], + title () { + return this.item.title + }, + + asyncData ({ store, route }) { + return store.dispatch('fetchItem', route.params.id) + }, + + computed: { + item () { + return this.$store.state.items[this.$route.params.id] + } + } +} +``` + +そしてタイトルは `template` 内でバンドルレンダラに渡されます: + +```html + + + {{ title }} + + + ... + + +``` + +**メモ:** + +- XSS 攻撃を防ぐために double-mustache(HTML エスケープした展開)を使うこと。 + +- 描画中にタイトルをセットするコンポーネントがない場合に備えて、`context` オブジェクトを作成する際にはデフォルトのタイトルをセットするようにすべきです。 + +--- + +同様のやり方で、この mixin を包括的にヘッドを管理するユーティリティに容易に拡張できます。 diff --git a/docs/ja/guide/hydration.md b/docs/ja/guide/hydration.md new file mode 100644 index 00000000..e67dfcbd --- /dev/null +++ b/docs/ja/guide/hydration.md @@ -0,0 +1,32 @@ +# クライアントサイドでのハイドレーション + +`entry-client.js` において、以下の記述で私たちは簡単にアプリケーションをマウントします: + +```js +// これは、ルート要素に id="app" をもつ App.vue テンプレートを想定します +app.$mount('#app') +``` + +サーバがマークアップを描画後に、この処理を実行し、すべての DOM を再生成することを私たちは当然したくありません。代わりに、静的なマークアップの"ハイドレート (hydrate)"とそれをインタラクティブに生成したいです。 + +サーバの描画出力を調べたら、アプリケーションのルート要素が以下のような特別な属性を持っていることに気づくでしょう: + +```js +
+``` + +この `data-server-rendered` という特別な属性は、クライアントサイドの Vue に、これがサーバ上で描画されたことを知らせ、この要素はハイドレーションモードでマウントされるはずです。`id="app"` に、単に `data-server-rendered` が追加されていないことに注意してください。ID または 他のセレクタを自分自身のルート要素に追加する必要があります。そうしないと、アプリケーションが適切にハイドレーションできなくなります。 + +開発モードでは、Vue はクラインアントサイドで生成された仮想 DOM が、サーバで描画された DOM の構成と一致しているか検証を行います。もしこれが一致していない場合、ハイドレーションを取りやめ、元の DOM を無視しスクラッチから描画を行います。**プロダクションモードでは、パフォーマンスの最大化のため、このアサーションは無効になります。** + +### ハイドレーション時の注意 + +サーバサイドの描画とクライアントサイドでのハイドレーションを行なった場合、ある特定の HTML の構造はブラウザによって変換されるかもしれないことがわかっています。例えば、あなたが Vue のテンプレート内に、以下のような記述をした場合です: + +```html +
+ +
hi
+``` + +ブラウザは、自動で `` を `` に挿入します。しかし、Vue によって生成された仮想 DOM は、`` を含みません。そのため、不一致が起こります。正しいマッチングを保証するために、あなたのテンプレート内では、必ず有効な HTML を記述してください。 diff --git a/docs/ja/guide/non-node.md b/docs/ja/guide/non-node.md new file mode 100644 index 00000000..2f7e6c1a --- /dev/null +++ b/docs/ja/guide/non-node.md @@ -0,0 +1,41 @@ +# 非 Node.js 環境における使用 + +`vue-server-renderer` の標準ビルドは Node.js 環境を想定していますが、これは、[PHP V8Js](https://github.com/phpv8/v8js) や [Oracle Nashorn](https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/) のような別のJavaScript 環境では使用できなくなります。2.5 のにおいて、環境にはほとんど影響されない、 `vue-server-renderer/basic.js` のビルドを出荷しました。これにより、上記の環境で使用できるようになります。 + +どちらの環境に対して、それは `global` と `process` オブジェクトをモックすることによって最初に環境を準備する必要があり、 `process.env.VUE_ENV` に `"server"` を設定し、そして `process.env.NODE_ENV` に `"development"` または {code6}"production"{/code6} を設定します。 + +Nashornでは、Java のネイティブタイマーを使用して、 `Promise{/ code0} または setTimeout` のポリフィルを提供する必要があります。 + +php-v8js での使用例: + +```php +executeString('var process = { env: { VUE_ENV: "server", NODE_ENV: "production" }}; this.global = { process: process };'); +$v8->executeString($vue_source); +$v8->executeString($renderer_source); +$v8->executeString($app_source); +?> +``` + +--- + +```js +// app.js +var vm = new Vue({ + template: `
{{ msg }}
`, + data: { + msg: 'hello' + } +}) + +// `vue-server-renderer/basic.js` によってエクスポーズ +renderVueComponentToString(vm, (err, res) => { + print(res) +}) +``` diff --git a/docs/ja/guide/routing.md b/docs/ja/guide/routing.md new file mode 100644 index 00000000..1a632773 --- /dev/null +++ b/docs/ja/guide/routing.md @@ -0,0 +1,153 @@ +# ルーティングとコード分割 + +## `vue-router` によるルーティング + +サーバーコードが任意の URL を受け入れる `*` ハンドラを使用していることに気付いたかもしれません。これにより訪れた URL を Vue アプリケーションに渡し、クライアントとサーバーの両方に同一のルーティング設定を再利用することが可能になります! + +この目的のために公式の `vue-router` を使用することが推奨されています。まずはルーターを作成するファイルを作成しましょう。 `createApp` に似ていますが、 リクエストごとに新たなルーターインスタンスも必要となるため、 `createRouter` 関数をエクスポートします: + +```js +// router.js +import Vue from 'vue' +import Router from 'vue-router' + +Vue.use(Router) + +export function createRouter () { + return new Router({ + mode: 'history', + routes: [ + // ... + ] + }) +} +``` + +そして `app.js` を更新します: + +```js +// app.js +import Vue from 'vue' +import App from './App.vue' +import { createRouter } from './router' + +export function createApp () { + // ルーターインスタンスを作成します + const router = createRouter() + + const app new Vue({ + // ルーターをルートVueインスタンスに注入します + router, + render: h => h(App) + }) + + // アプリケーションとルーターの両方を返します + return { app, router } +} +``` + +`entry-server.js` にサーバー側のルーティングロジックを実装する必要があります: + +```js +// entry-server.js +import { createApp } from './app' + +export default context => { + // 非同期のルートフックまたはコンポーネントが存在する可能性があるため、 + // 描画する前にすべての準備が整うまでサーバーが待機できるように + // プロミスを返します + return new Promise((resolve, reject) => { + const { app, router } = createApp() + + // サーバーサイドのルーターの場所を設定します + router.push(context.url) + + // ルーターが非同期コンポーネントとフックを解決するまで待機します + router.onReady(() => { + const matchedComponents = router.getMatchedComponents() + // 一致するルートがない場合、404で拒否します + if (!matchedComponents.length) { + reject({ code: 404 }) + } + + // プロミスは描画できるようにアプリケーションインスタンスを解決するべきです + resolve(app) + }, reject) + }) +} +``` + +サーバーバンドルがすでにビルドされていると仮定すると(再度になりますが、今はビルド設定は無視します)、サーバーでの使用方法は次のようになります: + +```js +// server.js +const createApp = require('/path/to/built-server-bundle.js') + +server.get('*', (req, res) => { + const context = { url: req.url } + + createApp(context).then(app => { + renderer.renderToString(app, (err, html) => { + if (err) { + if (err.code === 404) { + res.status(404).end('Page not found') + } else { + res.status(500).end('Internal Server Error') + } + } else { + res.end(html) + } + }) + }) +}) +``` + +## コード分割 + +コード分割やアプリケーションの部分的な遅延ローディングは初期描画のためにブラウザがダウンロードする必要のあるアセットの量を減らすのに役立ち、巨大なバンドルを持つアプリケーションの TTI (操作可能になるまでの時間)を大幅に改善します。重要なことは初期画面では"必要なものだけを読み込む"ということです。 + +Vue は非同期コンポーネントを最重要コンセプトとして提供しており、 [webpack 2の動的インポートをコード分割点として使用することへのサポート](https://webpack.js.org/guides/code-splitting-async/) と組み合わせることも可能です。そのためにすべきことは以下です: + +```js +// これを... +import Foo from './Foo.vue' + +// このように変えます +const Foo = () => import('./Foo.vue') +``` + +純粋なクライアントサイドの Vue アプリケーションを構築する場合、これはどんなシナリオでも機能するでしょう。ただし、これをサーバーサイドの描画で使用する場合はいくつかの制限があります。まず、描画を開始する前にサーバー上のすべての非同期コンポーネントを先に解決する必要があります。そうしなければ、マークアップ内に空のプレースホルダが表示されます。クライアント側では、ハイドレーションを開始する前にこれを行う必要があります。そうしなければ、クライアントはコンテンツの不一致エラーに陥ります。 + +アプリケーション内の任意の場所で非同期コンポーネントを使用するのは少し難解です(これは将来的に改善される可能性があります)。 ただし、**ルート (route) レベルで行うとシームレスに動作します**(すなわち、ルート設定で非同期コンポーネントを使用する)。ルートを解決する際に、 `vue-router` は一致した非同期コンポーネントを自動的に解決するためです。 必要なことは、サーバーとクライアントの両方で `router.onReady` を使用することです。すでにサーバーのエントリーで行ったので、クライアントのエントリーを更新するだけです。 + +```js +// entry-client.js + +import { createApp } from './app' + +const { app, router } = createApp() + +router.onReady(() => { + app.$mount('#app') +}) +``` + +非同期ルートコンポーネントを使用したルート設定の例: + +```js +// router.js +import Vue from 'vue' +import Router from 'vue-router' + +Vue.use(Router) + +export function createRouter () { + return new Router({ + mode: 'history', + routes: [ + { path: '/', component: () => import('./components/Home.vue') }, + { path: '/item/:id', component: () => import('./components/Item.vue') } + ] + }) +} +``` diff --git a/docs/ja/guide/streaming.md b/docs/ja/guide/streaming.md new file mode 100644 index 00000000..06aeb7f1 --- /dev/null +++ b/docs/ja/guide/streaming.md @@ -0,0 +1,33 @@ +# ストリーミング + +`vue-server-renderer` は、基本的なレンダラとバンドルレンダラの両方のためにストリームによる描画をサポートします。`renderToString` の代わりに`renderToStream`を使用するだけです: + +```js +const stream = renderer.renderToStream(context) +``` + +返り値は [Node.js stream](https://nodejs.org/api/stream.html) です: + +```js +let html = '' + +stream.on('data', data => { + html += data.toString() +}) + +stream.on('end', () => { + console.log(html) // 完全に描画 +}) + +stream.on('error', err => { + // エラーを処理 ... +}) +``` + +## ストリーミングに関する注意事項 + +ストリームによる描画モードでは、レンダラが仮想 DOM ツリーを横断するときに、できるだけ早くデータを出力します。つまり、より早く「最初のチャンク」を取得し、それをクライアントにすばやく出力し始めることを意味します。 + +しかし、最初のデータチャンクが発行したときに子コンポーネントがまだインスタンス化されていないと、ライフサイクルフックが呼び出されることはありません。つまり、子コンポーネントがライフサイクルフック内の描画コンテキストにデータを添付する必要がある場合、これらのデータはストリームの開始時に使用できません。アプリケーションは全体の HTML の表示の前に多くのコンテキスト情報(ヘッド情報やインラインに書かれたクリティカル CSS など)を表示する必要があるため、これらのコンテキストデータを使用する前にストリームの完了を待つ必要があります。 + +したがって、コンポーネントライフサイクルフックによって読み込まれたコンテキストデータに依存する場合は、ストリーミングモードを使用することは**推奨されません**。 diff --git a/docs/ja/guide/structure.md b/docs/ja/guide/structure.md new file mode 100644 index 00000000..0d203bd2 --- /dev/null +++ b/docs/ja/guide/structure.md @@ -0,0 +1,122 @@ +# ソースコードの構造 + +## ステートフルなシングルトンの回避 + +クライアントのみのコードを書くとき、私たちはコードが毎回新しいコンテキストで評価されるという事実に慣れています。しかし、 Node.js サーバーは長時間実行されるプロセスです。私たちのコードがプロセスに要求されるとき、それは一度評価されメモリにとどまります。つまりシングルトンのオブジェクトを作成したとき、それは全ての受信リクエスト間でシェアされると言うことです。 + +基本的な例としては、私たちは **リクエストごとに新しいルート Vue インスタンスを作成します**。それは各ユーザがそれぞれのブラウザでアプリケーションの新しいインスタンスを使用することに似ています。もし私たちが複数のリクエストをまたいでインスタンスを共有すると、それは容易にクロスリクエスト状態の汚染につながるでしょう。 + +そのため、直接アプリケーションのインスタンスを作成するのではなく、各リクエストで繰り返し実行される新しいアプリケーションのインスタンスを作成するファクトリ関数を公開する必要があります: + +```js +// app.js +const Vue = require('vue') + +module.exports = function createApp (context) { + return new Vue({ + data: { + url: context.url + }, + template: `
The visited URL is: {{ url }}
` + }) +} +``` + +そして私たちのサーバーコードはこうなります: + +```js +// server.js +const createApp = require('./app') + +server.get('*', (req, res) => { + const context = { url: req.url } + const app = createApp(context) + + renderer.renderToString(app, (err, html) => { + // エラーを処理 ... + res.end(html) + }) +}) +``` + +同じルールがルータ、ストア、イベントバスのインスタンスに適用されます。モジュールから直接エクスポートしアプリケーションにインポートするのでは無く、 `createApp` で新しいインスタンスを作成し、ルート (root) Vue インスタンスから注入する必要があります。 + +> `{ runInNewContext: true }` でバンドルレンダラを使用するとき、その制約を取り除くことが可能です。しかし各リクエストに対して新しい VM コンテキストを作成する必要があるため、いくらか重大なパフォーマンスコストがかかります。 + +## ビルドステップの紹介 + +これまでは、同じ Vue アプリケーションをクライアントへ配信する方法を論じてはいませんでした。これを行うには、webpack を使用して Vue アプリケーションをバンドルする必要があります。実際、webpack を使用して Vue アプリケーションをサーバーにバンドルしたいと思っているのはおそらく次の理由によるものです。 + +- 典型的な Vue アプリケーションは webpack と `vue-loader` によってビルドされ、 `file-loader` 経由でのファイルのインポートや`css-loader` 経由でCSSをインポートなどの多くの webpack 固有の機能は Node.jsで直接動作しません。 + +- Node.jsの最新バージョンはES2015の機能を完全にサポートしていますが、古いブラウザに対応するためにクライアントサイドのコードをトランスパイルする必要があります。これはビルドステップにも再び関係します。 + +従って基本的な考え方は webpack を使用してクライアントとサーバー両方をバンドルすることです。サーバーバンドルはサーバーによって SSR のために要求され、クライアントバンドルは静的なマークアップのためにブラウザに送信されます。 + +![architecture](https://cloud.githubusercontent.com/assets/499550/17607895/786a415a-5fee-11e6-9c11-45a2cfdf085c.png) + +セットアップの詳細については次のセクションで議論されます。今のところ、ビルドのセットアップが分かっていると仮定すると、webpack を有効にして Vue アプリケーションコードを書くことが可能になっています。 + +## webpackによるコード構造 + +webpack を使用してサーバーとクライアントのアプリケーションを処理しているので、ソースコードの大部分はユニバーサルに書かれており、すべての webpack の機能にアクセスできます。 同時に、ユニバーサルなコードを書くときに留意すべき[いくつかあります。](./universal.md) + +シンプルなプロジェクトは以下のようになります: + +```bash +src +├── components +│   ├── Foo.vue +│   ├── Bar.vue +│   └── Baz.vue +├── App.vue +├── app.js # 共通のエントリ +├── entry-client.js # ブラウザでのみ実行されます +└── entry-server.js # サーバでのみ実行されます +``` + +### `app.js` + +`app.js` はアプリケーションのユニバーサルエントリーです。クライアントアプリケーションでは、このファイルにルート Vue インスタンスを作成し、DOM に直接マウントします。しかし、SSRの場合は責務はクライアント専用のエントリファイルに映されます。`app.js` はシンプルに `createApp` 関数をエクスポートします: + +```js +import Vue from 'vue' +import App from './App.vue' + +// 新しいアプリケーション、ルータ、ストアを作成するためのファクトリ関数をエクスポートします +// インスタンス +export function createApp () { + const app = new Vue({ + // ルートインスタンスは単に App コンポーネントを描画します + render: h => h(App) + }) + return { app } +} +``` + +### `entry-client.js`: + +クライアントエントリは単にアプリケーションを作成しそれをDOMにマウントします: + +```js +import { createApp } from './app' + +// クライアント固有の初期化ロジック + +const { app } = createApp() +// これは App.vue テンプレートのルート要素が id="app" だからです。 +app.$mount('#app') +``` + +### `entry-server.js`: + +サーバーエントリは描画ごとに繰り返し呼び出すことができる関数をデフォルトでエクスポートします。現時点では、アプリケーションインスタンスを作成して返す以外のことはほとんど行いませんが、後でサーバーサイドのルートマッチングとデータプリフェッチロジックを実行します。 + +```js +import { createApp } from './app' + +export default context => { + const { app } = createApp() + return app +} +``` diff --git a/docs/ja/guide/universal.md b/docs/ja/guide/universal.md new file mode 100644 index 00000000..bf569a5f --- /dev/null +++ b/docs/ja/guide/universal.md @@ -0,0 +1,32 @@ +# ユニバーサルなコードを書く + +サーバサイドの描画について、さらに見ていく前に、"ユニバーサル"なコード(サーバーとクライアントの両方で動作するコード)を記述するときの制約について考えてみましょう。ユースケースとプラットフォームの API の相違により、異なる環境で実行したコードの動作は全く同じものにはなりません。ここでは、サーバサイドの描画を行う上で、知っておく必要がある重要な項目について説明します。 + +## サーバー上でのデータリアクティビテイ + +クライアントだけで実行するアプリでは、全てのユーザーがブラウザでアプリケーションの新しいインスタンスを使用します。サーバサイドの描画でも、同じ振る舞いが必要とされます: すなわち、複数のリクエストに跨った状態の汚染がないよう各リクエストは新しく独立したアプリケーションのインスタンスが必要になります。 + +実際の描画プロセスは決定的であることが求められるので、サーバー上でデータを"プリフェッチ"することもあります。これは描画を開始する時、アプリケーションの状態は既に解決済みであることを意味します。つまり、サーバー上では、データがリアクティブである必要はないので、デフォルトで無効になっています。データをリアクティブにしないことで、データをリアクティブなオブジェクトに変換する際のパフォーマンスコストを無視できます。 + +## コンポーネントのライフサイクルフック + +動的な更新がないので、ライフサイクルフックのうち、`beforeCreate` と `created` のみが SSR 中に呼び出されます。つまり、 `beforeMount` や `mounted` などの他のコンポーネントサイクルフックは、クライアントでのみ実行されます。 + +注意すべきもう一つは、`beforeCreate` と `created` において、例えば `setInterval` でタイマーを設定するような、グローバルな副作用を引き起こすコード避けるべきです。クライアントサイドのみのコードでは、タイマーを設定してから `beforeDestroy` または `destroyed` したりすることができます。しかしながら、SSR 中に破棄フックは呼び出されないため、タイマーは永遠に残ります。 これを回避するために、代わりに副作用コードを `beforeMount` または `mounted` に移動してください。 + +## プラットフォーム固有の API にアクセスする + +ユニバーサルコードでは、プラットフォーム固有の API へのアクセスは想定されていないので、`window` や `document` といったブラウザ環境のグローバル変数を直接使用すると、Node.js ではエラーが発生します。 + +サーバーとクライアントでコードを共有するものの、タスクが使用する API がプラットフォームによって異なる場合は、プラットフォーム固有の実装を ユニバーサル な API の内部でラップするか、それを行うライブラリを使用することをお勧めします。例えば、[axios](https://github.com/mzabriskie/axios) は、サーバーとクライアントの両方に同じ API を提供する HTTP クライアントです。 + +ブラウザ API を利用する際の一般的なアプローチは、クライアントだけで実行されるライフサイクルの中で遅延的にアクセスすることです。 + +サードパーティライブラリがユニバーサルに使用することを考慮していない場合、それをサーバサイドによって描画されるアプリケーションに統合することは難しいので注意してください。グローバル変数のいくつかをモックすることで動かすことができるようになる *かも*しれませんが、それはハックであり、他のライブラリの環境検出のコードを妨げる恐れがあります。 + +## カスタムディレクティブ + +ほとんどの カスタムディレクティブ は直接 DOM を操作するため、SSR 中にエラーが発生します。これを回避するには、2つの方法があります: + +1. 抽象化の仕組みとしてコンポーネントを使用し、カスタムディレクティブの代わりに仮想 DOM レベル(例えば、render 関数を使用すること)で実装することをお勧めします。 +2. コンポーネントに簡単に置き換えができないカスタムディレクティブの場合、サーバーレンダラを生成する際の [`directives`](../api/#directives) オプションを使用して、そのオプションの "サーバーサイドのバージョン" を用意することで回避できます。 From ec7f9c331ba5db57ccc27e9f958c504a272f36dd Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Mon, 28 May 2018 17:35:21 +0300 Subject: [PATCH 177/239] Russian translation moved to VuePress (#184) --- docs/.vuepress/config.js | 36 ++++ docs/.vuepress/public/_redirects | 5 + docs/ru/README.md | 53 ++++++ docs/ru/api/README.md | 270 ++++++++++++++++++++++++++ docs/ru/guide/README.md | 157 +++++++++++++++ docs/ru/guide/build-config.md | 215 +++++++++++++++++++++ docs/ru/guide/bundle-renderer.md | 52 +++++ docs/ru/guide/caching.md | 87 +++++++++ docs/ru/guide/css.md | 114 +++++++++++ docs/ru/guide/data.md | 316 +++++++++++++++++++++++++++++++ docs/ru/guide/head.md | 90 +++++++++ docs/ru/guide/hydration.md | 34 ++++ docs/ru/guide/non-node.md | 41 ++++ docs/ru/guide/routing.md | 153 +++++++++++++++ docs/ru/guide/streaming.md | 33 ++++ docs/ru/guide/structure.md | 123 ++++++++++++ docs/ru/guide/universal.md | 33 ++++ 17 files changed, 1812 insertions(+) create mode 100644 docs/ru/README.md create mode 100644 docs/ru/api/README.md create mode 100644 docs/ru/guide/README.md create mode 100644 docs/ru/guide/build-config.md create mode 100644 docs/ru/guide/bundle-renderer.md create mode 100644 docs/ru/guide/caching.md create mode 100644 docs/ru/guide/css.md create mode 100644 docs/ru/guide/data.md create mode 100644 docs/ru/guide/head.md create mode 100644 docs/ru/guide/hydration.md create mode 100644 docs/ru/guide/non-node.md create mode 100644 docs/ru/guide/routing.md create mode 100644 docs/ru/guide/streaming.md create mode 100644 docs/ru/guide/structure.md create mode 100644 docs/ru/guide/universal.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 01e9834c..42e5e8eb 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -14,6 +14,11 @@ module.exports = { lang: 'ja', title: 'Vue SSR ガイド', description: 'Vue.js サーバーサイドレンダリングガイド' + }, + '/ru/': { + lang: 'ru', + title: 'Руководство Vue SSR', + description: 'Руководство по серверному рендерингу Vue.js' } }, serviceWorker: true, @@ -113,6 +118,37 @@ module.exports = { '/ja/guide/streaming', '/ja/guide/non-node' ] + }, + '/ru/': { + label: 'Русский', + selectText: 'Languages', + editLinkText: 'Изменить эту страницу на GitHub', + nav: [ + { + text: 'Руководство', + link: '/ru/guide/' + }, + { + text: 'Справочник API', + link: '/ru/api/' + } + ], + sidebar: [ + ['/ru/', 'Введение'], + '/ru/guide/', + '/ru/guide/universal', + '/ru/guide/structure', + '/ru/guide/routing', + '/ru/guide/data', + '/ru/guide/hydration', + '/ru/guide/bundle-renderer', + '/ru/guide/build-config', + '/ru/guide/css', + '/ru/guide/head', + '/ru/guide/caching', + '/ru/guide/streaming', + '/ru/guide/non-node' + ] } } } diff --git a/docs/.vuepress/public/_redirects b/docs/.vuepress/public/_redirects index dabdceb1..5e2ecf29 100644 --- a/docs/.vuepress/public/_redirects +++ b/docs/.vuepress/public/_redirects @@ -7,3 +7,8 @@ /zh/api.html /zh/api/ /zh/basic.html /zh/guide/ /zh/* /zh/guide/:splat + +/ru/ /ru/ +/ru/api.html /ru/api/ +/ru/basic.html /ru/guide/ +/ru/* /ru/guide/:splat \ No newline at end of file diff --git a/docs/ru/README.md b/docs/ru/README.md new file mode 100644 index 00000000..62da90c0 --- /dev/null +++ b/docs/ru/README.md @@ -0,0 +1,53 @@ +# Руководство по серверному рендерингу Vue.js + +::: tip Примечание +Для этого руководства требуются следующие версии Vue и библиотек: + +- vue & vue-server-renderer 2.3.0+ +- vue-router 2.5.0+ +- vue-loader 12.0.0+ & vue-style-loader 3.0.0+ + +Если вы ранее использовали Vue 2.2 с серверным рендерингом, вы заметите, что рекомендуемая структура кода теперь [немного отличается](./guide/structure.md) (с новой опцией [runInNewContext](./api/#runinnewcontext), установленной в `false`). Ваше существующее приложение по-прежнему будет работать, но лучше внесите изменения с учётом новых рекомендаций. +::: + +## Что такое серверный рендеринг (SSR)? + +Vue.js — это фреймворк для создания приложений, выполняемых на клиенте (client-side). По умолчанию компоненты Vue создают и манипулируют DOM в браузере. Однако, также возможно рендерить те же компоненты в HTML-строки на сервере, отправлять их в браузер, и наконец «гидрировать» статическую разметку в полностью интерактивное приложение на клиенте. + +Приложение Vue.js отрендеренное на сервере также можно считать «изоморфным» или «универсальным», в том смысле, что большая часть кода приложения **является общей для сервера и клиента**. + +## Нужен ли вам SSR? + +По сравнению с традиционным SPA (Single-Page Application), преимуществами серверного рендеринга будут: + +- Лучшее SEO, поскольку поисковые роботы будут видеть полностью отрендеренную страницу. + + Обратите внимание, что на данный момент Google and Bing могут без проблем индексировать синхронные приложения JavaScript. Ключевое слово здесь — синхронные. Если ваше приложение запускается с индикатором загрузки, а потом догружает контент через Ajax, то поисковый робот просто не будет дожидаться окончания загрузки. Это значит, что если у вас есть асинхронный контент на страницах где SEO важен, то может потребоваться серверный рендеринг. + +- Лучшие показатели времени до отображения контента (time-to-content), особенно при плохом интернете или на медленных устройствах. Для разметки, отрендеренной на сервере, не требуется дожидаться пока весь JavaScript будет загружен и выполнен, поэтому ваш пользователь увидит полностью отрендеренную страницу раньше. Как правило, это приводит к лучшему пользовательскому опыту и может быть критичным для приложений, где время до отображения контента напрямую связано с коэффициентом конверсии. + +Следует учитывать и некоторые компромиссы при использовании серверного рендеринга: + +- Ограничения при разработке. Код только для браузера может быть использован лишь в определённых хуках жизненного цикла; некоторые внешние библиотеки могут нуждаться в особой обработке, чтобы иметь возможность запускаться в приложении с серверным рендерингом. + +- Более сложные требования по настройке и развёртыванию сборки. В отличие от полностью статичного SPA, который может быть развёрнут на любом статичном файловом сервере, приложение с серверным рендерингом требует окружения, где есть возможность запустить сервер Node.js. + +- Повышенная нагрузка на стороне сервера. Рендеринг полноценного приложения в Node.js очевидно более требователен к ресурсам процессора, чем простая раздача статичных файлов, поэтому если вы ожидаете большой трафик, будьте готовы к соответствующей нагрузке на сервер и используйте стратегии кэширования. + +Прежде чем использовать серверный рендеринг для вашего приложения, задайте себе вопрос, действительно ли он вам нужен. Ответ зависит от того, насколько важно время до контента для вашего приложения. Например, если вы разрабатываете панель мониторинга для внутренних нужд, где дополнительные несколько сотен миллисекунд начальной загрузки не так важны, то серверный рендеринг будет излишеством. Однако, в тех случаях, когда время до контента критично, серверный рендеринг может позволит достичь наилучшей производительности начальной загрузки. + +## SSR vs Пререндеринг + +Если вы интересуетесь серверным рендерингом только для того, чтобы улучшить SEO на нескольких маркетинговых страницах (например, `/`, `/about`, `/contact`, и т.д.), вам скорее всего будет достаточно __пререндеринга__. Вместо того, чтобы заставлять веб-сервер компилировать HTML на лету, пререндеринг просто сгенерирует статичные HTML-файлы для указанных маршрутов на этапе сборки. Преимуществом пререндеринга будет простота реализации, кроме того этот подход позволит вам оставить фронтенд полностью статичным. + +Если вы используете Webpack, то для добавления пререндеринга достаточно установить плагин [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin). Он был тщательно протестирован с приложениями Vue, а его [создатель](https://github.com/chrisvfritz) — член основной команды разработки Vue. + +## Об этом руководстве + +Это руководство ориентировано на SPA приложения с рендерингом на сервере, используя Node.js в качестве сервера. Использование серверного рендеринга Vue совместно с другими технологиями и настройками бэкэнда являются отдельной темой и кратко обсуждается в [отдельном разделе](./guide/non-node.md). + +Это руководство будет очень детальным и предполагает, что вы уже знакомы с самим Vue.js, имеете знания и опыт работы с Node.js и Webpack. Если вы предпочитаете более высокоуровневые решения, обеспечивающие работу из коробки — вам следует попробовать [Nuxt.js](https://nuxtjs.org/). Он построен на том же стеке Vue, но позволяет абстрагироваться от написания шаблонного кода, а также предоставляет некоторые дополнительные возможности, такие как генерация статичного сайта. Однако он может не подойти, если вам необходим полный контроль над структурой приложения. В любом случае, вам будет полезно прочитать это руководство, чтобы лучше понимать, как все составляющие работают вместе. + +По мере прочтения руководства, будет полезным обращаться к официальному [демо HackerNews](https://github.com/vuejs/vue-hackernews-2.0/), в котором используется большинство техник, изложенных в этом руководстве. + +Наконец, обратите внимание, что решения в этом руководстве не являются окончательными — мы решили, что они хорошо работают для нас, но это не означает, что они не могут быть улучшены. Они могут быть пересмотрены в будущем — поэтому не стесняйтесь вносить свой вклад, отправляя пулл-реквесты! diff --git a/docs/ru/api/README.md b/docs/ru/api/README.md new file mode 100644 index 00000000..2a0aaaa8 --- /dev/null +++ b/docs/ru/api/README.md @@ -0,0 +1,270 @@ +--- +sidebar: auto +--- + +# Справочник API + +## createRenderer + +Создаёт экземпляр [`Renderer`](#class-renderer) с (опциональными) [настройками](#renderer-options). + +``` js +const { createRenderer } = require('vue-server-renderer') +const renderer = createRenderer({ /* настройки */ }) +``` + +## createBundleRenderer + +Создаёт экземпляр [`BundleRenderer`](#class-bundlerenderer) с сборкой сервера и (опциональными) [настройками](#renderer-options). + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer(serverBundle, { /* настройки */ }) +``` + +Аргумент `serverBundle` может быть одним из следующих: + +- Абсолютный путь к созданному файлу сборки (`.js` или `.json`). Должен начинаться с `/`, чтобы трактоваться как путь к файлу. + +- Объект сборки, сгенерированный Webpack + `vue-server-renderer/server-plugin`. + +- Строка с кодом JavaScript (не рекомендуется). + +Подробнее в разделах [Представляем Bundle Renderer](../guide/bundle-renderer.md) и [Конфигурация сборки](../guide/build-config.md). + +## Класс: Renderer + +### renderer.renderToString + +Сигнатура: + +``` js +renderer.renderToString(vm, context?, callback?): ?Promise +``` + +Рендерит экземпляр Vue в строку. Объект контекста опционален. Коллбэк является обычным для Node.js коллбэком, где первый аргумент является ошибкой, а второй аргумент — отрендеренной строкой. + +С версии 2.5.0+ коллбэк является опциональным. Когда коллбэк не указан, метод возвращает Promise, который разрешается отрендеренным HTML. + +### renderer.renderToStream + +Сигнатура: + +``` js +renderer.renderToStream(vm[, context]): stream.Readable +``` + +Рендерит экземпляр Vue в [Node.js readable stream](https://nodejs.org/dist/latest-v8.x/docs/api/stream.html#stream_readable_streams). Объект контекста опционален. Подробнее в разделе [Стриминг](../guide/streaming.md). + +## Класс: BundleRenderer + +### bundleRenderer.renderToString + +Сигнатура: + +``` js +bundleRenderer.renderToString([context, callback]): ?Promise +``` + +Рендерит сборку в строку. Объект контекста опционален. Коллбэк является обычным для Node.js коллбэком, где первый аргумент является ошибкой, а второй аргумент — отрендеренной строкой. + +С версии 2.5.0+ коллбэк является опциональным. Когда коллбэк не указан, метод возвращает Promise, который разрешается отрендеренным HTML. + +### bundleRenderer.renderToStream + +Сигнатура: + +``` js +bundleRenderer.renderToStream([context]): stream.Readable +``` + +Рендерит сборку в [Node.js readable stream](https://nodejs.org/dist/latest-v8.x/docs/api/stream.html#stream_readable_streams). Объект контекста опционален. Подробнее в разделе [Стриминг](../guide/streaming.md). + +## Опции рендерера + +### template + +Предоставляет шаблон для всей HTML-страницы. Шаблон должен содержать комментарий ``, который определяет место подстановки отрендеренного контента приложения. + +Шаблон также поддерживает базовые интерполяции с использованием контекста рендера: + +- Используйте двойные фигурные скобки для интерполяции экранированного HTML; +- Используйте тройные фигурные скобки для интерполяции сырого HTML. + +Шаблон автоматически внедряет соответствующий контент, когда определённые свойства найдены в контексте рендера: + +- `context.head`: (string) любая разметка для head, которая должна быть вставлена в заголовочный тег страницы. + +- `context.styles`: (string) любой встроенный CSS, который должен быть вставлен в заголовочный тег страницы. Обратите внимание, что это свойство будет автоматически заполнено при использовании `vue-loader` + `vue-style-loader` для CSS компонента. + +- `context.state`: (Object) начальное состояние хранилища Vuex, которое должно быть внедрено в страницу как `window.__INITIAL_STATE__`. Внедряемый JSON автоматически обрабатывается с помощью [serialize-javascript](https://github.com/yahoo/serialize-javascript) для предотвращения XSS-уязвимостей. + + С версии 2.5.0+, встраиваемый скрипт также автоматически удаляется в режиме production. + +Кроме того, когда предоставлен `clientManifest`, шаблон автоматически внедряет следующее: + +- JavaScript и CSS ресурсы для клиентской части, необходимые для рендеринга (с асинхронными фрагментами добавляемыми автоматически); +- Оптимальные ресурсы `` для отображаемой страницы. + +Вы можете отключить все автоматические внедрения передав `inject: false` в рендерер. + +См. также: + +- [Использование шаблона страниц](../guide/#using-a-page-template) +- [Внедрение ресурсов вручную](../guide/build-config.md#manual-asset-injection) + +### clientManifest + +Предоставляет объект манифеста клиентской сборки, сгенерированный `vue-server-renderer/client-plugin`. Клиентский манифест предоставляет для рендерера сборки необходимую информацию для автоматического внедрения ресурсов в шаблон HTML. Подробнее в разделе [Генерация `clientManifest`](../guide/build-config.md#generating-clientmanifest). + +### inject + +Контролирует, выполнять ли автоматические внедрения при использовании `template`. По умолчанию `true`. + +См. также: [Внедрение ресурсов вручную](../guide/build-config.md#manual-asset-injection). + +### shouldPreload + +Функция, определяющая какие файлы должны иметь `` в генерируемых ресурсах. + +По умолчанию, только JavaScript и CSS файлы будут предзагружаться, так как они абсолютно необходимы для загрузки приложения. + +Для других типов ресурсов, таких как изображения или шрифты, предзагрузка может привести к ненужному увеличению объёмов передаваемой информации и даже к ухудшению производительности, поэтому список файлов, которые нужно предзагружать, зависит от ситуации. Вы можете точно контролировать, что требует предзагрузки, используя опцию `shouldPreload`: + +``` js +const renderer = createBundleRenderer(bundle, { + template, + clientManifest, + shouldPreload: (file, type) => { + // тип определяется на основе расширения файла. + // https://fetch.spec.whatwg.org/#concept-request-destination + if (type === 'script' || type === 'style') { + return true + } + if (type === 'font') { + // предзагружать только woff2 шрифты + return /\.woff2$/.test(file) + } + if (type === 'image') { + // предзагружать только важные изображения + return file === 'hero.jpg' + } + } +}) +``` + +### shouldPrefetch + +- Добавлено в версии 2.5.0+ + +Функция для управления файлами, которые должны содержаться в генерируемых ``. + +По умолчанию все ресурсы в асинхронных частях будут предварительно загружены, так как это директива с низким приоритетом; однако вы можете настроить что требуется предзагружать для лучшего контроля над использованием канала загрузки. Этот параметр ожидает такую же сигнатуру функции, как `shouldPreload`. + +### runInNewContext + +- Используется только в `createBundleRenderer` +- Возможные значения: `boolean | 'once'` (`'once'` поддерживается только с версии 2.3.1+) + +По умолчанию, рендерер сборки будет создавать новый контекст V8 для каждого рендеринга и повторно исполнять всю сборку. Это имеет некоторые преимущества — например, код приложения изолирован от процесса сервера и не нужно беспокоиться [о проблеме «синглтона с состоянием»](../guide/structure.md#avoid-stateful-singletons), которая упоминалась ранее в руководстве. Однако этот режим требует значительных затрат производительности, поскольку повторное выполнение сборки обходится дорого, особенно когда приложение становится большим. + +По умолчанию эта опция имеет значение `true` для обеспечения обратной совместимости, но рекомендуется использовать `runInNewContext: false` или `runInNewContext: 'once'` всегда, когда это возможно. + +> В версии 2.3.0 у этой опции есть ошибка, когда при `runInNewContext: false` сборка всё ещё исполнялась в отдельном глобальном контексте. Информация далее предполагает использование версии 2.3.1+. + +С опцией `runInNewContext: false`, код сборки будет выполняться в том же контексте `global`, что и серверный процесс, поэтому нужно быть осторожным с кодом, который изменяет `global` в вашем приложении. + +С опцией `runInNewContext: 'once'` (добавлено в версии 2.3.1+), сборка выполняется в отдельном контексте `global`, но только один раз при запуске. Это обеспечивает лучшую изоляцию кода приложения поскольку предотвращает случайно загрязнение объекта `global` серверного процесса. Предостережения заключаются в следующем: + +1. Зависимости, которые изменяют `global` (например, полифиллы) не должны быть объявлены внешними зависимостями в этом режиме; +2. Значения, возвращаемые при выполнении сборки будут использовать разные глобальные конструкторы, например, ошибка внутри сборки не будет экземпляром `Error` в серверном процессе. + +См. также: [Структура исходного кода](../guide/structure.md) + +### basedir + +- Используется только в `createBundleRenderer` + +Указание пути базового каталога для серверной сборки для разрешения зависимостей из `node_modules` в нём. Это необходимо только в том случае, если сгенерированный файл сборки располагается в другом месте, в отличии от используемых NPM-зависимостей, или когда ваш `vue-server-renderer` подключен NPM-ссылкой в вашем текущем проекте. + +### cache + +Реализация [кэширования на уровне компонентов](../guide/caching.md#component-level-caching). Объект кэша должен реализовать следующий интерфейс (соответствуя нотациям Flow): + +``` js +type RenderCache = { + get: (key: string, cb?: Function) => string | void; + set: (key: string, val: string) => void; + has?: (key: string, cb?: Function) => boolean | void; +}; +``` + +Для обычного использования достаточно передать [lru-cache](https://github.com/isaacs/node-lru-cache): + +``` js +const LRU = require('lru-cache') + +const renderer = createRenderer({ + cache: LRU({ + max: 10000 + }) +}) +``` + +Обратите внимание, что объект кэша по крайне мере должен реализовывать `get` и `set`. Кроме того, `get` и `has` опционально могут быть асинхронными, если они принимают второй аргумент как коллбэк. Это позволяет кэшу использовать асинхронные API, например для Redis: + +``` js +const renderer = createRenderer({ + cache: { + get: (key, cb) => { + redisClient.get(key, (err, res) => { + // обработка ошибок, если таковые будут + cb(res) + }) + }, + set: (key, val) => { + redisClient.set(key, val) + } + } +}) +``` + +### directives + +Позволяет предоставить серверную реализацию для ваших пользовательских директив: + +``` js +const renderer = createRenderer({ + directives: { + example (vnode, directiveMeta) { + // преобразуем vnode на основе метаданных привязанных к директиве + } + } +}) +``` + +Например, можете посмотреть [серверную реализацию для директивы `v-show`](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js). + +## Плагины webpack + +Webpack плагины предоставляются как отдельные файлы, которые должны быть подключены напрямую: + +``` js +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +``` + +По умолчанию генерируются файлы: + +- `vue-ssr-server-bundle.json` для серверной сборки; +- `vue-ssr-client-manifest.json` для клиентской сборки. + +Имена файлов могут быть изменены при создании экземпляров плагина: + +``` js +const plugin = new VueSSRServerPlugin({ + filename: 'my-server-bundle.json' +}) +``` + +Подробнее в разделе [Конфигурация сборки](../guide/build-config.md). diff --git a/docs/ru/guide/README.md b/docs/ru/guide/README.md new file mode 100644 index 00000000..fe2e4473 --- /dev/null +++ b/docs/ru/guide/README.md @@ -0,0 +1,157 @@ +# Начало работы + +## Установка + +``` bash +npm install vue vue-server-renderer --save +``` + +В руководстве мы будем использовать NPM, но вы свободно можете использовать и [Yarn](https://yarnpkg.com/en/). + +#### Примечания + +- Рекомендуется использовать Node.js версии 6+. +- `vue-server-renderer` и `vue` должны иметь одинаковые версии. +- `vue-server-renderer` зависит от некоторых нативных модулей Node.js и поэтому может использоваться только в Node.js. Возможно в будущем мы предоставим более простую сборку, которая сможет быть запущена в других средах исполнения JavaScript. + +## Рендеринг экземпляра Vue + +``` js +// Шаг 1: Создаём экземпляр Vue +const Vue = require('vue') +const app = new Vue({ + template: `
Hello World
` +}) + +// Шаг 2: Создаём рендерер +const renderer = require('vue-server-renderer').createRenderer() + +// Шаг 3: Рендерим экземпляр Vue в HTML +renderer.renderToString(app, (err, html) => { + if (err) throw err + console.log(html) + // =>
Hello World
+}) + +// с версии 2.5.0+, возвращает Promise если коллбэк не указан: +renderer.renderToString(app).then(html => { + console.log(html) +}).catch(err => { + console.error(err) +}) +``` + +## Интеграция с сервером + +Это достаточно просто когда мы используем сервер на Node.js, например [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: `
Вы открыли URL: {{ url }}
` + }) + + renderer.renderToString(app, (err, html) => { + if (err) { + res.status(500).end('Внутренняя ошибка сервера') + return + } + res.end(` + + + Привет + ${html} + + `) + }) +}) + +server.listen(8080) +``` + +## Использование шаблона страниц + +Когда вы рендерите приложение Vue, рендерер генерирует только разметку приложения. В примере выше нам потребовалось обернуть вывод дополнительным кодом для создания обычной HTML-страницы. + +Вы можете упростить это, предоставив шаблон страницы при создании рендерера. Чаще всего нам требуется расположить шаблон в отдельном файле, например `index.template.html`: + +``` html + + + Привет + + + + +``` + +Обратите внимание на комментарий `` — сюда будет подставлена разметка вашего приложения. + +Теперь мы можем прочитать этот файл и передать его в рендерер Vue: + +``` js +const renderer = createRenderer({ + template: require('fs').readFileSync('./index.template.html', 'utf-8') +}) + +renderer.renderToString(app, (err, html) => { + console.log(html) // выведется код всей страницы, с подставленным кодом приложения. +}) +``` + +### Интерполяции в шаблоне + +Шаблон поддерживает простые интерполяции. Например: + +``` html + + + + {{ title }} + + + {{{ meta }}} + + + + + +``` + +Мы можем предоставить необходимые данные для интерполяции, передав объект контекста для рендера вторым аргументом в `renderToString`: + +``` js +const context = { + title: 'привет', + meta: ` + + + ` +} + +renderer.renderToString(app, context, (err, html) => { + // заголовок страницы будет "привет" + // meta-теги также будут подставлены в код страницы +}) +``` + +Объект `context` может также использоваться совместно с экземпляром Vue приложения, что разрешает компонентам динамически регистрировать данные для интерполяции в шаблоне. + +Кроме того, шаблон поддерживает некоторые продвинутые функции: + +- Автоматическую подстановку критически важного CSS при использовании `*.vue` компонентов; +- Автоматическую подстановку ссылок и подсказок для ресурсов (preload / prefetch) при использовании `clientManifest`; +- Автоматическую подстановку и предотвращение XSS при встраивании Vuex-состояния для гидратации на стороне клиента. + +Мы обсудим это дальше, когда будем разбирать все связанные концепции. diff --git a/docs/ru/guide/build-config.md b/docs/ru/guide/build-config.md new file mode 100644 index 00000000..63ed3148 --- /dev/null +++ b/docs/ru/guide/build-config.md @@ -0,0 +1,215 @@ +# Конфигурация сборки + +Мы предполагаем, что вы уже знаете как настраивать Webpack для клиентской части проектов. Конфигурация для проекта SSR будет во многом схожей, но мы предлагаем разбивать конфигурацию на три файла: *base*, *client* и *server*. Базовая конфигурация (base) содержит конфигурацию, совместно используемую для обоих окружений, такие как пути вывода, псевдонимы и загрузчики. Конфигурация сервера (server) и конфигурация клиента (client) просто расширяют базовую конфигурацию, используя [webpack-merge](https://github.com/survivejs/webpack-merge). + +## Конфигурация серверной части + +Конфигурация серверной части предназначена для создания серверной сборки, которая будет передана в `createBundleRenderer`. Это должно выглядеть так: + +``` js +const merge = require('webpack-merge') +const nodeExternals = require('webpack-node-externals') +const baseConfig = require('./webpack.base.config.js') +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') + +module.exports = merge(baseConfig, { + // Укажите точку входа серверной части вашего приложения + entry: '/path/to/entry-server.js', + + // Это позволяет Webpack обрабатывать динамические импорты в Node-стиле, + // а также сообщает `vue-loader` генерировать серверно-ориентированный код + // при компиляции компонентов Vue. + target: 'node', + + // Для поддержки source map в bundle renderer + devtool: 'source-map', + + // Это сообщает что в серверной сборке следует использовать экспорты в стиле Node + output: { + libraryTarget: 'commonjs2' + }, + + // https://webpack.js.org/configuration/externals/#function + // https://github.com/liady/webpack-node-externals + // Внешние зависимости приложения. Это значительно ускоряет процесс + // сборки серверной части и уменьшает размер итогового файла сборки. + externals: nodeExternals({ + // не выделяйте зависимости, которые должны обрабатываться Webpack. + // здесь вы можете добавить больше типов файлов, например сырые *.vue файлы + // нужно также указывать белый список зависимостей изменяющих `global` (например, полифиллы) + whitelist: /\.css$/ + }), + + // Этот плагин преобразует весь результат серверной сборки + // в один JSON-файл. Имя по умолчанию будет + // `vue-ssr-server-bundle.json` + plugins: [ + new VueSSRServerPlugin() + ] +}) +``` + +После создания `vue-ssr-server-bundle.json` просто передайте путь к файлу в `createBundleRenderer`: + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { + // ...другие настройки рендерера +}) +``` + +В качестве альтернативы, вы также можете передать сборку как объект в `createBundleRenderer`. Это полезно для горячей перезагрузки во время разработки — см. демо HackerNews для [примера настройки](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js). + +### Ограничения externals + +Обратите внимание, что в параметре `externals` мы указываем белый список CSS файлов. Это связано с тем, что CSS, импортированный из зависимостей всё равно должен быть обработан Webpack. Если вы импортируете любые другие типы файлов, которые также полагаются на Webpack (например, `*.vue`, `*.sass`), вы должны их также добавить в белый список. + +Если вы используете `runInNewContext: 'once'` или `runInNewContext: true`, вам также требуется добавить в белый список являются полифиллы, которые изменяют `global`, например `babel-polyfill`. Это связано с тем, что при использовании режима нового контекста, **код внутри серверной сборки имеет свой собственный объект `global`**. Поскольку это не будет нужно на сервере при использовании Node 7.6+, на самом деле проще просто импортировать его в клиентской точке входа. + +## Конфигурация клиентской части + +Конфигурация клиентской части может оставаться практически такой же, как и базовой. Очевидно, вам нужно указать `entry` на файл входной точки клиентской части. Кроме того, если вы используете `CommonsChunkPlugin`, убедитесь, что используете его только в конфигурации клиентской части, потому что для серверной сборки требуется одна точка входа. + +### Generating `clientManifest` + +> требуется версия 2.3.0+ + +Помимо серверной сборки, мы также можем сгенерировать манифест сборки. С помощью манифеста клиентской части и серверной сборки, у рендерера появится информация о серверной *и* клиентской сборке, поэтому он может автоматически внедрять [директивы preload/prefetch](https://css-tricks.com/prefetching-preloading-prebrowsing/) в ссылки на CSS / теги script в отображаемом HTML. + +Выгода тут двойная: + +1. Он может заменить `html-webpack-plugin` для внедрения правильных URL-адресов ресурсов, когда в генерируемых именах файлов есть хэши. + +2. При рендеринге сборки, которая использует возможности разделения кода Webpack, мы можем гарантировать, что оптимальные части были предзагружены и предзаполнены, а также интеллектуально внедрять теги ` + + + + +` +``` + +### Внедрение ресурсов вручную + +По умолчанию, внедрение ресурсов выполняется автоматически при использовании опции `template` для рендера. Но иногда вам может понадобиться больше контроля над тем, как ресурсы должны внедряться в шаблон, или, возможно, вы не используете шаблон вообще. В таком случае вы можете передать опцию `inject: false` при создании рендерера и производить внедрение ресурсов вручную. + +В коллбэке `renderToString` объект `context`, который вы передали, предоставляет следующие методы: + +- `context.renderStyles()` + + Возвращает встроенные теги `