使用node+vue.js实现SPA应用,解决了SPA应用的最大缺点SEO

oytw5533 8年前

来自: http://segmentfault.com/a/1190000004372736

业务需求

最近公司要求开发web版的app,由于app是偏向内容方面,而且带了一个聊天模块,所以一般的多页开发不是很适合,而且主要是手机浏览,对加载速度或者用户体验来说都比较苛刻。调研了很多框架和模式,最后自己东拼西凑搞出来了这么一个玩意。

服务端

毫无疑问使用node,使用typescript可以有效的在编码同时查错,强类型语言写服务端毫无压力。

#app.ts 只贴重要代码    var webpack = require('webpack')  var webpackDevMiddleware = require('webpack-dev-middleware')  var WebpackConfig = require('./webpack.config')    import * as index from "./routes/index";  import * as foo from "./routes/foo";  import * as bar from "./routes/bar";    var app = express();    //启动服务的时候 打包并监听客户端用到的文件,webpackDevMiddleware是开发模式,他会打包js在内存里面,你改了文件,它也会重新打包  app.use(webpackDevMiddleware(webpack(WebpackConfig), {      publicPath: '/__build__/',      stats: {          colors: true      }  }));    //一般的配置项  app.set('views', __dirname + '/views');  app.set('view engine', 'ejs');  app.set('view options', { layout: false });  app.use(bodyParser.urlencoded({ extended: true }));  app.use(bodyParser.json());  app.use(methodOverride());  app.use(express.static(__dirname + '/public'));    var env = process.env.NODE_ENV || 'development';  if (env === 'development') {      app.use(errorHandler());  }    //路由配置  app.get('/', index.index);  app.get('/foo', foo.index);  app.get('/bar', bar.index);      app.listen(3000, function(){      console.log("Demo Express server listening on port %d in %s mode", 3000, app.settings.env);  });    export var App = app;

服务端渲染页面

#index.ts  import express = require("express")  import vueServer = require("vue-server") //服务端渲染vue的插件    var Vue = new vueServer.renderer(); //创建一个服务端的vue    export function index(req: express.Request, res: express.Response) {        //创建一个组件      var vm = new Vue({          template: `          <p>This is index!</p>          `      });        //等待html渲染完成,再返回给浏览器 vueServer.htmlReady是vue-server的自带事件      vm.$on('vueServer.htmlReady', function(html:string) {          //这里用的是ejs模板 可以把需要用到的数据设置成window下的全局变量,方便客户端的js访问。          res.render('layout',{server_html:html,server_data:'window.cm_data = {name:"张三"}'})      });    };
#layout.ejs  访问这个SPA的所有url返回的都是这个页面 <meta>标签都可以动态设置,只要传参数进来就可以  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="utf-8">      <title>Vue Router Example</title>      <style>          .v-link-active {              color: red;          }      </style>      <script>          //定义一些前端需要用到的全局属性,文章ID或用户信息什么的          //index.ts中传过来的是 window.cm_data = {name:"张三"}          //前端就能访问到了          <%-server_data%>      </script>  </head>  <body>    //这里的id是前端需要用到的一个标识  <div id="app">      <h1>Hello App!</h1>      <p>          <a v-link="{ path: '/foo' }">Go to Foo</a>          <a v-link="{ path: '/bar' }">Go to Bar</a>      </p>      //router-view是客户端vue-router需要解析的dom      //server_html是根据访问url地址生成的html,是做SEO的重点,不加载下面的app.js也可以看到内容      <router-view> <%-server_html%> </router-view>  </div>  //webpack打包好的js,主要是路由配置  <script src="/__build__/app.js"></script>  </body>  </html>

客户端

#app.js 这个是/__build__/app.js,可以用es6编写,webpack会转换的    import Vue from './vue.min' //客户端的vue.js  import VueRouter from './vue-router.min' //vue的路由插件,配合webpack可以很简单实现懒加载    //懒加载路由 只有访问这个路由才会加载js  import Foo from 'bundle?lazy!../../components/foo' //配合webpack的bundle-loader,轻松实现懒加载  import Bar from 'bundle?lazy!../../components/bar'  import Index from 'bundle?lazy!../../components/index'    var App = Vue.extend({})    Vue.use(VueRouter)    var router = new VueRouter({      //这里要好好说一下,一定要设置html5模式,不然前后端URL不统一会发生问题      //比如访问 http://localhost:3000/ 服务端定义是访问index.ts这个路由文件      //如果不是html5模式的话,经过客户端js运行之后会变成http://localhost:3000/#!/            //在比如直接浏览器输入 http://localhost:3000/foo 服务端定义是访问.ts这个路由文件      //如果不是html5模式的话,经过客户端js运行之后会变成 http://localhost:3000/foo/#!/            //设置了html5模式后,加载完js后不会加上#!这2个类似锚点的字符,实现前后端路由统一如果用户刷新浏览器的话,服务端也能渲染出相应的页面。      history: true, //html5模式 去掉锚点       saveScrollPosition: true //记住页面的滚动位置 html5模式适用  })    //定义路由,要和服务端路由路径定义的一样  router.map({      '/'   : {          component: Index //前端路由定义,      },      '/foo': {          component: Foo      },      '/bar': {          component: Bar      }  })    //启动APP  router.start(App, '#app')

需要完善的地方

  1. 前后端统一模板,已经找到方法了把html分离出来,node端用fs.readFileSync方法获取,客户端用webpack的raw-loader获取html内容

不放源码都是瞎扯。

源码地址

https://github.com/yjj5855/node-vue-server-webpack