|
| 1 | +# 빌드 설정 |
| 2 | + |
| 3 | +클라이언트 전용 프로젝트를 위해 webpack을 구성하는 방법을 이미 알고있다고 가정합니다. SSR 프로젝트 설정은 거의 유사하지만 구성을 세가지로(*기본*, *클라이언트*와 *서버*)로 나누는 것이 좋습니다. 기본 구성에는 출력될 경로 별칭 및 로더와 같은 두 환경에 공유되는 설정들이 있습니다. 서버와 클라이언트 설정은 단순히 [webpack-merge](https://github.com/survivejs/webpack-merge)를 사용해 기본 설정을 확장합니다. |
| 4 | + |
| 5 | +## 서버 설정 |
| 6 | + |
| 7 | +서버 설정은 `createBundleRenderer`에 전달될 서버 번들을 생성하기 위한 것 입니다. 아래와 같습니다. |
| 8 | + |
| 9 | +```js |
| 10 | +const merge = require('webpack-merge') |
| 11 | +const nodeExternals = require('webpack-node-externals') |
| 12 | +const baseConfig = require('./webpack.base.config.js') |
| 13 | +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') |
| 14 | +module.exports = merge(baseConfig, { |
| 15 | + // Point entry to your app's server entry file |
| 16 | + entry: '/path/to/entry-server.js', |
| 17 | + // This allows webpack to handle dynamic imports in a Node-appropriate |
| 18 | + // fashion, and also tells `vue-loader` to emit server-oriented code when |
| 19 | + // compiling Vue components. |
| 20 | + target: 'node', |
| 21 | + // For bundle renderer source map support |
| 22 | + devtool: 'source-map', |
| 23 | + // This tells the server bundle to use Node-style exports |
| 24 | + output: { |
| 25 | + libraryTarget: 'commonjs2' |
| 26 | + }, |
| 27 | + // https://webpack.js.org/configuration/externals/#function |
| 28 | + // https://github.com/liady/webpack-node-externals |
| 29 | + // Externalize app dependencies. This makes the server build much faster |
| 30 | + // and generates a smaller bundle file. |
| 31 | + externals: nodeExternals({ |
| 32 | + // do not externalize dependencies that need to be processed by webpack. |
| 33 | + // you can add more file types here e.g. raw *.vue files |
| 34 | + // you should also whitelist deps that modifies `global` (e.g. polyfills) |
| 35 | + whitelist: /\.css$/ |
| 36 | + }), |
| 37 | + // This is the plugin that turns the entire output of the server build |
| 38 | + // into a single JSON file. The default file name will be |
| 39 | + // `vue-ssr-server-bundle.json` |
| 40 | + plugins: [ |
| 41 | + new VueSSRServerPlugin() |
| 42 | + ] |
| 43 | +}) |
| 44 | +``` |
| 45 | + |
| 46 | +`vue-ssr-server-bundle.json`이 생성된 후 파일 경로를 `createBundleRenderer`에 전달합니다. |
| 47 | + |
| 48 | +```js |
| 49 | +const { createBundleRenderer } = require('vue-server-renderer') |
| 50 | +const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', { |
| 51 | + // ...other renderer options |
| 52 | +}) |
| 53 | +``` |
| 54 | + |
| 55 | +또는, 번들을 Object로 만들어 `createBundleRenderer`에 전달할 수 있습니다. 이는 개발중 핫 리로드를 사용할 때 유용합니다. HackerNews 데모의 [설정](https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js)을 참조하세요. |
| 56 | + |
| 57 | +### Externals 주의사항 |
| 58 | + |
| 59 | +`externals`옵션에서는 CSS파일을 허용하는 목록에 추가합니다. 의존성에서 가져온 CSS는 여전히 webpack에서 처리해야합니다. webpack을 사용하는 다른 유형의 파일을 가져오는 경우(예: `*.vue`, `*.sass`) 파일을 허용 목록에 추가해야합니다. |
| 60 | + |
| 61 | +`runInNewContext: 'once'` 또는 `runInNewContext: true`를 사용하는 경우 `전역 변수`를 수정하는 폴리필을 허용 목록에 추가해야합니다. 예를 들어 `babel-polyfill`이 있습니다. 새 컨텍스트 모드를 사용할 때 **서버 번들의 내부 코드가 자체적으로 `전역` 객체를 가지고 있기 때문입니다.** Node 7.6버전 이상을 사용할 때 서버에서는 실제로 필요하지 않으므로 클라이언트에서 가져오는 것이 더 쉽습니다. |
| 62 | + |
| 63 | +## 클라이언트 설정 |
| 64 | + |
| 65 | +클라이언트 설정은 기본 설정과 거의 동일합니다. 클라이언트의 `entry`파일을 가리키면 됩니다. 그 외에도 `CommonsChunkPlugin`을 사용하는 경우 서버 번들에 단일 entry 청크가 필요하기 때문에 클라이언트 설정에서만 사용해야합니다. |
| 66 | + |
| 67 | +### `clientManifest` 생성 |
| 68 | + |
| 69 | +> 2.3.0버전 이후 지원 |
| 70 | +
|
| 71 | +서버 번들 외에도 클라이언트 빌드 매니페스트를 생성할 수도 있습니다. 클라이언트 매니페스트와 서버 번들을 사용하면 렌더러에 서버 및 클라이언트 빌드 정보가 *모두* 포함되므로 [프리로드/프리페치 디렉티브](https://css-tricks.com/prefetching-preloading-prebrowsing/) 와 CSS 링크 / script 태그를 렌더링된 HTML에 자동으로 삽입할 수 있습니다. |
| 72 | + |
| 73 | +두가지 장점이 있습니다. |
| 74 | + |
| 75 | +1. 생성된 파일 이름에 해시가 있을 때 올바른 에셋 URL을 삽입하기 위해 `html-webpack-plugin`를 대체할 수 있습니다. |
| 76 | +2. webpack의 주문형 코드 분할 기능을 활용하는 번들을 렌더링할 때 최적의 청크를 프리로드/프리페치하고 클라이언트에 폭포수 요청을 피하기 위해 필요한 비동기 청크에 `<script></script>` 태그를 지능적으로 삽입할 수 있습니다. 이는 TTI(첫 작동까지의 시간)을 개선합니다. |
| 77 | + |
| 78 | +클라이언트 매니페스트를 사용하려면 클라이언트 설정은 아래와 같아야 합니다. |
| 79 | + |
| 80 | +```js |
| 81 | +const webpack = require('webpack') |
| 82 | +const merge = require('webpack-merge') |
| 83 | +const baseConfig = require('./webpack.base.config.js') |
| 84 | +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') |
| 85 | +module.exports = merge(baseConfig, { |
| 86 | + entry: '/path/to/entry-client.js', |
| 87 | + plugins: [ |
| 88 | + // Important: this splits the webpack runtime into a leading chunk |
| 89 | + // so that async chunks can be injected right after it. |
| 90 | + // this also enables better caching for your app/vendor code. |
| 91 | + new webpack.optimize.CommonsChunkPlugin({ |
| 92 | + name: "manifest", |
| 93 | + minChunks: Infinity |
| 94 | + }), |
| 95 | + // This plugins generates `vue-ssr-client-manifest.json` in the |
| 96 | + // output directory. |
| 97 | + new VueSSRClientPlugin() |
| 98 | + ] |
| 99 | +}) |
| 100 | +``` |
| 101 | + |
| 102 | +그런 다음 생성한 클라이언트 매니페스트를 페이지 템플릿과 함께 사용합니다. |
| 103 | + |
| 104 | +```js |
| 105 | +const { createBundleRenderer } = require('vue-server-renderer') |
| 106 | +const template = require('fs').readFileSync('/path/to/template.html', 'utf-8') |
| 107 | +const serverBundle = require('/path/to/vue-ssr-server-bundle.json') |
| 108 | +const clientManifest = require('/path/to/vue-ssr-client-manifest.json') |
| 109 | +const renderer = createBundleRenderer(serverBundle, { |
| 110 | + template, |
| 111 | + clientManifest |
| 112 | +}) |
| 113 | +``` |
| 114 | + |
| 115 | +이 설정을 사용하면 코드 분할을 사용하는 빌드의 서버렌더링된 HTML이 다음과 같이 표시됩니다.(전체가 자동으로 주입됩니다) |
| 116 | + |
| 117 | +```html |
| 118 | + |
| 119 | + |
| 120 | + <!-- chunks used for this render will be preloaded --> |
| 121 | + <link rel="preload" href="/manifest.js" as="script"> |
| 122 | + <link rel="preload" href="/main.js" as="script"> |
| 123 | + <link rel="preload" href="/0.js" as="script"> |
| 124 | + <!-- unused async chunks will be prefetched (lower priority) --> |
| 125 | + <link rel="prefetch" href="/1.js" as="script"> |
| 126 | + |
| 127 | + |
| 128 | + <!-- app content --> |
| 129 | + <div data-server-rendered="true"><div data-segment-id="430777">async </div></div> |
| 130 | + <!-- manifest chunk should be first --> |
| 131 | + <script src="/manifest.js"></script> |
| 132 | + <!-- async chunks injected before main chunk --> |
| 133 | + <script src="/0.js"></script> |
| 134 | + <script src="/main.js"></script> |
| 135 | + |
| 136 | +` |
| 137 | +``` |
| 138 | + |
| 139 | +### 수동 에셋 주입 |
| 140 | + |
| 141 | +`template` 렌더링 옵션을 제공하면 기본적으로 에셋 주입이 자동으로 수행됩니다. 그러나 템플릿에 에셋을 삽입하는 방법을 세밀하게 제어하거나 템플릿을 전혀 사용하지 않을 수도 있습니다. 이 경우 렌더러를 만들때 `inject: false`를 전달하고 수동으로 에셋 주입을 수행할 수 있습니다. |
| 142 | + |
| 143 | +`renderToString`콜백에서 전달한 `context`객체는 다음 메소드를 노출합니다. |
| 144 | + |
| 145 | +- `context.renderStyles() ` |
| 146 | + |
| 147 | +렌더링 중에 사용된 `*.vue` 컴포넌트에서 수집된 모든 CSS가 포함된 인라인 `<style></style>`태그가 반환됩니다. 자세한 내용은 [CSS 관리](./css.md)를 참조하십시오. |
| 148 | + |
| 149 | +`clientManifest`가 제공되면 반환되는 문자열에는 webpack에서 생성한 CSS파일 (예: `extract-text-webpack-plugin` 또는 imported with `file-loader`)에 대한 `<link rel="stylesheet">` 태그가 포함됩니다. |
| 150 | + |
| 151 | +- `context.renderState(options?: Object)` |
| 152 | + |
| 153 | +이 메소드는 `context.state`를 직렬화하고 스테이트를 `window.__INITIAL_STATE__`로 포함하는 인라인 스크립트를 리턴합니다. |
| 154 | + |
| 155 | +컨텍스트 스테이트 키와 윈도우 스테이트 키는 옵션 객체를 전달하여 사용자 정의할 수 있습니다. |
| 156 | + |
| 157 | +```js |
| 158 | + context.renderState({ |
| 159 | + contextKey: 'myCustomState', |
| 160 | + windowKey: '__MY_STATE__' |
| 161 | + }) |
| 162 | + // -> <script>window.__MY_STATE__={...}</script> |
| 163 | +``` |
| 164 | + |
| 165 | +- `context.renderScripts()` |
| 166 | + - `clientManifest`를 필요로 합니다. |
| 167 | + |
| 168 | +이 메소드는 클라이언트 애플리케이션이 시작하는데 필요한 `<script></script>`태그를 반환합니다. 애플리케이션 코드에서 비동기 코드 분할을 사용하는 경우 이 메소드는 포함할 올바른 비동기 청크를 지능적으로 유추합니다. |
| 169 | + |
| 170 | +- `context.renderResourceHints()` |
| 171 | + - `clientManifest`를 필요로 합니다. |
| 172 | + |
| 173 | +이 메소드는 현재 렌더링된 페이지에 필요한 `<link rel="preload/prefetch">` 리소스 힌트를 반환합니다. 기본적으로 다음과 같습니다. |
| 174 | + |
| 175 | +- 페이지에 필요한 JavaScript 및 CSS 파일을 미리 로드 |
| 176 | +- 나중에 필요할 수 있는 비동기 JavaScript 청크 프리페치 |
| 177 | + |
| 178 | +미리 로드된 파일은 [`shouldPreload`](./api.md#shouldpreload)옵션을 사용해 추가로 사용자 정의할 수 있습니다. |
| 179 | + |
| 180 | +- `context.getPreloadFiles()` |
| 181 | + - `clientManifest`를 필요로 합니다. |
| 182 | + |
| 183 | +이 메소드는 문자열을 반환하지 않고 대신 미리 로드해야 할 에셋을 나타내는 파일 객체의 배열을 반환합니다. 이는 프로그래밍 방식으로 HTTP/2 서버 푸시를 하는데 사용할 수 있습니다. |
| 184 | + |
| 185 | +`createBundleRenderer`에 전달된 `template`은 `context`를 사용하여 보간되므로 템플릿안에서 이러한 메소드를 사용할 수 있습니다.(`inject: false` 옵션과 함께) |
| 186 | + |
| 187 | +```html |
| 188 | + |
| 189 | + |
| 190 | + <!-- use triple mustache for non-HTML-escaped interpolation --> |
| 191 | + {{{ renderResourceHints() }}} |
| 192 | + {{{ renderStyles() }}} |
| 193 | + |
| 194 | + |
| 195 | + <!--vue-ssr-outlet--> |
| 196 | + {{{ renderState() }}} |
| 197 | + {{{ renderScripts() }}} |
| 198 | + |
| 199 | + |
| 200 | +``` |
| 201 | + |
| 202 | +`template`을 사용하지 않으면 문자열을 직접 연결할 수 있습니다. |
0 commit comments