# 第15章 Vue 周边拓展

本章节相关代码存放在Github (opens new window)中。

Vue 生态如今越来越完善了,随之而来也有了不少的周边。除了前面的 Vue Loader、Vuex、Vue CLI 等常用工具,这里也介绍一些可以使用 Vue 进行开发的周边拓展工具,包括 Vuepress、Vue 生成小程序等。

# 15.1 VuePress 快速搭建文档平台

VuePress 是 Vue 驱动的静态网站生成工具,基于 Markdown 维护的文档网站。如果你有自己的博客,那应该也了解过 Hexo 工具,而 VuePress 可以理解为一个 Vue 版本的 Hexo。基于 Vue 和 Webpack 的开发方式,可以让开发者简单又便捷地实现一些想要的功能。

# 15.1.1 VuePress 介绍

一个 VuePress 网站其实是一个由 Vue、Vue Router 和 Webpack 驱动的单页应用。虽然和 Nuxt.js 很相似,但相比于 Nuxt.js 用于应用程序的构建,VuePress 专注于以内容为中心的静态网站。VuePress 由两部分组成:
(1) 由 Vue 驱动的极简静态网站生成器。我们在初始化项目之后,只需要添加 Markdown 文档,以及进行简单的配置就可以生成一个功能完备的静态网站(包括菜单、导航、路由、搜索等功能)。
(2) 提供基于技术文档的默认主题(可以参考 Vue 官方主题样式),同时也支持自定义配置主题方式。

对比 Hexo,VuePress 最大的亮点在于,除了通用的 Markdown 能力之外,VuePress 允许在 Markdown 中使用 Vue,也就是说我们写着 Markdown 的过程中可以插入一个 Vue 组件。而基于 Vue 的驱动方式,我们可以使用 Vue 自由地调整默认主题和样式,对于熟悉 Vue 开发的小伙伴来说,开发效率有了质的提升。

# 15.1.2 快速开发静态网站

介绍了那么多,我们来看一下使用 VuePress 生成一个静态网站到底有多简单。

# 安装和使用

首先我们需要安装依赖,完整的开发和构建流程步骤如下。

(1) 安装依赖。

# 全局安装 vuepress
npm install -g vuepress

# 安装依赖
npm install

(2) 目录结构设计。
为了方便管理,我们创建一个 docs 文件夹,将一些 markdown 文档放在里面维护,文件夹外面就可以做一些简单的依赖管理、脚本等。整体来说,我们的目录结构如下:

├─docs/                     // vuepress根目录
│  ├─.vuepress/             // vuepress应用相关
│  │   ├─dist/              // 编译之后的静态文件
│  │   ├─theme/             // 自定义主题
│  │   ├─config.js          // 配置
│  │   ├─enhanceApp.js      // 应用级别的配置
│  ├─basic/                 // 基础类文档
│  ├─vue/                   // Vue类文档
│  ├─README.md              // 首页文档和配置
│
├─deploy.sh                 // 部署到Github脚本
├─package.json              // 项目配置
├─README.md                 // 项目说明

VuePress 应用相关的配置都放置在.vuepress 文件夹下,关于自定义主题、配置和应用级别的配置后面会一点点描述。其他的文档归类可根据文档类别进行文件夹的区分,而 docs 下的默认 README.md 则是首页内容。

基于这样的目录结构,我们可以确定如何进行开发和打包,因此可以在 package.json 配置:

{
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }
}

然后我们可以通过命令来进行:

# 本地开始写作
npm run docs:dev

# 构建生成静态文件
npm run docs:build

(3) 文档内容管理填充。
这里我们分了基础类文档和 Vue 类文档,分别放置在docs/basicdocs/vue目录下面,在将 markdown 挪到对应的地方之后,我们可以这么进行组织:

├─docs/                     // vuepress根目录
│  ├─....                   // 其他省略
│  ├─basic/                 // 基础类文档
│  │  ├─1/                  // 基础类文档分类1
│  │  ├─2/                  // 基础类文档分类2
│  │  ├─README.md           // 基础类文档默认页
│  ├─vue/                   // Vue类文档
│  │  ├─1/                  // Vue类文档分类1
│  │  ├─2/                  // Vue类文档分类2
│  │  ├─README.md           // Vue类文档默认页
│  ├─README.md              // 首页文档和配置
│
├─....                      // 其他省略

