Code前端首页关于Code前端联系我们

如何基于vue开发ui组件库(heaven-ui)

terry 2年前 (2023-09-08) 阅读数 131 #Vue

前言

Vue 是一个用于构建用户界面的渐进式框架,目前正在被越来越多的开发人员学习和使用。组件库可以帮助我们节省开发精力,无需一切从头开始,并且我们可以通过将小组件拼接在一起得到我们想要的最终页面。如果日常开发没有具体的业务需求,使用组件库进行开发无疑更加实用、高效,质量也相对更高。

本文一步步讲解了如何基于Vue构建UI组件库。

组件库官网

github地址

npm地址

1. 工艺表

首先我们简单了解一下构建UI组件库所涉及的技术栈。以下是我选择的:

  1. vue-cli:官方支持的 CLI 脚手架,提供现代的零配置构建设置;
  2. Vue:渐进式 JavaScript 库;
  3. Jest:用于单元测试组件库的 JavaScript 测试框架;

二.组件开发

项目初始化

开始,需要创建一个空的vue项目,在此基础上我们就可以开始编写接下来的组件库了!

npm i -g vue-cli // yarn add global vue-cli
vue init webpack heaven-ui  //(heaven-ui)可以随意更换成你的名称
cd heaven-ui
npm run dev
 

我们安装好依赖并进入项目启动服务后,vue-cli3会自动给我们展示一个默认页面。这里我使用sass来美化ui组件的样式。

目录结构

这是我的目录结构和说明
|- build/ # webpack打包配置 
|- lib/ # 打包生成的文件放这里 
|- src/ # 在这里写代码
     |- components/ # 各个组件,每个组件是一个子目录
     |- mixins/ # 复用的mixin
     |- utils # 工具目录
     |- App.vue # 本地运行的开发预览
     |- index.js # 打包入口,组件的导出
     |- main.js # 本地运行的运行
|- static/ # 存放一些额外的资源文件,图片之类的 
|- test/ # 测试文件夹
     |- specs/ # 存放所有的测试用例
     |- jest.conf.js/ # jest单元测试配置 
|- .npmignore 
|- .gitignore 
|- .babelrc 
|- README.md 
|- package.json
 

组件库结构

显示所有组件的入口点,组件的详细代码仅显示按钮部分

src/components/index.js

import Alert from './components/alert/index.js'
import Button from './components/button/index.js'
import ButtonGroup from './components/button-group/index.js'
import Checkbox from './components/checkbox/index.js'
import CheckboxGroup from './components/checkbox-group/index.js'
import DatePicker from './components/date-picker/index.js'
import Form from './components/form/index.js'
import FormItem from './components/form-item/index.js'
import Icon from './components/icon/index.js'
import Input from './components/input/index.js'
import Option from './components/option/index.js'
import Pagination from './components/pagination/index.js'
import Radio from './components/radio/index.js'
import RadioGroup from './components/radio-group/index.js'
import Rate from './components/rate/index.js'
import Select from './components/select/index.js'
import Switch from './components/switch/index.js'
import Table from './components/table/index.js'
import HTableColumn from './components/table-column/index.js'
import Tag from './components/tag/index.js'

const components = [
    Button,
    ButtonGroup,
    Checkbox,
    CheckboxGroup,
    DatePicker,
    Form,
    FormItem,
    Icon,
    Input,
    Option,
    Pagination,
    Radio,
    RadioGroup,
    Rate,
    Select,
    Switch,
    Table,
    HTableColumn,
    Tag,
]
    

 const install = function(Vue, opts = {}) {
    components.map(component => {
      Vue.component(component.name, component);
    })
    Vue.prototype.$alert = Alert;
 }

/* 支持使用标签的方式引入 */
if (typeof window !== 'undefined' && window.Vue) {
   install(window.Vue);
}


