使用node+vue.js實(shí)現(xiàn)SPA應(yīng)用
業(yè)務(wù)需求
最近公司要求開發(fā)web版的app,由于app是偏向內(nèi)容方面,而且?guī)Я艘粋€(gè)聊天模塊,所以一般的多頁(yè)開發(fā)不是很適合,而且主要是手機(jī)瀏覽,對(duì)加載速度或者用戶體驗(yàn)來(lái)說(shuō)都比較苛刻。調(diào)研了很多框架和模式,最后自己東拼西湊搞出來(lái)了這么一個(gè)玩意。
服務(wù)端
毫無(wú)疑問(wèn)使用node,使用typescript可以有效的在編碼同時(shí)查錯(cuò),強(qiáng)類型語(yǔ)言寫服務(wù)端毫無(wú)壓力。
#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();
//啟動(dòng)服務(wù)的時(shí)候 打包并監(jiān)聽客戶端用到的文件,webpackDevMiddleware是開發(fā)模式,他會(huì)打包js在內(nèi)存里面,你改了文件,它也會(huì)重新打包
app.use(webpackDevMiddleware(webpack(WebpackConfig), {
publicPath: '/__build__/',
stats: {
colors: true
}
}));
//一般的配置項(xiàng)
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;
服務(wù)端渲染頁(yè)面
#index.ts
import express = require("express")
import vueServer = require("vue-server") //服務(wù)端渲染vue的插件
var Vue = new vueServer.renderer(); //創(chuàng)建一個(gè)服務(wù)端的vue
export function index(req: express.Request, res: express.Response) {
//創(chuàng)建一個(gè)組件
var vm = new Vue({
template: `
<p>This is index!</p>
`
});
//等待html渲染完成,再返回給瀏覽器 vueServer.htmlReady是vue-server的自帶事件
vm.$on('vueServer.htmlReady', function(html:string) {
//這里用的是ejs模板 可以把需要用到的數(shù)據(jù)設(shè)置成window下的全局變量,方便客戶端的js訪問(wèn)。
res.render('layout',{server_html:html,server_data:'window.cm_data = {name:"張三"}'})
});
};
#layout.ejs 訪問(wèn)這個(gè)SPA的所有url返回的都是這個(gè)頁(yè)面 <meta>標(biāo)簽都可以動(dòng)態(tài)設(shè)置,只要傳參數(shù)進(jìn)來(lái)就可以
<!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中傳過(guò)來(lái)的是 window.cm_data = {name:"張三"}
//前端就能訪問(wèn)到了
<%-server_data%>
</script>
</head>
<body>
//這里的id是前端需要用到的一個(gè)標(biāo)識(shí)
<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是根據(jù)訪問(wèn)url地址生成的html,是做SEO的重點(diǎn),不加載下面的app.js也可以看到內(nèi)容
<router-view> <%-server_html%> </router-view>
</div>
//webpack打包好的js,主要是路由配置
<script src="/__build__/app.js"></script>
</body>
</html>
客戶端
#app.js 這個(gè)是/__build__/app.js,可以用es6編寫,webpack會(huì)轉(zhuǎn)換的
import Vue from './vue.min' //客戶端的vue.js
import VueRouter from './vue-router.min' //vue的路由插件,配合webpack可以很簡(jiǎn)單實(shí)現(xiàn)懶加載
//懶加載路由 只有訪問(wèn)這個(gè)路由才會(huì)加載js
import Foo from 'bundle?lazy!../../components/foo' //配合webpack的bundle-loader,輕松實(shí)現(xiàn)懶加載
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({
//這里要好好說(shuō)一下,一定要設(shè)置html5模式,不然前后端URL不統(tǒng)一會(huì)發(fā)生問(wèn)題
//比如訪問(wèn) http://localhost:3000/ 服務(wù)端定義是訪問(wèn)index.ts這個(gè)路由文件
//如果不是html5模式的話,經(jīng)過(guò)客戶端js運(yùn)行之后會(huì)變成http://localhost:3000/#!/
//在比如直接瀏覽器輸入 http://localhost:3000/foo 服務(wù)端定義是訪問(wèn).ts這個(gè)路由文件
//如果不是html5模式的話,經(jīng)過(guò)客戶端js運(yùn)行之后會(huì)變成 http://localhost:3000/foo/#!/
//設(shè)置了html5模式后,加載完js后不會(huì)加上#!這2個(gè)類似錨點(diǎn)的字符,實(shí)現(xiàn)前后端路由統(tǒng)一如果用戶刷新瀏覽器的話,服務(wù)端也能渲染出相應(yīng)的頁(yè)面。
history: true, //html5模式 去掉錨點(diǎn)
saveScrollPosition: true //記住頁(yè)面的滾動(dòng)位置 html5模式適用
})
//定義路由,要和服務(wù)端路由路徑定義的一樣
router.map({
'/' : {
component: Index //前端路由定義,
},
'/foo': {
component: Foo
},
'/bar': {
component: Bar
}
})
//啟動(dòng)APP
router.start(App, '#app')
需要完善的地方
前后端統(tǒng)一模板,已經(jīng)找到方法了把html分離出來(lái),node端用fs.readFileSync方法獲取,客戶端用webpack的raw-loader獲取html內(nèi)容
不放源碼都是瞎扯。
源碼地址
相關(guān)文章
node.js中的console.time方法使用說(shuō)明
這篇文章主要介紹了node.js中的console.time方法使用說(shuō)明,本文介紹了console.time的方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
Node.js配合node-http-proxy解決本地開發(fā)ajax跨域問(wèn)題
這篇文章主要介紹了Node.js配合node-http-proxy解決本地開發(fā)ajax跨域問(wèn)題,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08
使用node-media-server搭建一個(gè)簡(jiǎn)易的流媒體服務(wù)器
這篇文章主要介紹了使用node-media-server搭建一個(gè)簡(jiǎn)易的流媒體服務(wù)器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
export?default?和?export?的使用方式示例詳解
這篇文章主要介紹了export?default?和?export?的使用方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
解決使用node命令提示:'node'不是內(nèi)部或外部命令,也不是可運(yùn)行的程序
最近在工作中遇到了個(gè)常見的問(wèn)題,分享給大家,這篇文章主要給大家介紹了關(guān)于如何解決使用node命令提示:'node'不是內(nèi)部或外部命令,也不是可運(yùn)行的程序的相關(guān)資料,需要的朋友可以參考下2023-02-02

