# 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 网址,扫码访问: ![Demo地址](statics/qrcode-demo.png) ## 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 集成的实时检测工具,可以在编译前发现并修改这些错误。 ![运行开发环境](statics/run-dev0.png) 启动服务器时,选择需要调试的模块,将生成同一局域网内可访问的服务器地址 ![选择调试模块](statics/run-dev1.png) 通过浏览器开发者工具,可以看到开发服务器加载单个 html 页面,通过单个编译后 js 文件驱动 ![开发服务器页面目录结构](statics/run-dev2.png) P.S. !!源代码目录 `src` 内的语法错误(支持 ES6),将导致编译失败(页面不生成或更新) ![ESLint 语法报错](statics/run-devx.png) #### 应用待发布代码 dist 如果一切顺利(网页在苹果、安卓、微信内置浏览器均正常),完成基本的开发调试工作后,我们的应用已经可以编译——浏览器可直接运行代码,打包——压缩代码,等待部署到需要它的地方。 执行 `npm run dist` 命令 ![打包待发布应用](statics/run-dist0.png) 可以看到,命令执行过程,将自动在各应用目录生成需要的 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 文件,方便将报错的代码行对应到源码文件中的位置。 ![编译可阅读代码](statics/run-build0.png) 报错信息定位到源代码位置: ![报错信息定位到源代码](statics/run-build1.png) ### 应用发布 在 `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` 组件库,就是项目默认引入的**第三方组件库**,与引用其他组件库的方式没什么不同。 除此之外,开发者还可以引用自己封装的**单文件组件**,这也是我们从根组件开始中,通过引用关系完成组件树结构,组合出完整应用的方法。 引用自定义单文件组件: ![引用自定义单文件组件](statics/vue-com0.png) 单文件组件源码: ![单文件组件源码](statics/vue-com1.png) 单文件组件页面效果: ![单文件组件页面效果](statics/vue-com2.png) ### 路由 借助 [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 地址,应用加载不同的组件,即实现了单页面路由跳转的功能。 ![Demo首页](statics/vue-router1.png) ![Demo统计](statics/vue-router2.png) ### 单向数据流 每个“独立”的单文件组件 `*.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` 传递数据: ![父组件传递数据](statics/vue-prop1.png) 某些组件嵌套层级过深,事件一级级向上传递困难,可在根实例注册[事件总线][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-common1.png) 借助 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` 标签内的样式 ![生成发布包](webpack-css1.png) 而没有引用 `reset.mobile.css` 的 `hello-world` 轻应用页面出现问题: ![没有引用 reset 样式文件](webpack-css2.png) > 配置技巧: > 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/