Vue 서버 렌더링(SSR)
서버 렌더링이 무엇인지 간단하게 이해하면 구성 요소나 페이지를 서버를 통해 html 문자열을 생성하여 브라우저에 보내고 마지막으로 정적 표기'혼합'을 클라이언트의 완전한 상호작용을 하는 응용 프로그램으로 한다.전통적인 SPA(한 페이지 응용)에 비해 서비스 측의 렌더링은 SEO에 더욱 유리하고 페이지의 첫 화면 마운트 시간을 줄일 수 있다. 물론 개발에 있어 우리는 서비스 측의 렌더링을 지원하는 지식을 많이 배워야 한다.또한 서비스 측의 렌더링은 서버에 대한 압력도 상대적으로 크다. 서버가 정적 파일을 간단하게 출력하는 것에 비해 노드를 통해 페이지를 렌더링하여 클라이언트에게 전달하는 데 비용이 많이 들기 때문에 해당하는 서버 부하를 준비하는 데 주의해야 한다.
간단한 예
// 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
})
위의 예는
vue-server-renderer
npm 패키지를 이용하여 하나의 vue 예시를 마지막에 html로 렌더링했다.이 html을 클라이언트에게 보내면 서버 렌더링을 쉽게 실현할 수 있습니다.const server = require('express')()
server.get('*', (req, res) => {
// ... html
res.end(html)
})
server.listen(8080)
2. 공식 렌더링 절차
위의 예는 간단하지만 실제 프로젝트에서 루트, 데이터, 구성 요소화 등을 고려해야 하기 때문에 서버 렌더링은 하나
vue-server-renderer
npm 패키지만으로 쉽게 할 수 있는 것이 아니다. 다음은 Vue 공식 서버 렌더링 설명도를 제시한다.흐름도는 대체적으로 Source(원본)를 웹 패키지를 통해 두 개의 버블을 포장하는데 그 중에서 서버 버블은 서버에 사용하고 서버는 렌더기 버블렌더를 통해 버블을 html로 만들어 브라우저에 사용한다.또 다른 클라이언트 버블은 브라우저에 사용되는 것이다. 서버는 초기 첫 화면 페이지를 만드는 데 필요한 html일 뿐이고 후기의 상호작용과 데이터 처리는 브라우저 스크립트를 지원할 수 있는 클라이언트 버블이 완성해야 한다는 것을 잊지 마라.
구체적으로 어떻게 실현
실현 과정은 위의 설명도를 코드로 바꾸어 실현하는 것이다. 그러나 이 과정은 약간 복잡하고 인내심을 가지고 모든 세부 사항을 퇴고해야 한다.
1、먼저 하나의 기본판을 실현한다
프로젝트 구조의 예:
├── build
│ ├── webpack.base.config.js #
│ ├── webpack.client.config.js #
│ ├── webpack.server.config.js #
└── src
├── router
│ └── index.js #
└── views
│ ├── comp1.vue #
│ └── copm2.vue #
├── App.vue # vue
├── app.js # app
├── client-entry.js # client
├── index.template.html # html
├── server-entry.js # server
├── server.js # server
여기에서:
(1)、comp1.vue 및 copm2.vue 구성 요소
1
export default {
data () {
return {
msg: ''
}
}
}
(2)、App.vue 최고급 vue 구성 요소
vue-ssr
to comp1
to comp2
(3)、index.template.html
<html lang="zh_CN">
<head>
<title>{{ title }}title>
<meta charset="utf-8"/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1"/>
<meta name="renderer" content="webkit"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"/>
<meta name="theme-color" content="#f60"/>
head>
<body>
body>
html>
(4) 위의 기초 코드는 해석하지 않고 다음에 본다.
라우터
import Vue from 'vue'
import Router from 'vue-router'
import comp1 from '../views/comp1.vue'
import comp2 from '../views/comp2.vue'
Vue.use(Router)
export function createRouter () {
return new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/comp1',
component: comp1
},
{
path: '/comp2',
component: comp2
},
{ path: '/', redirect: '/comp1' }
]
})
}
app.js app 입구 파일
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
export function createApp (ssrContext) {
const router = createRouter()
const app = new Vue({
router,
ssrContext,
render: h => h(App)
})
return { app, router }
}
우리는
createApp
를 통해 루트 Vue 실례를 폭로했다. 이것은 모든 사용자가 새로운 실례를 얻어 상태 오염을 피하기 위해서이다. 그래서 우리는 반복적으로 실행할 수 있는 공장 함수createApp
를 썼다.같은 루트 루터를 저희도 같은 처리 방식createRouter
으로 루터의 실례를 폭로합니다.(5)client-entry.js client 포털 파일
import { createApp } from './app'
const { app, router } = createApp()
router.onReady(() => {
app.$mount('#app')
})
클라이언트 코드는 루트 해석이 완료되었을 때 app를 #app 탭에 마운트합니다
(7)server-entry.js 서버의 입구 파일
import { createApp } from './app'
export default context => {
// router.onReady , Promise
//
return new Promise((resolve, reject) => {
const { app, router } = createApp(context)
router.push(context.url)
router.onReady(() => {
resolve(app)
}, reject)
})
}
서버 입구 파일
2、포장
첫 번째 단계에서 우리는 루트가 있는 일상 기능 템플릿 코드를 실현했다. 이어서 우리는 웹 패키지를 이용하여 위의 코드를 서버와 클라이언트 키의 코드로 포장해야 한다. 입구 파일은 각각
server-entry.js
과client-entry.js
이다.(1) 웹 패키지 구축 설정
일반 설정은 베이스,client,server 세 개의 파일로 나뉜다.기본 설정 (base config) 은 출력 경로 (output path), 별명 (alias),loader 등 두 환경에서 공유되는 설정을 포함합니다.서버 설정(server config)과 클라이언트 설정(client config)은 웹 패키지-merge를 사용하여 기본 설정을 간단하게 확장할 수 있습니다.
webpack.base.config.js 프로필
const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
devtool: '#cheap-module-source-map',
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: '/',
filename: '[name]-[chunkhash].js'
},
resolve: {
alias: {
'public': path.resolve(__dirname, '../public'),
'components': path.resolve(__dirname, '../src/components')
},
extensions: ['.js', '.vue']
},
module: {
noParse: /es6-promise\.js$/,
rules: [
{
test: /\.(js|vue)/,
use: 'eslint-loader',
enforce: 'pre',
exclude: /node_modules/
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
preserveWhitespace: false,
postcss: [
require('autoprefixer')({
browsers: ['last 3 versions']
})
]
}
}
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name].[hash:7].[ext]'
}
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.json/,
use: 'json-loader'
}
]
},
performance: {
maxEntrypointSize: 300000,
hints: 'warning'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false }
}),
new ExtractTextPlugin({
filename: 'common.[chunkhash].css'
})
]
}
webpack.client.config.js 프로필
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const glob = require('glob')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const config = merge(base, {
entry: {
app: './src/client-entry.js'
},
resolve: {
alias: {
'create-api': './create-api-client.js'
}
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.VUE_ENV': '"client"',
'process.env.DEBUG_API': '"true"'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
return (
/node_modules/.test(module.context) && !/\.css$/.test(module.require)
)
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest'
}),
//
// JSON 。
// `vue-ssr-server-bundle.json`
new VueSSRClientPlugin()
]
})
module.exports = config
webpack.server.config.js 프로필
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const base = require('./webpack.base.config')
const nodeExternals = require('webpack-node-externals')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(base, {
target: 'node',
devtool: '#source-map',
entry: './src/server-entry.js',
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
resolve: {
alias: {
'create-api': './create-api-server.js'
}
},
externals: nodeExternals({
whitelist: /\.css$/
}),
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.VUE_ENV': '"server"'
}),
new VueSSRServerPlugin()
]
})
웹 패키지 설정이 완료되었습니다. 사실 물건도 많지 않습니다. 모두 일반적인 설정입니다.주의해야 할 것은
webpack.server.config.js
설정입니다. output은commonjs의library를 생성하는 것입니다. VueSSRServerPlugin
는 서버의 전체 출력을 하나의 JSON 파일로 구축하는 플러그인입니다.(2)、 webpack build poj
build 코드
webpack --config build/webpack.client.config.js
webpack --config build/webpack.server.config.js
패키지를 포장하면 서버와 같은 패키지 파일이 생성됩니다.config를 포장하면
vue-ssr-server-bundle.json
파일을 생성합니다. 이 파일은 createBundleRenderer
에 사용되며 서버에서 html 파일을 렌더링하는 데 사용됩니다.const { createBundleRenderer } = require('vue-server-renderer')
const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', {
// ……renderer
})
세심한 너는 client도 발견할 수 있어.config는 고객센터에서 사용하는 js 파일을 생성할 뿐만 아니라
vue-ssr-client-manifest.json
파일도 생성할 수 있습니다. 이 파일은 클라이언트 구축 목록입니다. 서버는 이 구축 목록을 가지고 초기화에 사용할 js 발걸음이나 css를 찾아서 html에 주입하여 브라우저에 보냅니다.(3) 서버 렌더링
사실 위에는 모두 준비 작업이 있는데 가장 중요한 단계는 웹 패키지가 구축된 자원 코드를 서비스 측에 html을 생성하는 데 사용하는 것이다.우리는 노드로 서버 응용 프로그램을 작성하여 포장된 자원을 통해 html을 생성하여 브라우저에 보내야 한다
server.js
const fs = require('fs')
const path = require('path')
const Koa = require('koa')
const KoaRuoter = require('koa-router')
const serve = require('koa-static')
const { createBundleRenderer } = require('vue-server-renderer')
const LRU = require('lru-cache')
const resolve = file => path.resolve(__dirname, file)
const app = new Koa()
const router = new KoaRuoter()
const template = fs.readFileSync(resolve('./src/index.template.html'), 'utf-8')
function createRenderer (bundle, options) {
return createBundleRenderer(
bundle,
Object.assign(options, {
template,
cache: LRU({
max: 1000,
maxAge: 1000 * 60 * 15
}),
basedir: resolve('./dist'),
runInNewContext: false
})
)
}
let renderer
const bundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
renderer = createRenderer(bundle, {
clientManifest
})
/**
*
* @param ctx
* @param next
* @returns {Promise}
*/
function render (ctx, next) {
ctx.set("Content-Type", "text/html")
return new Promise (function (resolve, reject) {
const handleError = err => {
if (err && err.code === 404) {
ctx.status = 404
ctx.body = '404 | Page Not Found'
} else {
ctx.status = 500
ctx.body = '500 | Internal Server Error'
console.error(`error during render : ${ctx.url}`)
console.error(err.stack)
}
resolve()
}
const context = {
title: 'Vue Ssr 2.3',
url: ctx.url
}
renderer.renderToString(context, (err, html) => {
if (err) {
return handleError(err)
}
console.log(html)
ctx.body = html
resolve()
})
})
}
app.use(serve('/dist', './dist', true))
app.use(serve('/public', './public', true))
router.get('*', render)
app.use(router.routes()).use(router.allowedMethods())
const port = process.env.PORT || 8089
app.listen(port, '0.0.0.0', () => {
console.log(`server started at localhost:${port}`)
})
여기에서 우리는 처음에 데모에서 사용했던
vue-server-renderer
npm 패키지를 사용했다. 읽기vue-ssr-server-bundle.json
와 vue-ssr-client-manifest.json
파일인rendererer를 통해 html를 내고 마지막ctx.body = html
을 브라우저에 보낸다. 우리는 console.log(html)
html을 내서 서버가 도대체 어떤 신성함을 나타냈는지 보려고 한다.
<html lang="zh_CN">
<head>
<title>Vue Ssr 2.3title>
<meta charset="utf-8"/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1"/>
<meta name="renderer" content="webkit"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"/>
<meta name="theme-color" content="#f60"/>
<link rel="preload" href="/dist/manifest-56dda86c1b6ac68c0279.js" as="script"><link rel="preload" href="/dist/vendor-3504d51340141c3804a1.js" as="script"><link rel="preload" href="/dist/app-ae1871b21fa142b507e8.js" as="script"><style data-vue-ssr-id="41a1d6f9:0">
.link {
margin: 10px;
}
style><style data-vue-ssr-id="7add03b4:0">style>head>
<body>
<div id="app" data-server-rendered="true">
<h1>vue-ssrh1>
<a href="/comp1" class="link router-link-exact-active router-link-active">to comp1a>
<a href="/comp2" class="link">to comp2a>
<section class="view"> 1section>
div>
<script src="/dist/manifest-56dda86c1b6ac68c0279.js" defer>
script>
<script src="/dit/vendor-3504d51340141c3804a1.js" defer>script>
<script src="/dist/app-ae1871b21fa142b507e8.js" defer>script>
body>
html>
서비스 측이 루트 아래의
1
도 과장된 것을 볼 수 있다. 고객지원 측에 동적 로드를 시키지 않고 그 다음은 html도 일부 주입되었다.<h3 class="heading">4. 소결 </h3>
<p>이 편은 vues sr의 간단한 절차를 간단하게 이해했고 상기 예의 demo는github에 놓아서 issue와star를 환영합니다.서버 렌더링과 비교적 중요한 부분은 첫 화면 데이터의 획득 렌더링이다. 일반적인 페이지 전시에서 일부 인터넷 데이터가 초기화된다. 서버 렌더링은 이런 데이터를 html에 삽입할 수 있다. 이 부분에 관련된 지식도 적지 않기 때문에 다음에 이야기하자.lt;/p>
<h3 class="heading">2018-5-28 업데이트 </h3>
<p>평론 구역에서 언급한 버그는 이미 복원되었습니다. 만약에 다른 문제가 있으면github에 대응하는 공사에서 issues를 제기할 수 있습니다. github의 issues는markdown 형식이 있어서 문제 설명과 토론을 편리하게 할 수 있습니다. 문제 설명은 가능한 한 명확하게 할 수 있습니다. 한편, 작가는 문제 원인을 조사할 수 있습니다<p>
<h3 class="heading">2018-7-14 업데이트 </h3>
<blockquote>
<p>Unexpected token < 해결하기;오보 문제p>
</blockquote>
<p>오류 원인은 html의 자바스크립트 자원 경로 오류로 인해 </p>
<p>서비스 측에서 보여준 html은script 라벨을 가지고 서비스 측에서 요청한 것에 대응합니다.js 정적 자원 경로가 잘못되었습니다 </p>
<pre><code class="hljs bash copyable"><script src=<span class="hljs-string">"/dist/manifest-56dda86c1b6ac68c0279.js"</span> defer>
"/dit/vendor-3504d51340141c3804a1.js"</span> defer>
"/dist/app-ae1871b21fa142b507e8.js"</span> defer>
댓글에 다음과 같은 수정이 언급되어 있습니다. 버그를 지적해 주셔서 감사합니다 @wfz
//webpack.base.config.js
output: {
- publicPath: '/dit/',
+ publicPath: '/',
},
//server.js
- app.use(serve('/dist', './dist', true))
- app.use(serve('/public', './public', true))
+ app.use(serve(__dirname + '/dist'))
html 출력
"/manifest-56dda86c1b6ac68c0279.js"</span> defer>
"/vendor-3504d51340141c3804a1.js"</span> defer>
"/app-ae1871b21fa142b507e8.js"</span> defer>
실행 항목
npm run install
npm run build:client // clientBundle
npm run build:server // serverBundle
npm run dev // node
open http://localhost:8089/
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.