vue2.0-快速构建高性能的SPA(结合webpack, router, 组件)

前言

vue真的很强大, 用过它的人都说好
SPA(single page app, 即单页面应用)

该文介绍了spa项目的创建, 设置路由, 添加组件, 使用markdown编辑器, 使用webpack实现按需加载, 使用sass等几部分的内容, 内容较多, 慎入

正文

项目演示地址: 戳这里
项目代码地址: 戳这里

项目准备

  • 初始化工程
npm install vue-cli -g  #install vue-cli
vue init webpack-sample my-vue-webpack-simple  # 创建基于模板webpack-sample的vue项目
  • 安装依赖
# install dependencies
npm install
  • 开启服务
# 开启本地服务器, 自带热加载, 默认地址localhost:8080
npm run dev

# 构建生产环境代码
npm run build

使用vue-router

  • 安装
npm install vue-router --save
  • 配置router
    ./src/main.js文件:
import Vue from 'vue'
import VueRouter from 'vue-router'

//components lists
import App from './App.vue';
import Home from './components/home.vue';
const Foo = { template: '<div>foo</div>' };
const Bar = { template: '<div>bar</div>' };

//创建子类构造器
const app = Vue.extend(App);

//使用模块化机制编程, 要调用 Vue.use(VueRouter)
Vue.use(VueRouter);

//定义路由
const routes = [
  { path: '/', component: Home },
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
];

//创建 router 实例,然后传 `routes` 配置
const router = new VueRouter({
  routes
});

// 挂载到根实例
new app({
  router
}).$mount('#app');
  • 设置路由模板./src/App.vue文件:
<template>
  <div class="main">
    <transition name="fade" mode="out-in" appear>
      <keep-alive>
        <router-view></router-view>
      </keep-alive>
    </transition>
  </div>
</template>
  • 添加页面过渡css(vue2.0和1.0有些许差别, 详情看文档)
.fade-enter-active, .fade-leave-active {
    transition: opacity 0.3s ease;
}
.fade-enter, .fade-leave-active {
    opacity: 0;
}

添加组件

  • 改写home.vue
<template>
    <div id="home">
     <h1>{{ msg }}</h1>
     <ol>
       <todo-item v-for="todo in todos" v-bind:todo="todo"></todo-item>
     </ol>
     <my_footer></my_footer>
    </div>
</template>
<script>
//定义组件
import my_footer from './footer.vue'
export default {
  name: 'home',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      todos: [
        { text: 'Learn JavaScript' },
        { text: 'Learn Vue' },
        { text: 'Build something awesome' }
      ]
    }
  },
  components:{  //引入组件
    'my_footer': my_footer,
    'todo-item': {
      props: ['todo'],
      template: '<li>{{ todo.text }}</li>'
    }
  }
}
</script>
  • 添加footer.vue
<template>
    <div class="weui-footer">
        <p class="weui-footer__links">
            <a href="javascript:void(0);" class="weui-footer__link">底部链接</a>
            <a href="javascript:void(0);" class="weui-footer__link">底部链接</a>
        </p>
        <p class="weui-footer__text">Copyright © 2008-2016 weui.io</p>
    </div>
</template>

使用简单的markdown编辑器

示例地址

  • 引入官网marked文件
import marked from '../../statics/js/marked@0.3.6.js'
  • 使用marked方法来计算输入的内容
<template>
    <div id="editor">
        <textarea :value="input" v-model="input"></textarea>
        <div class="weui-panel">
            <div class="weui-panel__hd">markdown预览</div>
            <div class="box"  v-html="compiledMarkdown"></div>
        </div>
    </div>
</template>
<script>
    import marked from '../../statics/js/marked@0.3.6.js'
    export default{
        name:'editor',
        data(){
            return {
                input: '# input markdown'
            }
        },
        computed: {
            compiledMarkdown: function () {
                return marked(this.input, {
                    sanitize: true
                })
            }
        }
    }
</script>
<style>
# balabala 省略
</style>
  • 给代码加高亮
    引入 highlight 的 js 和 css , 再在 marked 方法中配置下就OK了, 以下给出增加的代码片段, 插到相应位置上就可以了
<template>
  <div id="editor">
    <link rel="stylesheet" href="../../statics/css/solarized_light.min.css">
    <!-- ... -->
  </div>
</template>
<script>
//...
import hljs from '../../statics/js/highlight.min'
//...
    return marked(this.input, {
        sanitize: true,
        highlight: function(code, lang) {
            return hljs.highlightAuto(code, [lang]).value;
        }
    })
//...
</script>

  • 更多的设置自行google
    源码中默认的配置参数:
marked.defaults = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  sanitizer: null,
  mangle: true,
  smartLists: false,
  silent: false,
  highlight: null,
  langPrefix: 'lang-',
  smartypants: false,
  headerPrefix: '',
  renderer: new Renderer,
  xhtml: false
};

vue webpack 按需加载的实现

