直达服务器加载(服务器加载失败怎么办)「服务器直通卡」

  01服务端渲染简介

  服务端渲染不是一个新的技能;在Web最初的时间,页面就是通过服务端渲染来返回的,用PHP来说,通常是利用Smarty等模板写模板文件,然后PHP服务端框架将数据和模板渲染为页面返回,如许的服务端渲染有个缺点就是一旦要查察新的页面,就必要哀求服务端,革新页面。

  但如今的前端,为了寻求一些体验上的优化,通常整个渲染在欣赏器端利用JS来完成,共同history.pushState等方式来做单页应用(SPA:Single-PageApplication),也收到不错的结果,但是如许还是有一些缺点:第一次加载过慢,用户必要等待较长时间来等待欣赏器端渲染完成;对搜刮引擎爬虫等不友爱。这时间就出现了雷同于React,Vue2.0等前端框架来做服务端渲染。

  利用这些框架来做服务端渲染的分身了上面的几个长处,而且写一份代码就可以跑在服务端和欣赏器端。Vue2.0发布了也有一段时间了,新版本比力大的更新就是支持服务端渲染,近来有空折腾了下Vue的服务端渲染,记录下来。

  02在Vue2.0中利用服务端渲染

  官方文档给了一个简单的例子来做服务端渲染:

  //步调1:创建一个Vue实例varVue=require('vue')varapp=newVue({render:function(h){returnh('p','helloworld')}})//步调2:创建一个渲染器varrenderer=require('vue-server-renderer').createRenderer()//步调3:将Vue实例渲染成HTMLrenderer.renderToString(app,function(error,html){if(error)throwerrorconsole.log(html)//=pserver-rendered="true"helloworld/p})

