部落格終於差不多寫完了,雖然還是可能有一堆bug, 不過我迫不及待要寫一篇博文來分享了= =
部落格前臺展示
部落格後臺展示
專案地址
- github地址:github.com/BUPT-HJM/vu…
- 線上訪問地址: http://123.206.67.156:9000/ (域名還在備案中...)
大家一起來star、fork呀,歡迎提出各種改進意見,我知道肯定還有一堆問題?
域名還沒有備案,後面就變成imhjm.com?,敬請期待
專案起源
其實我一直在使用hexo搭建的部落格,也一直用得還挺順手,但是它只是個靜態站點,當我需要一些定製化的需求,我沒辦法做更多的改變,並且搭在github page上有時不穩定,而且覺得搭建一個網站然後自己來做各種優化是一件cool的事情,可以嘗試各種新技術,可以接觸到各個層面上的優化,所以寫一個部落格的念頭就開始了。
其實vue-blog這個專案開了好久了,但是因為斷斷續續開發,過一段時間就覺得之前的程式碼寫得不好就開始重寫,而且也是邊寫來邊學習新技術,vue2全家桶,koa2, webpack2,這些都是之前我沒怎麼了解的技術,但是至少通過這個部落格專案,我對它們也有了新的認識
這裡也要感謝一下@Ma63d(@chuckliu),部落格搭建初期學習了很多他的kov-blog程式碼,學習到很多東西,也讓自己的後期實現得比較順利
專案技術細節
就是這個圖啦~
但是當然還有更多細節
client端admin部分
這是部落格後臺,其實我的想法就是實現一個文字編輯器,有個左邊欄,然後右邊欄編輯,為了避免複雜,我只用了兩個頁,一個登入頁,然後一個就是帶有編輯器的列表頁
鑑權
統一使用axios來通訊,鑑權使用jwt,login頁中登入成功存入token,然後進入編輯頁,通過vue-router的beforeEach鉤子加入
Axios.defaults.headers.common['Authorization'] = 'Bearer ' + store.state.auth.token;複製程式碼
然後服務端接收時驗證token來決定是否鑑權成功,失敗時Axios統一攔截,刪除store裡存取的token, 通過vue-router再回到登入頁
狀態管理
列表和編輯器分成了兩個元件,用vuex統一管理狀態,通過action取/存然後mutaion修改狀態即可,但是這部分邏輯較多,具體實現就不展開描述了
// editor部分state
const state = {
articleList: [],
tagList: [],
currentArticle: {
id: -1,
index: -1,
content: '',
title: '',
tags: [],
save: true,
publish: false
},
allPage: 1,
curPage: 1,
selectTagArr: []
};複製程式碼
// auth部分state
const state = {
token: sessionStorage.getItem('token')
};複製程式碼
編輯器/markdown
受@chuckliu安利同樣使用了simplemde-markdown-editor,然後解析和高亮部分使用了marked.js
和highlight.js
client端front部分
event bus
因為考慮到前臺需要更快的載入速度,而且邏輯也比較簡單,就放棄使用vuex,採用vue event bus來實現非父子元件間的通訊
// 在main.js定義全域性event bus,不使用vuex管理
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$eventBus: {
get: function() {
return EventBus;
}
}
});
// 然後在元件內可以這樣使用
this.$eventBus.$on('filterListByTag', this.filterListByTag);
this.$eventBus.$emit('filterListByTag', this.filterListByTag);
this.$eventBus.$off('filterListByTag', this.filterListByTag);複製程式碼
keep-alive
排除article元件,其他則保留元件狀態或避免重新渲染。
<transition name="fade" mode="out-in">
<!-- keep-alive排除article -->
<keep-alive exclude="article">
<router-view>
</router-view>
</keep-alive>
</transition>複製程式碼
css小坑/小技巧
本來的transition是使用到transform的,因為移動端我的側邊欄是fixed,發現在切換的過程中側邊欄抖動,才發現是因為fixed是會跟隨transform的,可以具體參考CSS3 transform對普通元素的N多渲染影響,於是便刪去了切換時的移動,保留opacity
在佈局上學習了下vue官網的兩欄,兩欄超出部分都可以滑動,並且不互相影響,這是怎麼實現的?其實還是挺簡單的,不過用到一個小技巧,設定
// 這樣可以實現一個元素100%width以及100%height
div {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}複製程式碼
其他就是經典兩欄佈局和加個overflow-y:auto了
還有一個經典的佈局問題,就是foot部分如何實現在頁面沒什麼東西的,固定在頁面底部,頁面出現滾動條,然後foot部分跟隨在頁面主體後面
server端
server端直接上koa2了,支援async/await, 非同步部分用起來真的很舒服,現在node7+也支援了,所以小夥伴們趕緊用吧~
Json Web Token
再講講jwt鑑權吧
其實並不複雜
在驗證登入時,後端取出資料庫已有的密碼驗證,成功則用配置好的secret生成一個signed jwt,通俗點說你將它加密了再傳給客戶端
const token = jwt.sign({
uid: user._id,
name: user.name,
exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60 //1 hours
}, config.jwt.secret);複製程式碼
http無狀態,所以客戶端需要存下這個token, 然後之後請求時帶上這個token服務端驗證通過後返回資源即可
try {
tokenContent = await jwt.verify(token, config.jwt.secret);
} catch (err) {
if ('TokenExpiredError' === err.name) {
ctx.throw(401, 'token expired');
}
ctx.throw(401, 'invalid token');
}複製程式碼
如果想更深層次地瞭解jwt是什麼,可以自行去搜尋網上的文章
RESTful API
Representational State Transfer簡單來理解就是每個URL都是資源,通過不同的http method去操作這些資源就行,設計上就可以這樣,然後統一加上prefix:'/api'
router.get('/articles', verify, $.getAllArticles) //獲取所有文章
.post('/articles', verify, $.createArticle) //建立文章
.patch('/articles/:id', verify, $.modifyArticle) //修改特定文章
.get('/articles/:id', $.getArticle) //獲取特定文章
.delete('/articles/:id', verify, $.deleteArticle) //刪除特定文章複製程式碼
webpack相關
本來打算直接使用vue-cli,這部分就非常省心了,vue-cli這個腳手架做得確實很友好,我覺得vue比較好上手開發的一部分原因也是因為它吧,不過因為這個部落格專案就是學習的過程,不想這麼輕易地逃過這部分的學習?,直接上webpack2,然後參考vue-cli和webpack官網來寫,覺得也學習到很多東西,並且因為front和admin是分開的,所以也實現了多頁配置
開發環境
hot-reload是必備的
// ...
entry: {
'modules/admin': [
'webpack-hot-middleware/client?reload=true',
CLIENT_FOLDER + '/src/modules/admin/main'
],
'modules/front': [
'webpack-hot-middleware/client?reload=true',
CLIENT_FOLDER + '/src/modules/front/main'
]
},
// ...
plugins: [
new webpack.HotModuleReplacementPlugin()
// ..複製程式碼
用CleanWebpackPlugin清空下目錄,HtmlWebpackPlugin自動生成html,然後該寫的loader
生產環境
生產環境下改動就大了,先得刪去hot-reload和devtool部分,然後提取css
//...
styl: ExtractTextPlugin.extract({
use: [{
loader: 'css-loader',
options: {
minimize: true,
sourceMap: true
}
}, {
loader: 'stylus-loader',
options: {
sourceMap: true
}
}],
fallback: 'vue-style-loader'
}),
//...複製程式碼
UglifyJsPlugin壓縮程式碼,然後提取公有程式碼vendor、manifest,manifest用來防止vendor的hash在vendor部分沒有變化時不被修改
// ...
// 分別提取vendor、manifest
new webpack.optimize.CommonsChunkPlugin({
name: 'modules/vendor_admin',
chunks: ['modules/admin'],
minChunks: function(module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
join(__dirname, './node_modules')
) === 0
)
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'modules/manifest_admin',
chunks: ['modules/vendor_admin']
}),
//...複製程式碼
然後用CopyWebpackPlugin將static拷進dist就好了
與koa配合
這部分就可以看vue-cli是怎麼實現的了,vue-cli使用的是express通過webpack-dev-middleware和webpack-hot-middleware實現開發模式下熱過載,當然koa也可以
const koaWebpack = require('koa-webpack');
const webpack = require('webpack');
const webpackConfig = require('../webpack.config');
let compiler = webpack(webpackConfig);
app.use(koaWebpack({
compiler: compiler,
dev: {
//noInfo: true,
stats: {
colors: true,
chunks: false
},
publicPath: webpackConfig.output.publicPath,
}
}));複製程式碼
順便講到專案中用的是history模式,這裡也需要後端配合
app.use(convert(historyApiFallback({
verbose: process.env.NODE_ENV == 'production' ? false : true,
index: '/front.html',
rewrites: [
{ from: /^\/admin$/, to: '/admin.html' },
{ from: /^\/admin\/login/, to: '/admin.html' },
{ from: /^\/$/, to: '/front.html' },
{ from: /^\/article/, to: '/front.html' }
]
})))複製程式碼
但是這裡有個坑是要注意書寫use的順序,放在router api後面就行,這裡需要理解下koa的洋蔥結構
webpack優化
我覺得這部分除了區分開發環境和生產環境以外,還要注意到對webpack打包的過程和模組的分析,不要吝嗇webpack打包的輸出
// 顯示顏色,耗時長的都有顏色區分 --colors
// 可以看到每一步的耗時 --profile
// 顯示模組 --display-modules
// 並且按size大小排序 --sort-modules-by size
webpack.config.js --colors --profile --display-modules --sort-modules-by size複製程式碼
這樣就能看出打包什麼耗去你大量的時間,佔據了大量空間,還有是否是重複打包
還有一個直觀而又酷炫的方式github.com/alexkuz/web…
直接看佔比就能看出哪部分需要你去優化了
我通過alias和external優化了下,效果還是挺明顯的,最後本地生產環境打包大概14-20s,也還可以接受,但是估計還有優化的空間,其實我也試過happypack和並行的uglify,不過發現沒什麼效果= =
//...
resolve: {
extensions: ['.js', '.vue', '.json'],
modules: [join(__dirname, './node_modules')],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'vuex$': 'vuex/dist/vuex.esm.js',
'vue-router$': 'vue-router/dist/vue-router.esm.js',
'simplemde$': 'simplemde/dist/simplemde.min.js',
'highlight.js$': 'highlight.js/lib/highlight.js',
'fastclick': 'fastclick/lib/fastclick.js',
'lib': resolve(__dirname, './client/src/lib'),
'api': resolve(__dirname, './client/src/api'),
'publicComponents': resolve(__dirname, './client/src/components'),
}
},
//...
// html template引入simplemde cdn
externals: {
'simplemde': 'SimpleMDE'
},
//...複製程式碼
還有一些快取優化的問題,就是js/css的hash問題,js chunkhash然後提取css使用contenthash,為了避免改動vendor的hash,commonChunk額外提取出manifest(extract the webpack bootstrap logic into a separate file
),這樣當vendor沒有被修改的時候,重新執行webpack不會再生產新的hash,變動的只有manifest,具體可以看webpack.js.org/plugins/com…
關於多頁配置
其實只需要根據模組劃分然後多寫幾行配置就行,要做類似腳手架的話可以使用glob
這些工具
具體看程式碼吧,會生成以下目錄
dist
|---css
|---modules
|---admin.xxx.css
|---front.xxx.css
|---fonts
|---modules
|---admin.xxx.min.js
|---front.xxx.min.js
|---vendor_admin.xxx.min.js
|---vendor_front.xxx.min.js
|---manifest_admin.xxx.min.js
|---manifest_front.xxx.min.js
|---static
|---admin.html
|---front.html複製程式碼
線上部署及優化
pm2
表示之前我在玩耍node服務的時候都是使用screen命令的,pm2確實很贊,監控/日誌管理這些都很完善,日誌方面我還使用了pm2-logrotate
pm2 install pm2-logrotate
pm2 set pm2-logrotate:retain 100 //控制日誌數量
pm2 set pm2-logrotate:size 1M //控制日誌切割大小複製程式碼
nginx
nginx基本是上線必備,應該也不用多說了,開啟gzip效果確實很顯著,本來前臺的vendor_front從227k直接被壓縮到84k,簡直cool!?
最後
謝謝閱讀~
歡迎follow我哈哈github.com/BUPT-HJM
看到這裡,不star不行了?
github.com/BUPT-HJM/vu…
歡迎繼續觀光我的部落格~
歡迎關注