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

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

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

正文

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

项目准备

  • 初始化工程

    1
    2
    npm install vue-cli -g  #install vue-cli
    vue init webpack-sample my-vue-webpack-simple # 创建基于模板webpack-sample的vue项目
  • 安装依赖

    1
    2
    # install dependencies
    npm install
  • 开启服务

    1
    2
    3
    4
    5
    # 开启本地服务器, 自带热加载, 默认地址localhost:8080
    npm run dev

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

使用vue-router

  • 安装

    1
    npm install vue-router --save
  • 配置router
    ./src/main.js文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <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有些许差别, 详情看文档)

    1
    2
    3
    4
    5
    6
    .fade-enter-active, .fade-leave-active {
    transition: opacity 0.3s ease;
    }
    .fade-enter, .fade-leave-active {
    opacity: 0;
    }

添加组件

  • 改写home.vue
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <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文件

    1
    import marked from '../../statics/js/marked@0.3.6.js'
  • 使用marked方法来计算输入的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <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了, 以下给出增加的代码片段, 插到相应位置上就可以了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <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
    源码中默认的配置参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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.config.js文件,使用webpack的chunk 查看CommonChunks插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //...
    //初始化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
    之前的路由写法:

    1
    2
    3
    4
    5
    6
    7
    8
    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 }
    ];

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)
}
}
];

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

1
2
3
4
5
6
7
8
9
10
11
12
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
1
2
<script src="./dist/common.js"></script>
<script src="./dist/main.js"></script>
  • 执行npm run build,编译后的文件结构如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ├── dist/                      # 编译后的目标文件夹
    │ ├── common.js # js分发文件
    │ ├── main.js # 压缩处理后的入口文件
    │ └── chunts # 按需加载的子文件夹
    │ ├── 0-xxxxxx.js
    ├── 1-xxxxxx.js
    ├── 2-xxxxxx.js
    └── ...
    ├── ...

如图:




## 切换路由效果
点击查看官网-过渡效果的介绍
- 应用场景:同级路由间切换用fade动效,不同级路由间切换时,用slide-leftslide-right
- 修改src/App.vue文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.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);
}


效果图如下:



使用scss

  • 安装依赖

    1
    npm install sass-loader node-sass --save-dev
  • 配置webpack config

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = {
    ...
    module: {
    rules: [
    ...,
    {
    test: /\.scss$/,
    loaders: ["style-loader", "css-loader", "sass-loader"]
    }
    ]
    }
    };
  • 在模块中引入scss文件

    1
    require("../statics/css/style.scss");

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

问题总结

  • build项目之后, 静态资源的如图片加载失败
    原因肯定是路径不对, 所以才找不到, 建议将webpack.config.js中的publicPath改为路径dist/, 若不行,再查看其它有关路径设置的地方
vue