基于 webcomponent 的组件开发工作流

2,046 阅读4分钟

开发 omi-component-template 是为了让开发 @omiu 的人都可以更快更高效的上手 omi 组件的开发,并帮助开发同学了解 omi 组件开发的整体流程.可用于统一代码风格与代码规范。

omi-component-template 是使用下一代前端开发与构建工具 Vite ,结合之前开发的 omi-vite 脚手架针对 @omiu 组件开发特定的组件开发模板,可以结合 omi-cli 组合使用。

原有组件打包配置迁移

omi-component-template 开发的主要任务就是将原有的 @omiu/button 的组件迁移到 vite 的环境下,因此需要对打包构建流程进行相应的修改。vite 的生产端打包使用 rollup 进行打包,因此需要将原有的 rollup.config.js 迁移到新的项目中的 vite.config.js 中的 rollupOptions

将 rollup 打包配置迁移很简单,当然也不能无脑 Ctrl CV 了,因为 Vite 本身其实已经内置了很多的插件了因此我们需要先简化配置.

我们可以从 awesome-vite 中找到需要的插件以及 vite 内置插件我们需要删除的部分

包含在 Vite 中的插件

已在 vite 中默认覆盖的插件

实际迁移过程

原有的 rollup.config.js

import nodeResolve from "rollup-plugin-node-resolve";

import typescript from 'rollup-plugin-typescript';
import scss from 'rollup-plugin-scss'
import commonjs from '@rollup/plugin-commonjs';
const fs = require('fs')
const license = require("rollup-plugin-license");
const pkg = require("../package.json");
const licensePlugin = license({
  banner: `${pkg.name} v${pkg.version} http://omijs.org\r\nFront End Cross-Frameworks Framework.\r\nBy dntzhang https://github.com/dntzhang \r\n Github: https://github.com/Tencent/omi\r\n MIT Licensed.`
});

export default {
  input: "src/index.tsx",
  output: {
    format: "es",
    file: "./src/index.esm.js",
    name: pkg.name,
    sourcemap: true,
    strict: true
  },
  plugins: [
    nodeResolve({
    	main: true
    }),
    scss({
      //output: false,
      output: function (styles, styleNodes) {
        fs.writeFileSync('./src/index.css', styles)
      },
    }),
    typescript(),
    commonjs(),

    licensePlugin
  ],
  external: ['omi']
};

根据上面从 vite 官方文档和 awesome-vite 中可以知道迁移之后我们不需要手动配置,vite基本以及默认内置了

import nodeResolve from "rollup-plugin-node-resolve";
import typescript from 'rollup-plugin-typescript';
import scss from 'rollup-plugin-scss'
import commonjs from '@rollup/plugin-commonjs';

因此我们需要处理一下 licensePlugin 部分,经过搜索 vite 和 rollup-plugin-license 插件目前还不是很兼容,于是就去社区中再次搜索是否有什么可以代替的东西,于是找到 vite-plugin-banner 插件,解决 banner 打包后自动插入的问题,经过测试和简单的插件源码的阅读发现基本可以满足使用要求。

基于 webcomponent 的 omi 组件测试流程

前期技术参考过 React 及 Vue 组件库的单元测试流程由于生态部分有部分不太兼容,于是换了思路去看基于webcomponent 的技术生态,想去看看有没有相关的组件测试之类的东西。顺藤摸瓜从Google出的新一代webcomponent 框架 lit 中找到了相关的生态,webcomponent 本身就应该与框架无关,于是便尝试使用 @web/test-runner + @open-wc/testing 的技术栈进行测试.

安装测试工具

npm i -D @open-wc/testing@next

方案基本为使用 @open-wc/testing 提供的 fixture, html 模块对 webcomponent 进行渲染,Chai 用于断言,使用 expect 对产物进行判断是否与期望一致(CSS Style, Attribute Props 等)

import { html, fixture, expect } from '@open-wc/testing'

import '../src/index.esm.js'

const defaultProps = {
  plain: false,
  round: false,
  circle: false,
  loading: false,
  disabled: false,
  autofocus: false,
  nativeType: 'button',
  block: false,
}

describe('Testing o-button', () => {
  it('tests button with default props', async () => {
    const el = await fixture(html` <o-button>Default</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('o-button')).to.be.true
  })

  it('tests setting type', async () => {
    const el = await fixture(html`
      <o-button type="primary">Primary</o-button>
    `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('o-button-primary')).to.be.true
  })

  it('tests setting disabled', async () => {
    const el = await fixture(html` <o-button disabled>Disabled</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(button.getAttribute('disabled')).to.equal('')
    expect(classList.includes('is-disabled')).to.be.true
  })

  it('tests setting plain', async () => {
    const el = await fixture(html` <o-button plain>Plain</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('is-plain')).to.be.true
  })

  it('tests setting size', async () => {
    const el = await fixture(html` <o-button size="medium">Medium</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('o-button-medium')).to.be.true
  })

  it('tests setting round', async () => {
    const el = await fixture(html` <o-button round>Round</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('is-round')).to.be.true
  })

  it('tests setting circle', async () => {
    const el = await fixture(html` <o-button circle>Circle</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('is-circle')).to.be.true
  })

  it('tests setting block', async () => {
    const el = await fixture(html` <o-button block>Block</o-button> `)
    const button = el.shadowRoot.querySelector('button')
    const classList = button.getAttribute('class').split(' ')
    expect(classList.includes('is-block')).to.be.true
  })

  it('tests setting loading', async () => {
    const el = await fixture(html` <o-button loading>Loading</o-button> `)
    const svg = el.shadowRoot.querySelector('svg')
    expect(svg).to.not.be.null
  })

  it('passes the a11y audit', async () => {
    const el = await fixture(html` <o-button></o-button> `)
    expect(el).shadowDom.to.be.accessible()
  })
})