Ice-UI 造轮子
前言
已经有了那些经典且成熟的库或者框架,为什么还要自己去造轮子呢?
其实,造轮子对于提升自己的技能水平
是有很大帮助的。
- 经常用别人写的按钮组件,为何不自己写一个试试?
- 经常用别人写的消息弹框,为何不自己写一个试试?
- 自己真的一步一步写出来之后,就会发现「就那么回事」,事实就是能力得到了提升。
同时,可以提高自己解决问题
的能力。
- 当我们自己写业务代码的时候,只需要满足自己业务的逻辑。
- 而写轮子的的时候,就需要满足大部分人的需求,这就很锻炼自己的提炼需求、分析需求和解决问题的能力。
一般网站的开发流程
- 立项 - 确定要做,确定人员,确定预算等
- 需求 - 需求收集和分析
- 收集比分析更难,有的时候用户也不知道自己的需求
- 亨利·福特曾说过,「如果我最初是问消费者他们想要什么,他们应该是会告诉我,要一匹更快的马!」
- 可以用「用例图」来分析需求
- 可行性分析
- 系统设计(功能设计、框架设计)
- UML 图、时序图等
- 原型设计(草图、线框图)
- 草图用纸和笔画
- 线框图可以用 Balsamiq
- 交互设计
- 可以用 Axure RP、墨刀、Sketch.app
- 视觉设计
- 可以用 Photoshop、Fireworks、Sketch.app
- 程序开发
- 测试
- 功能预演
- 内测
- 灰度发布
- 正式发布
项目起步
-
首先选择一个空目录,创建一个 README.md 文件并简单先编辑一下
-
初始化 git
git init git add . git commit -m init
在GitHub创建仓库,建立连接,
git push
-
在GitHub上确立项目的LINCENSE
-
项目初始化
npm init npm install -S Vue
-
开始开发自己的组件
自动化测试
BDD Behavior Driven Development 行为驱动开发
TDD Test Driven Development 测试驱动开发
Assert 断言
使用 Karma + Mocha 做单元测试
- Karma(
[ˈkɑrmə]
卡玛)是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例 - Mocha(
[ˈmoʊkə]
摩卡)是一个单元测试框架/库,它可以用来写测试用例 - Sinon(西农)是一个 spy / stub / mock 库,用以辅助测试。
步骤
-
安装各种工具
npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies
-
创建 karma 配置
// 新建 karma.conf.js,内容如下 module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['mocha', 'sinon-chai'], client: { chai: { includeStack: true } }, // list of files / patterns to load in the browser files: [ 'dist/**/*.test.js', 'dist/**/*.test.css' ], // list of files / patterns to exclude exclude: [], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: {}, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ['ChromeHeadless'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false, // Concurrency level // how many browser should be started simultaneous concurrency: Infinity }) }
-
创建测试文件(test/button.test.js)
const expect = chai.expect; import Vue from 'vue' import Button from '../src/button' Vue.config.productionTip = false Vue.config.devtools = false describe('Button', () => { it('存在.', () => { expect(Button).to.be.ok }) it('可以设置icon.', () => { const Constructor = Vue.extend(Button) const vm = new Constructor({ propsData: { icon: 'settings' } }).$mount() const useElement = vm.$el.querySelector('use') expect(useElement.getAttribute('xlink:href')).to.equal('#i-settings') vm.$destroy() }) it('可以设置loading.', () => { const Constructor = Vue.extend(Button) const vm = new Constructor({ propsData: { icon: 'settings', loading: true } }).$mount() const useElements = vm.$el.querySelectorAll('use') expect(useElements.length).to.equal(1) expect(useElements[0].getAttribute('xlink:href')).to.equal('#i-loading') vm.$destroy() }) it('icon 默认的 order 是 1', () => { const div = document.createElement('div') document.body.appendChild(div) const Constructor = Vue.extend(Button) const vm = new Constructor({ propsData: { icon: 'settings', } }).$mount(div) const icon = vm.$el.querySelector('svg') expect(getComputedStyle(icon).order).to.eq('1') vm.$el.remove() vm.$destroy() }) it('设置 iconPosition 可以改变 order', () => { const div = document.createElement('div') document.body.appendChild(div) const Constructor = Vue.extend(Button) const vm = new Constructor({ propsData: { icon: 'settings', iconPosition: 'right' } }).$mount(div) const icon = vm.$el.querySelector('svg') expect(getComputedStyle(icon).order).to.eq('2') vm.$el.remove() vm.$destroy() }) it('点击 button 触发 click 事件', () => { const Constructor = Vue.extend(Button) const vm = new Constructor({ propsData: { icon: 'settings', } }).$mount() const callback = sinon.fake(); vm.$on('click', callback) vm.$el.click() expect(callback).to.have.been.called }) })
-
创建测试脚本
在 package.json 里面找到 scripts 并改写
"scripts": { "dev-test": "parcel watch test/* --no-cache & karma start", "test": "parcel build test/* --no-minify && karma start --single-run" },
-
运行测试脚本
npm run test
一次性运行npm run dev-test
经行 watch 监听
持续集成
什么是持续集成
Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。
持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码"集成"到主干。
持续集成的好处在于,每次代码的小幅变更,就能看到运行结果,从而不断累积小的变更,而不是在开发周期结束时,一下子合并一大块代码。
使用准备
- 拥有 Github 账号
- 在该账号下面有一个项目
- 该项目里有可运行的代码
- 该项目还包含构建或者测试脚本
开始使用,访问官方网站 travis-ci.org ,用 Github 账户登录,选择需要激活的仓库,然后 Travis 就会监听这个仓库的所以变化。
.travis.yml
Travis 要求项目的根目录下面,必须有一个.travis.yml
文件。这是配置文件,指定了 Travis 的行为。该文件必须保存在 Github 仓库里面,一旦代码仓库有新的 Commit,Travis 就会去找这个文件,执行里面的命令。
language: node_js
node_js:
- "12"
addons:
chrome: stable
sudo: required
before_script:
- "sudo chown root /opt/google/chrome/chrome-sandbox"
- "sudo chmod 4755 /opt/google/chrome/chrome-sandbox"
发布 npm 包
确保代码测试通过
npm run test
全部是绿色才行
上传代码到 npmjs.org
-
创建 index.js ,在 index.js 里将想要的导出的内容全部导出
-
node 目前确实不支持 import,需要用 babel 转译 import,
npx parcel build index.js --no-minify
-
将package.json 的
"main"
改为dist/index.js
-
注册一个 npm 账户,确认下
邮箱
-
在项目根目录运行
npm adduser npm publish
要将 npm 从淘宝源切换回官方源
本地加速调试
在项目目录使用 npm link
或者yarn link
,在本地使用时 使用 npm link <package_name>
小结
-
测试用例中,如果有一部操作,需要在 it 的 fn 中传入参数 done,并在异步操作执行完后调用 done()。
-
Vue.nextTick([callback,content])
参数:
{Function} [callback]
{Object} [context]
用法:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据 vm.msg = 'Hello' // DOM 还没有更新 Vue.nextTick(function () { // DOM 更新了 })
-
props 是 对象或者函数时,需要用函数 return
-
父元素设置了
min-height
之后,子元素的height:100%
就无作用了。 -
发布订阅模式
emit/on/off
-
单向数据流
Vuepress 制作官网
快速上手
将 VuePress 安装为本地依赖
yarn add -D vuepress # npm install -D vuepress
创建第一篇文档
mkdir docs && echo '# Hello VuePress' > docs/README.md
在 package.json
中添加一些 scripts.
{
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
}
在本地启动服务器
yarn docs:dev # npm run docs:dev
基本配置
目录结构
.
├─ docs
│ ├─ README.md
│ └─ .vuepress
│ └─ config.js
└─ package.json
必要的配置文件 .vuepress/config.js
module.exports = {
base:'/Ice-UI/',
title: 'Ice-UI',
description: '一个简约、易用的 UI 框架',
themeConfig: {
logo: '/assets/img/logo.png',
nav: [
{text: '主页', link: '/'},
{text: '文档', link: '/get-started/'},
{text: 'Github', link: 'https://github.com/Orange-ice/Ice-UI'},
],
displayAllHeaders: true, // 默认值:false
sidebar: [
{
title: '入门',
children: [
'/install/',
'/get-started/',
]
},
{
title: '组件',
collapsable: false, // 可选的, 默认值是 true,
children: [
'/components/button',
'/components/tabs',
'/components/input',
'/components/grid',
'/components/layout',
'/components/toast',
'/components/collapse',
'/components/popover',
]
},
]
}
}