调整好内容之后,我们来看看要怎么配置。

(4) 首页配置。
默认的主题提供了一个首页(Homepage)的布局这里,我们直接使用它,所以在根级的 README 中进行以下配置:

## <!-- docs/README.md -->

home: true # 使用默认主题的首页布局
actionText: 快速阅读 →
actionLink: /basic/1/basic-1-1.md
features:

- title: 基础类文档
  details: 基础类文档描述。
- title: Vue 类文档
  details: Vue 类文档描述。
- title: 其他补充
  details: 其他补充描述。
  footer: MIT Licensed | Copyright © 2018-present 被删

---

(5) 默认配置。
想要项目顺利跑起来,我们还需要补充默认配置,需要在 docs/.express 文件夹下添加 config.js 文件:

// docs/.express/config.js
module.exports = {
  title: "某个前端文档网站", // 首页标题
  shouldPrefetch: () => false, // 关闭预加载,可能导致首页渲染比较慢
  description: "Just playing around", // 首页描述
  themeConfig: {
    // logo: '/assets/img/logo.png',
    // 假定是 GitHub. 同时也可以是一个完整的 GitLab URL
    repo: "godbasin/vuepress-demo",
    // 自定义仓库链接文字。默认从 `themeConfig.repo` 中自动推断为
    // "GitHub"/"GitLab"/"Bitbucket" 其中之一,或是 "Source"。
    repoLabel: "Github",
    // 假如你的文档仓库和项目本身不在一个仓库:
    // docsRepo: 'vuejs/vuepress',
    // 假如文档不是放在仓库的根目录下:
    // 这里我们是放置在docs下的
    docsDir: "docs",
    // 假如文档放在一个特定的分支下:
    // 这里我们放在一个叫sourcecode的分支下,因为master需要用来放置生成的静态文件
    docsBranch: "sourcecode",
    // 顶部导航配置
    nav: [
      { text: "概述", link: "/" },
      { text: "基础类文档", link: "/basic/" },
      { text: "Vue类文档", link: "/vue/" }
      // 我们也可以添加FAQ的
      // { text: 'FAQ', link: '/faq' }
    ],
    // 左侧导航菜单配置
    sidebar: {
      "/basic/": [
        {
          title: "基础类文档分类1", // 菜单名
          collapsable: true, // 是否支持折叠菜单
          children: ["/basic/1/basic-1-1.md", "/basic/1/basic-1-2.md"]
        },
        {
          title: "基础类文档分类2", // 菜单名
          collapsable: true, // 是否支持折叠菜单
          children: ["/basic/2/basic-2-1.md", "/basic/2/basic-2-2.md"]
        }
      ],
      "/vue/": [
        {
          title: "Vue类文档分类1", // 菜单名
          collapsable: true, // 是否支持折叠菜单
          children: ["/vue/1/vue-1-1.md", "/vue/1/vue-1-2.md"]
        },
        {
          title: "Vue类文档分类2", // 菜单名
          collapsable: true, // 是否支持折叠菜单
          children: ["/vue/2/vue-2-1.md", "/vue/2/vue-2-2.md"]
        }
      ]
    }
  }
};

(6) 启动项目。
配置完成后,我们可以使用npm run docs:dev来启动本地环境,能看到环境搭建起来了:

image
图 15-1 本地环境启动

然后我们可以看到页面效果: 首页
图 15-2 首页效果

Vue类文档默认页
图 15-3 Vue 类文档默认页效果

到这里,我们可以快速简单地将文档管理起来。

# 添加组件使用

接下来我们需要添加自己的组件,例如我想在首页中添加一个弹窗。

(1) 全局引入 Element。
这里我想要直接使用 Element 的弹窗样式,可以通过引入 Element 来直接使用,首先我们需要执行npm install element-ui来安装 Element。同时,我们可以通过创建一个.vuepress/enhanceApp.js文件来做一些应用级别的配置:

// docs/.vuepress/enhanceApp.js
// 当该文件存在的时候,会被导入到应用内部

// 引入ElementUI
import ElementUI from "element-ui";
// 全局引入样式
import "element-ui/lib/theme-chalk/index.css";

