一个基于vue二、koa2和mongodb的博客

博客终于差很少写完了,虽然仍是可能有一堆bug, 不过我火烧眉毛要写一篇博文来分享了= =
javascript

博客前台展现

博客前台展现

博客后台展现

博客后台展现

博客后台编辑

项目地址

你们一块儿来star、fork呀,欢迎提出各类改进意见,我知道确定还有一堆问题😵
域名尚未备案,后面就变成imhjm.com😆,敬请期待css

项目起源

其实我一直在使用hexo搭建的博客,也一直用得还挺顺手,可是它只是个静态站点,当我须要一些定制化的需求,我没办法作更多的改变,而且搭在github page上有时不稳定,并且以为搭建一个网站而后本身来作各类优化是一件cool的事情,能够尝试各类新技术,能够接触到各个层面上的优化,因此写一个博客的念头就开始了。
其实vue-blog这个项目开了很久了,可是由于断断续续开发,过一段时间就以为以前的代码写得很差就开始重写,并且也是边写来边学习新技术,vue2全家桶,koa2, webpack2,这些都是以前我没怎么了解的技术,可是至少经过这个博客项目,我对它们也有了新的认识
这里也要感谢一下@Ma63d(@chuckliu),博客搭建初期学习了不少他的kov-blog代码,学习到不少东西,也让本身的后期实现得比较顺利html

项目技术细节

就是这个图啦~
vue

博客总体架构

可是固然还有更多细节

client端admin部分

这是博客后台,其实个人想法就是实现一个文本编辑器,有个左边栏,而后右边栏编辑,为了不复杂,我只用了两个页,一个登陆页,而后一个就是带有编辑器的列表页java

鉴权

统一使用axios来通讯,鉴权使用jwt,login页中登陆成功存入token,而后进入编辑页,经过vue-router的beforeEach钩子加入node

Axios.defaults.headers.common['Authorization'] = 'Bearer ' + store.state.auth.token;复制代码

而后服务端接收时验证token来决定是否鉴权成功,失败时Axios统一拦截,删除store里存取的token, 经过vue-router再回到登陆页webpack

状态管理

列表和编辑器分红了两个组件,用vuex统一管理状态,经过action取/存而后mutaion修改状态便可,可是这部分逻辑较多,具体实现就不展开描述了ios

// 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.jshighlight.jscss3

client端front部分

event bus

由于考虑到前台须要更快的加载速度,并且逻辑也比较简单,就放弃使用vuex,采用vue event bus来实现非父子组件间的通讯nginx

// 在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的洋葱结构

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…
欢迎继续观光个人博客~

欢迎关注

相关文章
相关标签/搜索