我们知道在这之前,使用vue webpack 模板构建出的项目是将所有的js都编译到一个build.js文件中,旨在只加载一次资源,后续会有本地化app的效果,但当业务逻辑较多,组件较多时,这个文件就会很大,从而使首屏加载的时间very very long ~,第一次进入项目就要等上半分乃至几分钟,要是我的话肯定等不鸟→_→;

那么读到这里,有点经验的肯定会想到,能不能优化成类似requirejs的按需加载,首屏不需要的资源先不加载,到使用时再下载下来;官网的东西还是很强大的,虽然目前国内还没什么现成的例子,但通过官方文档,还是能进展一二的。废话不多说了,下面重点来了

//...
//初始化webpack 自带的 chunk 插件
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common');
module.exports = {
  entry: './src/main.js',  //可引入多个入口文件,编译后的文件个数也会相应增多
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: 'dist/',
    filename: '[name].js',  //自动生成文件名
    chunkFilename: "chunts/[name]-[chunkhash:8].js"    //生成的子文件路劲/文件名
  },
  //...
  plugins: [ commonsPlugin ],
  //...
}
//...
  • 修改入口文件src/main.js
    之前的路由写法:
import home from './components/home.vue';
import lists from './components/lists.vue';

const routes = [
  { path: '/', component: Index },
  { path: '/home', component: home },
  { path: '/lists', component: lists }
];

修改之后:点击查看官网-异步组件模块

const routes = [
  {
    path: '/',
    component: function (resolve) {
      require(['./components/index.vue'], resolve)
    }
  },
  {
    path: '/home',
    component: function (resolve) {
      require(['./components/home.vue'], resolve)
    }
  },
  {
    path: '/lists',
    component: function (resolve) {
      require(['./components/lists.vue'], resolve)
    }
  }
];

或者改写成这样:(是不是更加一目了然呢😝)

const routes = [
  { path: '/', component: view('index') },
  { path: '/home', component: view('home') },
  { path: '/lists', component: view('lists') }
];

//rebase url `./components/`
function view(name) {
    return function(resolve) {
        require(['./components/' + name + '.vue'], resolve);
    }
};
  • index.html中替换依赖文件build.js
<script src="./dist/common.js"></script>
<script src="./dist/main.js"></script>
  • 执行npm run build,编译后的文件结构如下
├── dist/                      # 编译后的目标文件夹
│   ├── common.js              # js分发文件
│   ├── main.js                # 压缩处理后的入口文件
│   └── chunts                 # 按需加载的子文件夹
│        ├── 0-xxxxxx.js
│        ├── 1-xxxxxx.js
│        ├── 2-xxxxxx.js
│        └── ...
├── ...

如图:
<p style="text-align: center;">
<img src="http://statics.oulafen.com/blog_vue-webpack-chunts.jpg" style="margin: auto; width: 50%;" />
</p>

切换路由效果

点击查看官网-过渡效果的介绍

  • 应用场景:同级路由间切换用fade动效,不同级路由间切换时,用slide-leftslide-right
  • 修改src/App.vue文件
<template>
  <div class="main">
    <transition :name="routerTransition" mode="out-in" appear>
      <keep-alive>
        <router-view></router-view>
      </keep-alive>
    </transition>
  </div>
</template>
<script>
  export default{
    data(){
      return {
        routerTransition: 'fade'
      }
    },
    watch: {
      '$route' (to, from) {
        const toDepth = to.path.split('/').length
        const fromDepth = from.path.split('/').length

        if(toDepth != fromDepth){
          this.routerTransition = toDepth < fromDepth ? 'slide-right' : 'slide-left'
        }else{
          this.routerTransition = 'fade'
        }
      }
    }
  }
</script>
  • 添加动效css
.fade-enter-active, .fade-leave-active {
    transition: opacity 0.3s ease;
}
.fade-enter, .fade-leave-active {
    opacity: 0;
}
.slide-left-enter, .slide-right-leave-active {
  opacity: 0;
  -webkit-transform: translate(30px, 0);
  transform: translate(30px, 0);
}
.slide-left-leave-active, .slide-right-enter {
  opacity: 0;
  -webkit-transform: translate(-30px, 0);
  transform: translate(-30px, 0);
}
.slide-left-enter-active, .slide-left-leave-active, .slide-right-enter-active, .slide-right-leave-active{
  transition: all .3s cubic-bezier(.55,0,.1,1);
}

效果图如下:
<p style="text-align: center;">
<img src="http://statics.oulafen.com/blog_vue_spa_demo_index_2.gif" style="margin: auto;" />
</p>

使用scss

  • 安装依赖
npm install sass-loader node-sass --save-dev
  • 配置webpack config
module.exports = {
  ...
  module: {
    rules: [
      ...,
      {
        test: /\.scss$/,
        loaders: ["style-loader", "css-loader", "sass-loader"]
      }
    ]
  }
};
  • 在模块中引入scss文件
require("../statics/css/style.scss");

参考链接:https://github.com/jtangelder/sass-loader

问题总结

  • build项目之后, 静态资源的如图片加载失败
    原因肯定是路径不对, 所以才找不到, 建议将webpack.config.js中的publicPath改为路径dist/, 若不行,再查看其它有关路径设置的地方
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容