export default ({
  Vue // VuePress 正在使用的 Vue 构造函数
}) => {
  try {
    // 使用element-ui
    Vue.use(ElementUI);
  } catch (e) {
    console.error(e.message);
  }
};

到这里,我们已经全局引入了 ElementUI 库,接下来可以直接在文档中使用了。

(2) 在 Markdown 文档中使用 Vue 组件。
一般来说,所有在.vuepress/components中找到的*.vue 文件将会自动地被注册为全局的异步组件,例如这里我们增加一个 Element 的弹窗:

<!-- docs/.vuepress/components/MyDialog.vue -->
<!-- 这里直接使用Element官方弹窗的代码 -->
<template>
  <el-button type="text" @click="open">点击打开 Message Box</el-button>
</template>

<script>
  export default {
    methods: {
      open() {
        this.$alert("这是一段内容", "标题名称", {
          confirmButtonText: "确定",
          callback: action => {
            this.$message({
              type: "info",
              message: `action: ${action}`
            });
          }
        });
      }
    }
  };
</script>

然后我们可以直接使用这些组件在任意的 Markdown 文件中(组件名是通过文件名取到的),例如这里我们在首页中使用:

## <!-- docs/README.md -->

home: true
actionText: 快速阅读 →
actionLink: /basic/1/basic-1-1.md
features:

- title: 基础类文档
  details: 基础类文档描述。
- title: Vue 类文档
  details: Vue 类文档描述。
- title: 其他补充
  details: 其他补充描述。
  footer: MIT Licensed | Copyright © 2018-present 被删

---

# Element 弹窗

<MyDialog/>

然后我们就可以看到效果:
首页
图 15-4 首页效果

点击后出现弹窗
图 15-5 点击后出现弹窗

点击此处查看源码 (opens new window)

对于 VuePress,快速生成可用主题和配置是它的一大亮点,而直接在 Markdown 文件中使用 Vue 组件则是特别便利实用的另外一个特色。当然,VuePress 还支持自定义主题配置,不过这里篇幅关系就不多介绍,大家感兴趣可以去官网查看,也可以去https://github.com/godbasin/front-end-playground查看使用VuePress搭建的前端游乐场,源码在sourcecode分支。

# 15.2 Vue 生成 web 与小程序

如今,小程序用完即走、无需安装的便捷吸引了越来越多的用户愿意使用,同时微信提供的流量吸引了不少的小程序开发者加入其中。小程序有一套自己的规范,与常用的几大框架都不好进行兼容,我们来看一下为什么。

# 15.2.1 小程序与 Web 的区别

