创建项目

  1. 创建仓库并上传到Github仓库中
  1. git init
  2. git commit -m 'init'
  1. 声明软件许可

易新UI轮子开发笔记 - 图1

Create new file => LICENSE => Choose a license template => MIT License => Review and submit => Commit new file => Create pull request => Merge pull request => Confirm merge => Delete branch

  1. 初始化项目
  1. npm init

version: 0.0.1

description: 这是一个UI框架

keywords: vue,ui

author: Layouwen

license: MIT

  1. 安装Vue
  1. npm i vue
  1. 设置unsigned

选中”Allow unsigned requests”

创建第一个全局组件

创建button.js

  1. Vue.component('g-button', {
  2. template: `
  3. <button>hi</button>
  4. `
  5. })

在index.html中引用

  1. <g-button>
  2. <script src="./button.js"></script>

添加g-button的基本样式

  1. .g-button{
  2. padding: 0 1em;
  3. height: var(--button-height);
  4. font-size: var(--font-size);
  5. border: 1px solid var(--border-color);
  6. border-radius: var(--border-radius);
  7. background: var(--button-bg);
  8. }
  9. .g-button:hover {
  10. border-color: var(--border-color-hover);
  11. }
  12. .g-button:active {
  13. background-color: var(--button-active-bg);
  14. }
  15. .g-button:focus {
  16. outline: none;
  17. }

将button改为单文件组件

  1. <template>
  2. <button class="g-button">按钮</button>
  3. </template>
  4. <script>
  5. export default {}
  6. </script>
  7. <style lang="scss">
  8. .g-button {
  9. padding: 0 1em;
  10. height: var(--button-height);
  11. font-size: var(--font-size);
  12. border: 1px solid var(--border-color);
  13. border-radius: var(--border-radius);
  14. background: var(--button-bg);
  15. &:hover {
  16. border-color: var(--border-color-hover);
  17. }
  18. &:active {
  19. background-color: var(--button-active-bg);
  20. }
  21. &:focus {
  22. outline: none;
  23. }
  24. }
  25. </style>

去iconfont添加图标,并应于进去

使用slot插槽自定义按钮文字,使用props自定义icon名字、icon位置

button.js

  1. <template>
  2. <div>
  3. <button class="g-button" :class="{[`icon-${iconPosition}`]: true}">
  4. <svg v-if="icon" class="icon">
  5. <use :xlink:href="`#icon-${icon}`"></use>
  6. </svg>
  7. <div class="content">
  8. <slot></slot>
  9. </div>
  10. </button>
  11. </div>
  12. </template>
  13. <script>
  14. export default {
  15. props: ['icon', 'iconPosition']
  16. }
  17. </script>

index.html

  1. <g-button>
  2. 按钮
  3. </g-button>
  4. <g-button icon="settings">
  5. 按钮
  6. </g-button>
  7. <g-button icon="settings" icon-position="right">
  8. 按钮
  9. </g-button>

将Props改写为对象形式,更好控制其类型

  1. props: {
  2. icon: {},
  3. iconPosition: {
  4. type: String,
  5. default: 'left'
  6. }
  7. }

将svg代码改为Icon组件

添加loading状态

添加button-group组件

  1. <g-button-group>
  2. <g-button icon="left">上一页</g-button>
  3. <g-button>1</g-button>
  4. <g-button>2</g-button>
  5. <g-button>3</g-button>
  6. <g-button icon="right" icon-position="right">下一页</g-button>
  7. </g-button-group>

检测button-group中的元素是否合法

  1. mounted() {
  2. for(let node of this.$el.children) {
  3. let name = node.nodeName.toLowerCase()
  4. if(name != 'button') {
  5. console.warn(`g-button-group 中应该只含有 button, 而你的是 ${name}`)
  6. }
  7. }
  8. }

使用chai作单元测试

  1. npm i -D chai
  2. npm i -D chai-spies

添加自动测试

  1. npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies

