# CoUI-SPA-demo
**Repository Path**: ZeVan-777/CoUI-SPA-demo
## Basic Information
- **Project Name**: CoUI-SPA-demo
- **Description**: CoUI 单页面应用 Demo 工程
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2017-10-19
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# CoUI 单页面应用开发指南
- [CoUI 单页面应用开发指南](#coui-单页面应用开发指南)
- [Ch.0 简介](#ch0-简介)
- [Ch.1 基本步骤 Cheat Sheet](#ch1-基本步骤-cheat-sheet)
- [项目初始化](#项目初始化)
- [常用命令](#常用命令)
- [热更新开发环境 dev](#热更新开发环境-dev)
- [应用待发布代码 dist](#应用待发布代码-dist)
- [可阅读编译后代码 build](#可阅读编译后代码-build)
- [应用发布](#应用发布)
- [Ch.2 Vue SPA 应用开发基础](#ch2-vue-spa-应用开发基础)
- [组件化](#组件化)
- [路由](#路由)
- [单向数据流](#单向数据流)
- [Ch.3 Webpack 功能点](#ch3-webpack-功能点)
- [定义公共包](#定义公共包)
- [更多功能,暂不介绍](#更多功能暂不介绍)
- [Ch.x 总结](#chx-总结)
## Ch.0 简介
本文涉及工具可分为三类:
1. 环境工具,正确安装,掌握基本命令行即可:
`node`、`gulp`、`slush`、`slush-coui`
2. 项目依赖,工作流构建其上,一般无需修改:
`webpack` 相关
3. 深度依赖,开发使用较多,需掌握常用功能:
`npm`、`vue`、`vue-router`、`coui`、`ES2015`
行文上由浅入深,有一定基础的开发人员,可按需要阅读特定部分内容。
样例工程存放的 [Git 仓库][git_repo],及 Demo 网址,扫码访问:

## Ch.1 基本步骤 Cheat Sheet
### 项目初始化
CoUI 通过基于 `gulp` 的脚手架工具 `slush`,配套提供最小可用的项目初始化工程,方便快速构建,以初次安装为例,步骤如下:
``` bash
# 1. 安装 Node 环境 `v6.x.x` LTS (一般自带 npm 包管理器)
# 检验方法:
node -v
npm -v
# 2. 安装项目脚手架工具,全局安装所需 Node 模块
npm i -g gulp slush slush-coui
# 3. (自选合适的目录)构建项目
slush coui
# 4. 局部安装项目需要的 Node 模块
npm i
# 或(推荐后者)
yarn install
```
经过一段时间(耐心地)等待,且网络与运气俱佳情况下,就可以进入“期待已久”的开发阶段了。
项目的目录结构如下,详情后文介绍:
``` bash
+-- bin # 构建所需脚本
+-- config # 构建相关配置
+-- node_modules # 项目所需 Node 模块
+-- src # !!项目源代码
| +-- public # 所有应用的公共静态资源
| +-- hello-world2 # 单个应用的源代码
| | +-- index.js # !!应用入口文件
| | +-- index.vue # 应用的初始页面组件
| +-- App.vue # 单应用的根组件模板
+-- .babelrc # Babel 编译 JS 所需配置文件
+-- .eslintrc.js # ESlint 语法 Syntax 检测配置文件
+-- gulpfile.js # gulp 任务处理脚本
+-- index.html # !!单页面模板页面
+-- package.json # npm 包管理文件(存储相关脚本命令、依赖包信息等)
+-- yarn.lock # 项目依赖 Node 模块版本信息(yarn install 时生效)
```
> “靠巧合编程” —— 《程序员的修炼之道》
> !!常见问题:
> 1. Node 版本控制:进入 Node 生态圈,意味着会使用各种 Node 模块(`node_modules`)。这些模块可能因为其中使用的特性,导致只在**特定 Node 版本中可用**。所以,我们应尽量避免引用的模块,使得必须使用太新版本 Node,但在多人开发中,仍可预见对 Node 版本的升级需求。基于以上原因,推荐使用 [nvm][nvm_guide] 安装、管理各版本 Node。
> 2. npm 速度慢:存放在特定服务器中 Node 模块,由于政策原因,国内可能访问困难,表现为执行 `npm install` 命令易卡死。推荐安装 [nrm][nrm_guide],使用国内的淘宝镜像 `nrm use taobao` 解决
> 3. Node 模块版本:`npm install` 命令默认安装最新的包,某些第三方模块的破坏性更新,可能导致项目 BUG。npm 5+ 之后才提供 `package-lock.json` 文件锁定项目使用模块的版本,如果使用低版本 npm,可以使用 [yarn][yarn_guide] 安装、管理 Node 模块,模块版本信息将记录在 `yarn.lock` 文件 —— stabler,且存在模块缓存机制 —— faster。
### 常用命令
`src` 源代码目录代码,必须经过 Webpack 处理后,才能成为各浏览器可识别的 HTML、JS、CSS 资源。现介绍项目根目录(`package.json`中定义)常用命令,辅助开发过程。
#### 热更新开发环境 dev
通过命令行工具,执行 `npm run dev`,将开启项目**开发服务器**,其主要功能如下:
1. 服务器以 `src` 目录作为根目录,在各应用(定义了单页面应用入口 `index.js` 的文件夹)目录中存储单页面应用
2. 访问单页面应用的浏览器,通过 `websocket` 与服务器保持连接。当源代码发生修改(保存),Webpack 自动重编译并更新到服务器,继而更新浏览器页面。整个过程快速,且无需手动操作。
3. 每一次编译 `src` 目录的源代码,都会先行通过 ESLint 进行代码格式检测,并将报错信息显示在命令行工具中。**格式问题会导致得不到编译后文件**,清除这些错误,推荐利用 IDE 集成的实时检测工具,可以在编译前发现并修改这些错误。

启动服务器时,选择需要调试的模块,将生成同一局域网内可访问的服务器地址

通过浏览器开发者工具,可以看到开发服务器加载单个 html 页面,通过单个编译后 js 文件驱动

P.S. !!源代码目录 `src` 内的语法错误(支持 ES6),将导致编译失败(页面不生成或更新)

#### 应用待发布代码 dist
如果一切顺利(网页在苹果、安卓、微信内置浏览器均正常),完成基本的开发调试工作后,我们的应用已经可以编译——浏览器可直接运行代码,打包——压缩代码,等待部署到需要它的地方。
执行 `npm run dist` 命令

可以看到,命令执行过程,将自动在各应用目录生成需要的 html、js、css,这些浏览器运行时所需资源。
> 那些细节:
> 1. 根据 Webpack 插件信息看来,CSS文件压缩率并不高,压缩后代码还破坏了可读性。聊胜于无,对 CSS 文件处理的意义,更多将体现在通过各类插件,让开发者无感知处理大部分浏览器兼容性问题。
> 2. `public` 公共文件夹下存放的 js、css 资源,已经集合了项目各应用所需,达到了精简功能包尺寸的目的。
> 3. 功能包独立引用的 js 文件体积过大,这是引用了几个第三方 Node 模块的结果。Webpack 的 CommonJS 模块实现,似乎导致在模块引用上 `All or Nothing` 问题 —— 引用模块将编译其所有的 JS,即使只使用一小部分功能。
#### 可阅读编译后代码 build
从项目源代码,到可以直接运行在各浏览器的代码,中间经历的一系列处理过程,将通过以 Webpack 为中心的工具链完成。
在调试环境可正常运行的代码,经处理后,发生问题的可能性不是没有。而编译、混淆、压缩等一系列处理过程将导致待发布代码阅读困难,查错无门。
通过 `npm run build` 命令,将编译源代码,生成可发布目录结构,剔除少了那些性能优化的工作 —— 让代码可阅读,且提供 map 文件,方便将报错的代码行对应到源码文件中的位置。

报错信息定位到源代码位置:

### 应用发布
在 `dist` 目录存放的待发布应用,需要部署到特定的服务器目录中,才能发挥其应有的作用,目前构建的应用,配置上仅针对在原生 WebViewer 沙箱运行的方式。
比如 Web 服务器常见的静态资源缓存问题,在这种场景下无需处理(沙箱读取的资源均为下载好的本地资源,精简功能包体积即可)。
以**发布轻应用到 MXM 平台为例**,配套提供 `npm run zip` 及 `npm run upload`(在 `config/service.js` 配置必要的信息)命令,简化发布过程。
本质上,该过程仍然为上传功能包到 MXM 服务器指定目录,供各设备下载更新。
## Ch.2 Vue SPA 应用开发基础
三大现代 JS 框架中,相比 `React` 和 `Angular`,`Vue` 仅学习使用成本低这一优点,就足矣令我们选择它,更何况在开发过程中,它的表现优雅而高效。
借助项目 Webpack 构建工具的配置,我们可以将一个个 `*.vue` (组件)文件及第三方资源,组合(编译)成为完整的单页面应用。
下面将以 Demo 工程为例,介绍需要掌握(以及自行深入)的基本概念。
### 组件化
项目默认已将 [CoUI][coui_docs] 引入公共包。作为 Coracle 自己的 Vue UI 组件库,我们可以持续维护、新增组件,封装高效、健壮的组件,以远离**复制粘贴式代码复用**的陷阱。
即使公共包已经引用了 CoUI 组件库,仍需要在应用的入口文件 `index.js` 中,声明在 Vue **全局使用 CoUI 组件**。
``` javascript
// src/hello-world2/index.js
import Vue from 'vue';
import App from '../App.vue';
import CoUI from 'coui';
// 。。。
// 一次性注册所有 CoUI 组件到这个单页面应用
Vue.use(CoUI);
// 在页面中实例化 App.vue 根组件
new Vue({ // eslint-disable-line
el: '#app',
render: h => h(App),
// 。。。
});
```
否则,需要在使用了 CoUI 组件的 `*.vue` 组件文件中,**单独声明**对其引用。
``` html
```
如果对 `*.vue` 文件结构感到陌生,推荐通过 `Vue` 的[官方文档-单文件组件][vue_single]学习。
对项目使用者而言,`CoUI` 组件库,就是项目默认引入的**第三方组件库**,与引用其他组件库的方式没什么不同。
除此之外,开发者还可以引用自己封装的**单文件组件**,这也是我们从根组件开始中,通过引用关系完成组件树结构,组合出完整应用的方法。
引用自定义单文件组件:

单文件组件源码:

单文件组件页面效果:

### 路由
借助 [vue router][vue_router_docs] 的管理,我们可以将浏览器地址栏的 hash 值 —— `window.location.hash`,与页面渲染的组件对应,从而实现不跳转页面,更新应用的目的 —— SPA。这些 Hash 改变页面,将在浏览器历史记录堆栈。
通过在 `App.vue` 生成的根组件实例,配置路由信息,`vue router` 将在匹配到对应地址时,更新对应的组件到预先定义的动态组件插槽 `` 中:
``` javascript
// src/hello-world2/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import index from './index.vue'
// 。。。
// 注册 vue router 插件
Vue.use(VueRouter);
const router = new VueRouter({
base: __dirname, // 配置当前目录为单应用根目录
routers: [
{
path: '/',
component: index, // 即在 #/ 路径下,App.vue 中的 router-view 标签,应加载 index.vue 组件
redirect: 'home'
componets: [ // 定义 index.vue 组件在 #/home 和 #/stat 其中的 router-view 标签分别加载的组件
{
path: 'home',
component: Home
},
{
path: 'stat',
component: Stat
}
]
},
// 错误路径重定向到首页
{
path: '*',
redirect: 'home'
}
]
})
```
可以看出,通过声明在根组件实例的 VueRouter 对象,不同 hash 地址,应用加载不同的组件,即实现了单页面路由跳转的功能。


### 单向数据流
每个“独立”的单文件组件 `*.vue`,仅仅声明了组件的构成,有一定的方式可以访问组件运行时实例(如 `ref` 引用),但一般不推荐使用,这破坏了组件间相互解耦隔离的特性。
运用 [prop向下,evnet向上](https://cn.vuejs.org/v2/guide/components.html#组件组合) 的方式。子组件通过`属性prop` 接受来自父组件的数据,父组件通过定义监听事件 `@event`,响应子组件状态的改变。
子组件 `Card.vue` 声明属性:
``` javascript
// Card.vue
export default {
// 。。。
props: {
item: {
type: Object,
required: true
}
}
// 。。。
}
```
父组件 `home.vue` 传递数据:

某些组件嵌套层级过深,事件一级级向上传递困难,可在根实例注册[事件总线][vue_bus],声明响应所有事件;更复杂的应用,可引入状态管理机制,通过 [Vuex][vue_flux] 实现,在此不做介绍。
## Ch.3 Webpack 功能点
Webpack 从入口文件起,解析项目中模块的依赖关系(一切 JS、CSS、图片等资源被视为模块),最后生成驱动 HTML 页面功能的静态资源。其强大的功能让人充满想象,但其复杂的配置选项,以及种类繁多的插件扩展机制,令人望而生畏。
本文仅就项目使用的主要 Webpack 功能点进行介绍,并自定义修改方法。
### 定义公共包
将各应用都在使用的模块,提出到公共包,可精简功能包体积,优化流量及更新速度。传统的手动引用式虽然也可达到目的,但易出错(重复引用、资源版本、路径等问题)。得益于 Webpack 对加载模块的缓存机制,声明到公共包的模块,无论在哪、几次引用,最后的页面使用的都是公共包中的资源。
我们将各环境通用的 Webpack 配置项,存放在 `webpack.base.conf.js` 文件中:
``` javascript
// webpack.base.conf.js
const entries = fs.readdirSync('./src').filter(isApp)
.reduce(getEntries, { // 定义 `public` 入口,合并公共模块
'public': ['fastclick', 'vue-router', 'vue', 'coui', 'CoUI/lib/style.css']
});
module.exports = {
entry: entries, // 构建入口的数组,写到 webpack `entry`配置项
// 。。。
}
```
我们可以看到,除了公共 JS 脚本,公共包中还添加了 CoUI 的样式文件 `CoUI/lib/style.css`。
在最终的可发布目录我们可以看到,`pulbic` 文件夹下存放了公共脚本 `public.js` 与公共样式 `public.css`:

借助 Webpack 插件 `CommonsChunkPlugin`,我们还可以向已经定义过的 `public` 处理入口,(通过 `minChunks` 配置项)自定义**添加更多模块的规则**。
``` javascript
// webpack.prod.conf.js
var baseWebpackConfig = require('./webpack.base.conf');
// 。。。
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'public',
minChunks({resource}, count) {
if (count < 2) return false;
const chunks = Object.keys(baseWebpackConfig.entry);
return count >= chunks.length - 1;
}
}),
// 。。。
]
// 。。。
```
上述代码,判断在所有轻应用都被引用的模块,属于公共包,反之则被分配到引用了它们的轻应用中。
仅 `hello-world2` 引用了 `reset.mobile.css`,因此最终生成 css 文件体积略大,而 CoUI 的公共样式 `style.css` 在 Webpack 配置文件中已被加入进 `public.css`
``` html
```
`hello-world2.css` 将包含在轻应用中单独引用以及书写在单文件组件 `style` 标签内的样式

而没有引用 `reset.mobile.css` 的 `hello-world` 轻应用页面出现问题:

> 配置技巧:
> 1. 一般认为,Webpack 相关的配置文件不会被修改。因此脚手架工程将提供了默认的 `public` 入口,打包“一定”会被用于项目的公共模块
> 2. 从 `public` 公共包引入更多模块的规则可以看出,在所有模块被引用的模块,将被判定进入公共包。因此在 `src/App.vue` 这个公共根组件中引用脚本或样式,即可向公共包新增模块。
> 3. 公共包一般由整个项目组共用,但各成员本地构建所使用的项目目录不一定相同。比如极端情况下,只有一个轻应用的项目,打包时,唯一轻应用中所有被引用模块,将被判定为公共包模块。
### 更多功能,暂不介绍
有基础的使用者可通过修改 `bin` 中的配置,改变项目开发、构建行为。
## Ch.x 总结
本单页面样例项目将在[开源Git仓库][git_repo]持续更新,`slush-coui` 初始化工程的更新(维护项目构建逻辑,非必须),也将反馈到该项目。目前的构建场景,针对门户轻应用完成构建,并不广泛适用与各平台所需优化的情况,针对项目中各类需求,欢迎通过各类渠道反馈(如issue)。
从某种程度上,这套基于 Webpack 构建的解决方案,对开发者的技术栈提出了更多的要求,主要基于以下几点考虑:
1. 渐进推行,符合目前轻应用的功能包与公共包分离模式,单页面应用间基本相互独立,可逐渐替换
2. 统一规范,且不论开发方式的优劣,一系列统一、通用的开发模式,利于技术上的复用推广
3. 弹性扩展,从最小可用,到迭代发展,`vue` 和 `webpack` 生态圈都有这样的特质,面临今后多变的需求
[git_repo]:https://gitee.com/ZeVan-777/CoUI-SPA-demo
[nvm_guide]:http://bubkoo.com/2017/01/08/quick-tip-multiple-versions-node-nvm/
[nrm_guide]:https://segmentfault.com/a/1190000000473869
[yarn_guide]:https://yarnpkg.com/zh-Hans/docs/getting-started
[coui_docs]:http://app.coracle.com:5303
[vue_single]:https://cn.vuejs.org/v2/guide/single-file-components.html
[vue_router_docs]:https://router.vuejs.org/zh-cn/essentials/getting-started.html
[vue_bus]:https://cn.vuejs.org/v2/guide/components.html#非父子组件的通信
[vue_flux]:https://vuex.vuejs.org/zh-cn/