export default {
    install,
    Alert,
    Button,
    ButtonGroup,
    Checkbox,
    CheckboxGroup,
    DatePicker,
    Form,
    FormItem,
    Icon,
    Input,
    Option,
    Pagination,
    Radio,
    RadioGroup,
    Rate,
    Select,
    Switch,
    Table,
    HTableColumn,
    Tag,
}
 

src/components/button/index.js

import HButton from './src/button';

HButton.install = function(Vue) {
  Vue.component(HButton.name, HButton);
};

export default HButton;
 

组成部分是这样的

如何基于vue开发ui组件库(heaven-ui)如何基于vue开发ui组件库(heaven-ui)

封装配置

目录创建完成后,就该填血了。要打包一个组件库项目,我们首先要配置我们的webpack,否则写完源码后我们无法运行它。所以我们先找到建筑目录

webpack.base.js。保存一些基本规则配置

webpack.prod.js。整个元件库的封装配置

build/webpack.base.conf.js

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: process.env.NODE_ENV === 'production' ? './src/index.js' : './src/main.js'
  },
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'untils': resolve('src/untils'),
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.scss$/,
        loaders:['style','css','sass']
      }
    ]
  },
  node: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}
 

build/webpack.prod.conf.js

'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: 'heaven-ui.min.js',
    library: 'heaven-ui',
    libraryTarget: 'umd'
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      filename: 'heaven-ui.min.css',
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin()
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig
 

这里我配置了输出目录为lib,打包结果如下:

如何基于vue开发ui组件库(heaven-ui)如何基于vue开发ui组件库(heaven-ui)

3。单元测试

单元测试具有以下优势:

1。可以检测功能的隐藏错误

2。确保代码重构的安全。

组件库中的每个组件都可以重构或更新迭代。如果单元测试覆盖率高,则代码更改后更容易发现潜在的问题。例如,版本升级后,某些功能缺失。

组件库开发调试完成后,我们需要编写每个组件对应的单元测试,以达到100%覆盖率的目标。

以按钮为例:

测试/规格/Button.spec.js

import Vue from 'vue'
import Button from '@/components/button'

describe('button.vue', () => {
  it('button是否存在',()=>{
    expect(Button).to.be.ok;
  })
})
 

4。发布 NPM

在发布npm之前,我们需要根据npm的合约规则编写package.json。我们来解决组件库打包问题。首先,我们需要让脚手架编译我们的组件代码并发送到指定目录,我们通常按照合约交付规范输出到lib目录。项目打包后,我们需要将描述、关键字等写入打包文件中。详情如下:

description 元件库的描述文字

关键字 组件库关键字

许可证

repository 组件库关联的Git仓库地址

homepage 元件库显示的第一页地址

main 组件库主入口地址(使用组件时引入的地址)

private声明组件库的隐私性。如果要发布到公共npm网络上,请删除该属性或将其设置为false

publishConfig 用于设置npm发布的地址。这个配置对于​​团队中的npm服务器非常关键,可以设置为私有npm仓库

发布到npm的方法也很简单。首先我们需要在npm官网注册一个账号,然后登录控制台。最后我们执行npmpublish,即

是的。具体流程如下:

// 登录
 npm login
 // 发布
 npm publish
 // 如果发布失败提示权限问题,请执行以下命令
 npm publish --access public
 

注:本出版物版本号需要修改

5。总结

我们可以使用vue-cli或者其他工具生成另一个demo项目,并使用这个项目来引入我们的组件库。如果您的包尚未发布,您可以在

找到它

在组件库的项目目录下,使用npm link或yarn link命令创建链接

然后在demo目录中使用npm link packagename或yarnlink packagename,其中packagename是组件库的

包名,然后在demo工程的入口文件中

import Vue from vue
import Heaven from 'heaven-ui'
import 'heaven-ui/dist/heaven-ui.min.css'
// 其他代码 ...

Vue.use(Heaven)
 

这样设置后,我们创建的组件就可以在这个项目中使用了

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门