小程序的渲染层和逻辑层分别由两个线程管理:渲染层的界面使用了 WebView 进行渲染,逻辑层采用 JsCore 线程运行 JS 脚本。通过双线程的方式,我们可以阻止开发者使用一些浏览器提供的,诸如跳转页面、操作 DOM、动态执行脚本的开放性接口,来保证小程序的安全性,也方便平台进行管控。但是这样的设计也带来其他的问题,逻辑层和渲染层的通信(setData)需要由 Native(微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发。以上这些情况意味着:
(1) 在浏览器中可以运行的 DOM、BOM 等对象和 API,都无法在小程序中使用。
(2) 小程序的一些 API 使用方式与浏览器不一致(请求、缓存等)。

对于强交互的场景(通信过多容易引起性能问题),小程序引入了原生组件,这样用户和原生组件的交互可以节省通信,从而提升性能。但这样还意味着:
(3) 在浏览器中可以使用的组件,在小程序中成了原生组件(input、video、canvas 等),使用方式和渲染效果完全不一致。

在这样的种种情况下,我们跑在浏览器中的代码,如果想要在小程序中运行,我们必须要重新开发来实现。那为什么一定要使用小程序呢,直接用 H5 不好吗?其实小程序做了很多的体验优化,同时天然的预加载、原生组件的流畅性,最重要的还是微信平台提供的流量,在很多情况下都不失为一个不错的选择。

# 15.2.2 开源类 Vue 小程序框架

虽然我们没法一套代码同时运行在浏览器和小程序中,但是依然有不少的开发者或者团队开发了一些框架,来尽可能使用一套代码完成多种环境的开发,例如 mpvue(Vue 系)、wepy(Vue 系)、taro(React 系)等。但毕竟不是官方支持,同时小程序的 API 和兼容性也不断在变动,使用的开源框架要是更新不及时,甚至可能成为技术债务。

# 15.2.3 kbone 的出现

kbone 作为一套解决方案应运而生,用于支持让一个 vue 项目可以同时在 web 端和小程序端被使用。前面说过,在小程序中无法直接使用 DOM/BOM 等 API,因此 kbone 提供了适配层的方式来进行兼容。适配层它提供基础的 DOM/BOM API,让小程序端尽量能使用 web 端的能力:
image
图 15-6 kbone 适配层

简单来说,就是提供了一个将 DOM/BOM 相关 API 转换成在小程序中的实现方式,这里有点像前面《第13章 实战:表单配置化实现》中使用的配置方式,适配层维护一套虚拟 DOM 节点,然后根据节点信息,再通过简单的wx:forwx:if来生成页面相关代码。

我们来看下,我们在第8章中的 Todo List 代码,直接使用 kbone 来运行,跑在 web 和小程序两端的效果是怎样的。

# kbone 安装

使用 kbone,官方提供了三种方式。

(1) 新项目使用 kbone-cli。
对于新项目,可以使用 kbone-cli 来创建项目,首先需要全局安装:

# 全局安装 kbone-cli
npm install -g kbone-cli

(2) 基于模板快速调整开发。
除了使用 kbone-cli 外,也可以直接将现有模板复制下来,然后在模板基础上进行开发改造。

(3) 自行改造 Webpacl 配置开发。
如果想要更灵活地搭建自己的项目,或者是想对已有的项目进行改造,则需要自己补充对应配置(构建到小程序代码的 webpack 配置、使用 webpack 构建中使用到的特殊插件 mp-webpack-plugin 配置)来实现 kbone 项目的构建。

# Todo List 接入 kbone

其实针对已有的项目,不管是以上哪种方式都是可以的,这里我们就直接使用 kbone-cli 生成一个新项目,然后把原有的 Todo List 内容导入查看。

(1) 初始化项目。
首先执行kbone init kbone-todo-list,生成一个初始化项目,初始化的时候需要选择对应的框架,这里我们选择 Vue:

image
图 15-7 kbone 初始化

初始化完成后,我们来看看目录说明:

<!-- 此模板 Web 端使用单入口,通过 vue-router + 动态 import 的方式来运行;小程序端则按照业务分拆成多个页面,同属一个业务的页面则通过 vue-router 来组织。 -->

├─ build
│  ├─ miniprogram.config.js  // mp-webpack-plugin 配置
│  ├─ webpack.base.config.js // Web 端构建基础配置
│  ├─ webpack.dev.config.js  // Web 端构建开发环境配置
│  ├─ webpack.mp.config.js   // 小程序端构建配置
│  └─ webpack.prod.config.js // Web 端构建生产环境配置
├─ dist
│  ├─ mp                     // 小程序端目标代码目录,使用微信开发者工具打开,用于生产环境
│  └─ web                    // web 端编译出的文件,用于生产环境
├─ src
│  ├─ common                 // 通用组件
│  ├─ mp                     // 小程序端入口目录
│  │  ├─ home                // 小程序端 home 页面
│  │  │  └─ main.mp.js       // 小程序端入口文件
│  │  └─ other               // 小程序端 other 页面
│  │     └─ main.mp.js       // 小程序端入口文件
│  ├─ detail                 // detail 页面
│  ├─ home                   // home 页面
│  ├─ list                   // list 页面
│  ├─ router                 // vue-router 路由定义
│  ├─ store                  // vuex 相关目录
│  ├─ App.vue                // Web 端入口主视图
│  └─ main.js                // Web 端入口文件
└─ index.html                // Web 端入口模板

(2) 迁移 Todo List 代码。
初始化 kbone 项目之后,我们需要将原有的代码迁移过来,迁移过程中有以下注意事项:

  • https 引入的 css/js 资源在小程序中不再可用,可以下载下来通过相对路径引入
  • scoped 样式在小程序中不可用,需要自行加作用域或是进行冲突解决
  • 部分样式在 Web 端和小程序端表现不一致,需要进行调整
  • Web 端的鼠标 hover、dbclick 等状态和事件在小程序中不再可用,需要进行调整
  • 之前全局引入了 Velocity 进行延时动画,在小程序中需要移除
  • 路由调整,需要同时调整src/router/index.js文件、src/mp/todo/main.mp.js文件、build/miniprogram.config.js文件、build/webpack.mp.config.js

(3) Web 端启动。
代码迁移过程比较琐碎,这里就不详细讲啦,大家可以去找源码进行参考。迁移完之后,我们首先将 Web 端跑起来,需要执行npm run web命令之后,我们来看看效果:
image
图 15-8 Web 端启动效果

我们能看到主要包括两个变动:
(1) 增加添加按钮,因为在小程序端无法监控@keyup.enter事件。
(2) 删除图标,从之前需要鼠标 hover 可见,调整成直接可见,因为在小程序中没有 hover 状态。

点击删除的时候,会进行弹窗,这里使用的是 Vuex 进行状态更新(参考《第11章 全局数据管理与 Vuex》):
点击删除
图 15-9 点击删除弹窗效果

(4) 小程序端启动。
在小程序端启动,要先执行npm run mp命令,接下来还需要先进入 dist/mp 目录执行npm install安装相关的依赖包(主要是 miniprogram-render 和 miniprogram-element),然后用开发者工具打开 dist/mp 目录后再进行 npm 构建(菜单栏-工具-构建 npm):

工具中效果
图 15-10 工具中效果

我们能看到除了样式有些不一致之外,其他功能都是可用的。另外,我们从工具中可以看到,最终生成的代码基本上是通过<element>组件来实现的,我们看看生成的代码:

<!-- dist/mp/pages/todo/index.html -->
<element
  wx:if="{{pageId}}"
  class="{{bodyClass}}"
  style="{{bodyStyle}}"
  data-private-node-id="e-body"
  data-private-page-id="{{pageId}}"
>
</element>
// dist/mp/pages/todo/index.json
// 这里使用了官方兼容层组件miniprogram-element
{
  "enablePullDownRefresh": false,
  "usingComponents": {
    "element": "miniprogram-element"
  }
}

如果要继续看下去,你会发现 dist/mp/pages/todo/index.js 文件中也是很多兼容性的代码,也可以验证了我们前面说过的,适配层维护一套虚拟 DOM 节点信息,再通过简单的wx:forwx:if配合 miniprogram-element 中的 element 组件来生成页面相关代码。

点击删除的时候,Vuex 状态相关也是可以正常执行的:
手机执行效果
图 15-11 手机执行效果

点击此处查看源码 (opens new window)

当然,kbone 也会存在一些问题,官方也进行了相关的说明,常见的例如:

  • 小程序里的所有页面必须同源
  • 受限于小程序环境,目前无法支持异步组件
  • 小程序不支持属性选择器,所以无法支持 scoped style
  • iframe 标签无法支持
  • vue 的 transition 组件无法完美支持
  • 不可使用 rem
  • 等等

关于 kbone 相关的暂时就介绍到这里,其实我们的 Todo List 样式是 PC 端适应的,而如果做了移动端的样式兼容,就可以很方便地使用 kbone 同构生成 Web 代码(移动端+PC)和小程序代码了,而小程序官方的微信开放社区也正是这样做的,一套代码维护多端。

# 15.3 其他 Vue 周边

除了 VuePress 和 kbone 之外,常用的还有 Nuxt.js。

Nuxt.js 是一个基于 Vue(Vue.js、Vue Router、Vuex 等)、Webpack 和 Babel 的通用应用框架,它预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置,开发中可以直接基于初始化的项目上进行快速开发。使用服务器端渲染,最主要其实就是为了解决 SEO 的问题,同时服务器端渲染还能解决的首屏加载速度的问题。Nuxt.js 为"客户端/服务端"这种典型的应用架构模式提供了许多有用的特性,包括强大的路由功能、异步数据加载、中间件支持、布局支持等。当然,除了服务端渲染,Nuxt.js 同时还支持单页应用(SPA)、生成静态站点等模式。

其实我们前面也介绍过 Vuex、Vue CLI 等,另外 Vue 还包括了 Vue Loader、vue-devtools 等工具,由于篇幅关系这里不介绍使用,大家可以去官网上学习下怎样使用。Vue 作为如今国内的主流框架之一,提供了很完备的生态和社区,越来越多的开发者也加入进来一起建设,周边拓展可见也会越来越丰富。