易新UI轮子开发笔记

312 阅读4分钟

创建项目

  1. 创建仓库并上传到Github仓库中
git init
git commit -m 'init'
  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. 初始化项目
npm init

version: 0.0.1

description: 这是一个UI框架

keywords: vue,ui

author: Layouwen

license: MIT

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

选中"Allow unsigned requests"

创建第一个全局组件

创建button.js

Vue.component('g-button', {
    template: `
        <button>hi</button>
    `
})

在index.html中引用

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

添加g-button的基本样式

.g-button{
    padding: 0 1em;
    height: var(--button-height);
    font-size: var(--font-size);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    background: var(--button-bg);
}
.g-button:hover {
    border-color: var(--border-color-hover);
}
.g-button:active {
    background-color: var(--button-active-bg);
}
.g-button:focus {
    outline: none;
}

将button改为单文件组件

<template>
    <button class="g-button">按钮</button>
</template>

<script>
    export default {}
</script>

<style lang="scss">
    .g-button {
        padding: 0 1em;
        height: var(--button-height);
        font-size: var(--font-size);
        border: 1px solid var(--border-color);
        border-radius: var(--border-radius);
        background: var(--button-bg);
        &:hover {
            border-color: var(--border-color-hover);
    }
        &:active {
            background-color: var(--button-active-bg);
    }
        &:focus {
            outline: none;
     }
    }
</style>

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

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

button.js

<template>
    <div>
        <button class="g-button" :class="{[`icon-${iconPosition}`]: true}">
            <svg v-if="icon" class="icon">
                <use :xlink:href="`#icon-${icon}`"></use>
            </svg>
            <div class="content">
                <slot></slot>
            </div>
        </button>
    </div>
</template>

<script>
    export default {
        props: ['icon', 'iconPosition']
    }
</script>

index.html

<g-button>
    按钮
</g-button>
<g-button icon="settings">
    按钮
</g-button>
<g-button icon="settings" icon-position="right">
    按钮
</g-button>

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

props: {
    icon: {},
    iconPosition: {
        type: String,
        default: 'left'
    }
}

将svg代码改为Icon组件

添加loading状态

添加button-group组件

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

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

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

使用chai作单元测试

npm i -D chai
npm i -D chai-spies

添加自动测试

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

根目录创建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: ['Chrome'],


        // 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('#icon-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('#icon-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

使用 travis 持续测试

travis网站地址

在根目录添加 .travis.yml 文件

language: node_js
node_js:
  - "12"
  - "13"
  - "14"
addons:
  chrome: stable
sudo: required
before_script:
  - "sudo chown root /opt/google/chrome/chrome-sandbox"
  - "sudo chown 4755 /opt/google/chrome/chrome-sandbox"

进入网站,添加对应仓库,回到本地仓库 git commit 和 push,网站自动进行测试。

发布npm包

注册 npmjs 账号

npmjs官网

添加账号

npm adduser

由于index.js中的import有兼容问题

npx parcel build index.js --no-minify

发布

npm publish

本地自动更新

npm link