直达服务器加载(服务器加载失败怎么办) 直达服务器加载(服务器加载失败怎么办)「服务器直通卡」 行业资讯

  如许子,共同通常的Node服务端框架就可以简单来实现服务端渲染了,但是,在真实场景中,我们一样平常采取.vue文件的模块构造方式,如许的话,服务端渲染就必要利用webpack来将Vue组件举行打包为单个文件。

  03共同Webpack渲染.vue文件

  先创建一个服务端的入口文件server.js

  importVuefrom'vue';importAppfrom'./vue/App';exportdefaultfunction(options){constVueApp=Vue.extend(App);constapp=newVueApp(Object.assign({},options));returnnewPromise(resolve={resolve(app);});}

  这里和欣赏器端的入口文件大同小异,只是默认导出了一个函数,这个函数吸取一个服务端渲染时服务端传入的一些设置,返回一个包罗了app实例的Promise;

  简单写一个App.vue的文件

  templateh1{{title}}/h1/templatemodule.exports={props:['title']/

  这里将会读取服务端入口文件传入options的data属性,取到title值,渲染到对应DOM中;

  再看看Webpack的设置,和客户端渲染同样是大同小异:

  constwebpack=require('webpack');constpath=require('path');constprojectRoot=__dirname;constenv=process.env.NODE_ENV||'development';module.exports={target:'node',//告诉Webpack是node代码的打包devtool:null,//既然是node就不消devtool了entry:{app:path.join(projectRoot,'src/server.js')},output:Object.assign({},base.output,{path:path.join(projectRoot,'src'),filename:'bundle.server.js',libraryTarget:'commonjs2'//和客户端差别}),plugins:[newwebpack.DefinePlugin({'process.env.NODE_ENV':JSON.stringify(env),'process.env.VUE_ENV':'"server"'//设置vue的环境变量,告诉vue是服务端渲染,就不会做耗性能的dom-diff操纵了})],resolve:{extensions:['','.js','.vue'],fallback:[path.join(projectRoot,'node_modules')]},resolveLoader:{root:path.join(projectRoot,'node_modules')},module:{loaders:[{test:/.vue$/,loader:'vue'},{test:/.js$/,loader:'babel',include:projectRoot,exclude:/node_modules/}]}};

  此中重要就是三处差别:声明node模块打包;修改打包后模块加载方式为commonjs(commonjs2具体可以看Webpack官方文档);再就是vue的服务端打包优化了,这部分假如不传的话背面vue服务端渲染会慢至几十秒,一度以为服务端代码挂了。

  末了就是服务端载入天生的bundle.server.js文件:

  constfs=require('fs');constpath=require('path');constvueServerRenderer=require('vue-server-renderer');constfilePath=path.join(__dirname,'src/bundle.server.js');//读取bundle文件,并创建渲染器constcode=fs.readFileSync(filePath,'utf8');constbundleRenderer=vueServerRenderer.createBundleRenderer(code);//渲染Vue应用为一个字符串bundleRenderer.renderToString(options,(err,html)={if(err){console.error(err);}content.replace('divid="app"/div',html);});

  这里options可以传入vue组件所必要的data等信息;下面还是以官方实例中的express来做服务端示例下:

  constfs=require('fs');constpath=require('path');constvueServerRenderer=require('vue-server-renderer');constfilePath=path.join(think.ROOT_PATH,'view/bundle.server.js');global.Vue=require('vue')//读取bundle文件,并创建渲染器constcode=fs.readFileSync(filePath,'utf8');constbundleRenderer=vueServerRenderer.createBundleRenderer(code);//创建一个Express服务器varexpress=require('express');varserver=express();//摆设静态文件夹为"assets"文件夹server.use('/assets',express.static(path.resolve(__dirname,'assets');));//处理惩罚全部的Get哀求server.get('*',function(request,response){//设置一些数据,可以是数据库读取等等constoptions={data:{title:'helloworld'}};//渲染Vue应用为一个字符串bundleRenderer.renderToString(options,(err,html)={//假如渲染时发生了错误if(err){//打印错误到控制台console.error(err);//告诉客户端错误returnresponse.status(500).send('ServerError');}//发送布局和HTML文件response.send(layout.replace('divid="app"/div',html));});//监听5000端口server.listen(5000,function(error){if(error)throwerrorconsole.log('Serverisrunningatlocalhost:5000')});

  如许子根本就是Vue服务端渲染的整个流程了,如许子和利用平凡的模板渲染并没有什么其他的上风,但是当渲染完成后再由客户端担当渲染后就可以做到无缝切换了,下面我们来看看和客户端连合渲染;

  04和欣赏器渲染无缝聚集

  为了和客户端渲染连合,我们将webpack设置文件分为三部分,base共用的设置,server设置,client欣赏器端设置,如下:

  webpack.base.js

  constpath=require('path');constprojectRoot=path.resolve(__dirname,'../');module.exports={devtool:'#source-map',entry:{app:path.join(projectRoot,'src/client.js')},output:{path:path.join(projectRoot,'www/static'),filename:'index.js'},resolve:{extensions:['','.js','.vue'],fallback:[path.join(projectRoot,'node_modules')],alias:{'Common':path.join(projectRoot,'src/vue/Common'),'Components':path.join(projectRoot,'src/vue/Components')}},resolveLoader:{root:path.join(projectRoot,'node_modules')},module:{loaders:[{test:/.vue$/,loader:'vue'},{test:/.js$/,loader:'babel',include:projectRoot,exclude:/node_modules/}]}};

  webpack.server.js

  constwebpack=require('webpack');constbase=require('./webpack.base');constpath=require('path');constprojectRoot=path.resolve(__dirname,'../');constenv=process.env.NODE_ENV||'development';module.exports=Object.assign({},base,{target:'node',devtool:null,entry:{app:path.join(projectRoot,'view/server.js')},output:Object.assign({},base.output,{path:path.join(projectRoot,'view'),filename:'bundle.server.js',libraryTarget:'commonjs2'}),plugins:[newwebpack.DefinePlugin({'process.env.NODE_ENV':JSON.stringify(env),'process.env.VUE_ENV':'"server"','isBrowser':false})]});

  服务端的设置,和之前多了一个isBrowser的全局变量,用于在Vue模块中做一些差别处理惩罚;

  webpack.client.js

直达服务器加载(服务器加载失败怎么办) 直达服务器加载(服务器加载失败怎么办)「服务器直通卡」 行业资讯

  constwebpack=require('webpack');constExtractTextPlugin=require('extract-text-webpack-plugin');constbase=require('./webpack.base');constenv=process.env.NODE_ENV||'development';constconfig=Object.assign({},base,{plugins:[newwebpack.DefinePlugin({'process.env.NODE_ENV':JSON.stringify(env),'isBrowser':true})]});config.vue={loaders:{css:ExtractTextPlugin.extract({loader:'css-loader',fallbackLoader:'vue-style-loader'}),sass:ExtractTextPlugin.extract('vue-style-loader','css!sass?indentedSyntax'),scss:ExtractTextPlugin.extract('vue-style-loader','css!sass')}};config.plugins.push(newExtractTextPlugin('style.css'));if(env==='production'){config.plugins.push(newwebpack.LoaderOptionsPlugin({minimize:true}),newwebpack.optimize.UglifyJsPlugin({compress:{warnings:false}}));}module.exports=config;

  服务端打包时间会忽略css等样式文件,欣赏器端打包时间就将样式文件单独打包到一个css文件。如许在实行webpack时间就必要指定--config参数来编译差别的版本了,如下:

  #编译客户端webpack--configwebpack.client.js#编译服务器端webpack--configwebpack.server.js

  同样,入口文件也提出三个文件,index.js,server.js,client.js

  index.js

  importVuefrom'vue';importAppfrom'./vue/App';importClipButtonfrom'Components/ClipButton';importToastfrom'Components/Toast';Vue.filter('byte-format',value={constunit=['Byte','KB','MB','GB','TB'];letindex=0;letsize=parseInt(value,10);while(size=1024indexunit.length){size/=1024;index++;}return[size.toString().substr(0,5),unit[index]].join('');});Vue.use(Toast);Vue.component('maple-clip-button',ClipButton);constcreateApp=functioncreateApp(options){constVueApp=Vue.extend(App);returnnewVueApp(Object.assign({},options));};export{Vue,createApp};

  index.js中做一些通用的组件、插件加载,一些全局的设置,末了返回一个可以天生app实例的函数供差别环境来调用;

  server.js

  import{createApp}from'./index';exportdefaultfunction(options){constapp=createApp(options);returnnewPromise(resolve={resolve(app);});}

  大部分逻辑已经抽为共用了,以是服务端这里就是简单将app实例通过promise返回;

  client.js

  importVueResourcefrom'vue-resource';import{createApp,Vue}from'./index';Vue.use(VueResource);consttitle='Test';constapp=createApp({data:{title},el:'#app'});exportdefaultapp;

  客户端也雷同,这里在客户端加载VueResource这个插件,用于客户端的ajax哀求;通常通过ajax哀求服务端返回数据再初始化app,如许根本就是一个单页的服务端渲染框架了,一样平常环境下,我们做单页应用还会共同history.pushState等通过URL做路由分发;如许,我们服务端也最好利用同一套路由来渲染差别的页面。

  05服务端和欣赏器路由共用

  路由这里利用vue-router就可以了,欣赏器端还是通过正常的方式载入路由设置,服务端同样载入路由设置,并在渲染之前利用router.push渲染必要显现的页面,以是,在通用的入口文件参加路由设置:

  importVuefrom'vue';importrouterfrom'./router';importAppfrom'./vue/App';constcreateApp=functioncreateApp(options){constVueApp=Vue.extend(App);returnnewVueApp(Object.assign({router},options));};export{Vue,router,createApp};

  路由文件是如许子的:

  importVuefrom'vue';importVueRouterfrom'vue-router';importViewUploadfrom'../vue/ViewUpload';importViewHistoryfrom'../vue/ViewHistory';importViewLibsfrom'../vue/ViewLibs';Vue.use(VueRouter);constroutes=[{path:'/',component:ViewUpload},{path:'/history',component:ViewHistory},{path:'/libs',component:ViewLibs}];constrouter=newVueRouter({mode:'history',routes,base:__dirname});exportdefaultrouter;

  这里路由的利用HTML5的history模式;

  服务端入口文件如许设置:

  import{createApp,router}from'./index';exportdefaultfunction(options){constapp=createApp({data:options.data});router.push(options.url);returnnewPromise(resolve={resolve(app);});}

  这里在初始化app实例后,调用router.push(options.url)将服务端取到的urlpush到路由之中;

  06利用中碰到的坑

  整个过程还算顺遂,此中碰到最多的题目就是有些模块只能在服务端大概欣赏器来利用,而利用ES6模块加载是静态的,以是必要将静态加载的模块改为动态加载,以是就有了上面设置isBrowser这个全局属性,通过这个属性来判定模块加载了,比如我项目中用到的clipboard模块,之前是直接利用ES6加载的;

  templatea@click.prevent:href="text"slot/slot/a/templateimportClipboardfrom'clipboard';exportdefault{props:['text'],mounted(){returnthis.$nextTick(()={this.clipboard=newClipboard(this.$el,{text:()={returnthis.text;}});this.clipboard.on('success',()={this.$emit('copied',this.text);});});}};/

  如许就会在服务端渲染时间报错,将加载改为动态加载就可以了:

  letClipboard=null;if(isBrowser){Clipboard=require('clipboard');}

  假如这个模块在服务端并不会渲染,那背面的代码并不必要更改;

  尚有VueResource插件也是必要欣赏器环境的,以是必要将它单独设置在client.js中;

  07总结

  同样的路由通过Vue服务端渲染后的HTML总是一样的,这和React渲染后会加上哈希差别,以是可以做渲染后结果的缓存优化,这部分可以参考官方文档的做法,总的来说,Vue服务端渲染相沿了Vue客户端的轻量做法,也显得比力轻量,唯一不敷之处大概就是服务端也同样必要webpack来完成。

????????

  原文:

  https://blog.alphatr.com/how-to-use-ssr-in-vue-2.0.html

  点击“阅读原文”,看更多

  精选文章

↓↓↓

客户评论

我要评论