vue服务端渲染的实例代码
本篇文章主要介绍了vue服务端渲染的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
客户端请求服务器,服务器根据请求地址获得匹配的组件,在调用匹配到的组件返回Promise (官方是asyncData方法)来将需要的数据拿到。最后再通过window.__initial_state=data将其写入网页,最后将服务端渲染好的网页返回回去。接下来客户端将用新的store状态把原来的store状态替换掉,保证客户端和服务端的数据同步。遇到没被服务端渲染的组件,再去发异步请求拿数据。 服务端渲染的环境搭建 这是vue官网的服务端渲染的示意图,ssr有两个入口文件,分别是客户端的入后文件和服务端的入口文件,webpack通过两个入口文件分别打包成给服务端用的server bundle和给客户端用的client bundle.当服务器接收到了来自客户端的请求之后,会创建一个渲染器bundleRenderer,这个bundleRenderer会读取上面生成的server bundle文件,并且执行它的代码, 然后发送一个生成好的html到浏览器,等到客户端加载了client bundle之后,会和服务端生成的DOM进行Hydration(判断这个DOM和自己即将生成的DOM是否相同,如果相同就将客户端的vue实例挂载到这个DOM上) 实现步骤: 1、创建vue实例(main.js) importVuefrom'vue' importAppfrom'./App.vue' importiViewfrom'iview'; import{createStore}from'./store' import{createRouter}from'./router' import{sync}from'vuex-router-sync' Vue.use(iView); export functioncreateApp() { conststore = createStore() constrouter = createRouter() sync(store,router) constapp =newVue({ router, store, render: h = h(App) return{app,router,store} 因为要做服务端渲染,所以这里不需要再用el去挂载,现将app、router、store导出 2、服务端入口文件(entry-server.js) import{ createApp }from'./main' constisDev = process.env.NODE_ENV !=='production' const{ app,router,store } = createApp() ponent){ letstores = [] ponent) { ponent.asyncData !=='undefined') { ponent.asyncData({store,route: router.currentRoute})) { stores.push(a) ponents !=='undefined') { ponents){ ponents[c]) ponent) returnstores export defaultcontext = { return newPromise((resolve,reject) = { consts = isDev Date.now() const{url} = context constfullPath = router.resolve(url).route.fullPath if(fullPath !== url) { reject({url: fullPath }) router.push(url) router.onReady(() = { constmatchedComponents = router.getMatchedComponents() if(!matchedComponents.length) { reject({code:404}) letallAsyncData = getAllAsyncData(matchedComponents[0]) Promise.all(allAsyncData).then(() = { isDev console.log(`data pre-fetch:${Date.now() - s}ms`) context.state = store.state resolve(app) }).catch(reject) },reject) 这个文件的主要工作是接受从服务端传递过来的context参数,context包含当前页面的url,用getMatchedComponents方法获取当前url下的组件,返回一个数组,遍历这个数组中的组件,如果组件有asyncData钩子函数,则传递store获取数据,最后返回一个promise对象。 store.state的作用是将服务端获取到的数据挂载到context对象上,后面在server.js文件里会把这些数据直接发送到浏览器端与客户端的vue实例进行数据(状态)同步。 3、客户端入口文件(entry-client.js) importVuefrom'vue' import'es6-promise/auto' import{ createApp }from'./main' importProgressBarfrom'./components/ProgressBar.vue' // global progress bar constbar = Vue.prototype.$bar =newVue(ProgressBar).$mount() document.body.appendChild(bar.$el) Vue.mixin({ beforeRouteUpdate(to,from,next) { const{ asyncData } =this.$options if(asyncData) { Promise.all(asyncData({ store:this.$store, route: to })).then(next).catch(next) }else{ next() const{ app,router,store } = createApp() if(window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__) router.onReady(() = { router.beforeResolve((to,from,next) = { constmatched = router.getMatchedComponents(to) constprevMatched = router.getMatchedComponents(from) letdiffed =false constactivated = matched.filter((c,i) = { returndiffed || (diffed = (prevMatched[i] !== c)) constasyncDataHooks = activated.map(c = c.asyncData).filter(_ = _) if(!asyncDataHooks.length) { returnnext() bar.start() Promise.all(asyncDataHooks.map(hook = hook({ store,route: to }))) .then(() = { bar.finish() next() .catch(next) app.$mount('#app') if('htt凡科抠图:'=== location.protocol navigator.serviceWorker) { navigator.serviceWorker.register('/service-worker.js') 这句的作用是如果服务端的vuex数据发生改变,就将客户端的数据替换掉,保证客户端和服务端的数据同步 Service Worker主要用于拦截并修改访问和资源请求,细粒度地缓存资源。它运行浏览器在后台,运行环境与普通页面脚本不同,所以不能直接参与页面交互。出于安全考虑,service worker只能运行在HTT凡科抠图上,防止被人从中攻击。 4、创建服务端渲染器(server.js) constfs = require('fs') constpath = require('path') constLRU = require('lru-cache') constexpress = require('express') pression = require('compression') constresolve= file = path.resolve(__dirname,file) const{ createBundleRenderer } = require('vue-server-renderer') constisProd = process.env.NODE_ENV ==='production'|| process.env.NODE_ENV ==='beta' constuseMicroCache = process.env.MICRO_CACHE !=='false' constserverInfo = `express/${require('express/package.json').version}`+ `vue-server-renderer/${require('vue-server-renderer/package.json').version}` constapp = express() '),'utf-8') functioncreateRenderer(bundle,options) { returncreateBundleRenderer(bundle,Object.assign(options,{ template, cache: LRU({ max:1000, maxAge:1000*60*15 basedir: resolve('./dist'), runInNewContext:false letrenderer letreadyPromise if(isProd) { constbundle = require('./dist/vue-ssr-server-bundle.json') constclientManifest = require('./dist/vue-ssr-client-manifest.json') renderer = createRenderer(bundle,{ clientManifest }else{ readyPromise = require('./build/setup-dev-server')(app,(bundle,options) = { renderer = createRenderer(bundle,options) constserve= (path,cache) = express.static(resolve(path),{ maxAge: cache isProd 1000*60*60*24*30:0 pression({threshold:0})) app.use('/dist',serve('./dist',true)) app.use('/static',serve('./static',true)) app.use('/service-worker.js',serve('./dist/service-worker.js')) constmicroCache = LRU({ max:100, maxAge:1000 constisCacheable= req = useMicroCache functionrender(req,res) { consts = Date.now() res.setHeader("Content-Type","text/html") res.setHeader("Server",serverInfo) consthandleError= err = { if(err.url) { res.redirect(err.url) }else if(err.code ===404) { res.status(404).end('404 | Page Not Found') }else{ // Render Error Page or Redirect res.status(500).end('500 | Internal Server Error') console.error(`error during render :${req.url}`) console.error(err.stack) constcacheable = isCacheable(req) if(cacheable) { consthit = microCache.get(req.url) if(hit) { if(!isProd) { console.log(`cache hit!`) returnres.end(hit) constcontext = { title:'Vue DB',// default title url: req.url renderer.renderToString(context,(err,html) = { if(err) { returnhandleError(err) res.end(html) if(cacheable) { microCache.set(req.url,html) if(!isProd) { console.log(`whole request:${Date.now() - s}ms`) app.get('*',isProd render : (req,res) = { readyPromise.then(() = render(req,res)) constport = process.env.PORT ||8888 app.listen(port,() = { console.log(`server started at localhost:${port}`) 5、客户端api文件create-api-client.js * Created by lin on 2017/8/25. import axios from 'axios'; let api; axios.defaults.baseURL = process.env.API_URL; axios.defaults.timeout = 10000; axios.interceptors.response.use((res) = { if (res.status = 200 res.status 300) { return res; return Promise.reject(res); }, (error) = { return Promise.reject({message: '网络异常,请刷新重试', err: error}); if (process.__API__) { api = process.__API__; } else { api = { get: function(url) { return new Promise((resolve, reject) = { axios.get(url).then(res = { resolve(res); }).catch((error) = { reject(error); post: function(target, options = {}) { return new Promise((resolve, reject) = { axios.post(target, options).then(res = { resolve(res); }).catch((error) = { reject(error); export default api; 6、服务端api文件create-api-server.js * Created by lin on 2017/8/25. import axios from 'axios'; let cook = process.__COOKIE__ || ''; let api; axios.defaults.baseURL = 'v2/'; axios.defaults.timeout = 10000; axios.interceptors.response.use((res) = { if (res.status = 200 res.status 300) { return Promise.resolve(res); return Promise.reject(res); }, (error) = { // 网络异常 return Promise.reject({message: '网络异常,请刷新重试', err: error, type: 1}); if (process.__API__) { api = process.__API__; } else { api = { get: function(target) { return new Promise((resolve, reject) = { axios.request({ url: encodeURI(target), method: 'get', headers: { 'Cookie': cook }).then(res = { resolve(res); }).catch((error) = { reject(error); post: function(target, options = {}) { return new Promise((resolve, reject) = { axios.request({ url: target, method: 'post', headers: { 'Cookie': cook params: options }).then(res = { resolve(res); }).catch((error) = { reject(error); export default api; 六、那些年遇到的那些坑 问题1、window is not defined 答案1:给用到浏览器对象的地方加if (typeof window !== 'undefined') {},有一些插件里也用到了浏览器对象,在使用的地方也加一个条件判断: if (typeofwindow !== 'undefined') { Vue.use(VueAnalytics, { id: process.env.UA_TRACKING_ID, router } 问题2:用到非Vue系列的插件,如hello.all.js(三方登录的插件),需要用的地方才引用,报的错和问题1一样。 答案2:这个时候不能再用import导入,需要使用require, let hello if (typeof window !== 'undefined') { hello = require('hello') } 问题3:引用bootstrap 答案3:将bootstrap.css和bootstrap.js加入webpack.base.config.js的entry中的vendor中 问题6:bootstap需要jquery,此时把jQuery加在vendor中没用。 答案6:给webpack.base.config.js的plugins添加一个插件,如: newwebpack.ProvidePlugin({ $ : "jquery", jQuery : "jquery", "window.jQuery" :"jquery" }) 七、例子 这是一个服务端渲的例子 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持凡科。 (责任编辑:admin) |