根目录创建karma.conf.js

  1. module.exports = function (config) {
  2. config.set({
  3. // base path that will be used to resolve all patterns (eg. files, exclude)
  4. basePath: '',
  5. // frameworks to use
  6. // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
  7. frameworks: ['mocha', 'sinon-chai'],
  8. client: {
  9. chai: {
  10. includeStack: true
  11. }
  12. },
  13. // list of files / patterns to load in the browser
  14. files: [
  15. 'dist/**/*.test.js',
  16. 'dist/**/*.test.css'
  17. ],
  18. // list of files / patterns to exclude
  19. exclude: [],
  20. // preprocess matching files before serving them to the browser
  21. // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
  22. preprocessors: {},
  23. // test results reporter to use
  24. // possible values: 'dots', 'progress'
  25. // available reporters: https://npmjs.org/browse/keyword/karma-reporter
  26. reporters: ['progress'],
  27. // web server port
  28. port: 9876,
  29. // enable / disable colors in the output (reporters and logs)
  30. colors: true,
  31. // level of logging
  32. // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
  33. logLevel: config.LOG_INFO,
  34. // enable / disable watching file and executing tests whenever any file changes
  35. autoWatch: true,
  36. // start these browsers
  37. // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
  38. browsers: ['Chrome'],
  39. // Continuous Integration mode
  40. // if true, Karma captures browsers, runs the tests and exits
  41. singleRun: false,
  42. // Concurrency level
  43. // how many browser should be started simultaneous
  44. concurrency: Infinity
  45. })
  46. }

创建 test目录 button.test.js 文件

  1. const expect = chai.expect;
  2. import Vue from 'vue'
  3. import Button from '../src/button'
  4. Vue.config.productionTip = false
  5. Vue.config.devtools = false
  6. describe('Button', () => {
  7. it('存在.', () => {
  8. expect(Button).to.be.ok
  9. })
  10. it('可以设置icon.', () => {
  11. const Constructor = Vue.extend(Button)
  12. const vm = new Constructor({
  13. propsData: {
  14. icon: 'settings'
  15. }
  16. }).$mount()
  17. const useElement = vm.$el.querySelector('use')
  18. expect(useElement.getAttribute('xlink:href')).to.equal('#icon-settings')
  19. vm.$destroy()
  20. })
  21. it('可以设置loading.', () => {
  22. const Constructor = Vue.extend(Button)
  23. const vm = new Constructor({
  24. propsData: {
  25. icon: 'settings',
  26. loading: true
  27. }
  28. }).$mount()
  29. const useElements = vm.$el.querySelectorAll('use')
  30. expect(useElements.length).to.equal(1)
  31. expect(useElements[0].getAttribute('xlink:href')).to.equal('#icon-loading')
  32. vm.$destroy()
  33. })
  34. it('icon 默认的 order 是 1', () => {
  35. const div = document.createElement('div')
  36. document.body.appendChild(div)
  37. const Constructor = Vue.extend(Button)
  38. const vm = new Constructor({
  39. propsData: {
  40. icon: 'settings',
  41. }
  42. }).$mount(div)
  43. const icon = vm.$el.querySelector('svg')
  44. expect(getComputedStyle(icon).order).to.eq('1')
  45. vm.$el.remove()
  46. vm.$destroy()
  47. })
  48. it('设置 iconPosition 可以改变 order', () => {
  49. const div = document.createElement('div')
  50. document.body.appendChild(div)
  51. const Constructor = Vue.extend(Button)
  52. const vm = new Constructor({
  53. propsData: {
  54. icon: 'settings',
  55. iconPosition: 'right'
  56. }
  57. }).$mount(div)
  58. const icon = vm.$el.querySelector('svg')
  59. expect(getComputedStyle(icon).order).to.eq('2')
  60. vm.$el.remove()
  61. vm.$destroy()
  62. })
  63. it('点击 button 触发 click 事件', () => {
  64. const Constructor = Vue.extend(Button)
  65. const vm = new Constructor({
  66. propsData: {
  67. icon: 'settings',
  68. }
  69. }).$mount()
  70. const callback = sinon.fake();
  71. vm.$on('click', callback)
  72. vm.$el.click()
  73. expect(callback).to.have.been.called
  74. })
  75. })

修改 package.json 的 scripts 参数

  1. "scripts": {
  2. "dev-test": "parcel watch test/* --no-cache & karma start",
  3. "test": "parcel build test/* --no-minify && karma start --single-run"
  4. },

开始测试

  1. npm run test
  2. npm run dev-test