blog icon indicating copy to clipboard operation
blog copied to clipboard

从零开始搭建Vue组件库 (keywords: 组件库、 vue、 web component。)

Open wuweijia opened this issue 6 years ago • 8 comments

tips: 阅读时间约为10分钟 keywords: 组件库、 vue、 web component。

为什么要做 ?

  1. 公司的业务迭代逐渐平稳,样式多变性逐渐消失,UI模块化已经形成规范。
  2. 开发人员不知道已经存在哪些组件,容易出现重复造轮子的情况。
  3. 易于管理和维护
  4. 独立部署之后UI也可以看到组件库写了哪些组件,方便设计的工作。

应该具备哪些功能

1. 可视化

一个组件库一定要有一个可以预览组件的地方

2. 文档

每个组件都要有一个说明文档

wuweijia avatar Nov 28 '18 06:11 wuweijia

step 1 生成一个基础框架

构建vue组件库,直接可以拿来vue-cli初始化一个项目来做架子,一来方便,二来你可以直接测试你写的组件在项目中能不能run的起来。

执行 vue init webpack projectname 初始化一个项目

step 2 整理文件结构

我们只需要component 用来存放组件 和 pages 可以用来展示组件,其他我们可以先不管,或自行处理

有了写组件的地方,我们就可以开始编写组件了。

step 3 写一个组件

src/component/button/button.vue
src/component/button/button.scss

<style lang="scss" scoped>
  @import './button.scss';
</style>
<template>
  <button class="button">{{title}}</button>
</template>
<script>
export default {
  name: 'Button',
  data: () => ({
    title: '这是一个button',
  }),
}
</script>

.button {
  width: 180px;
  height: 60px;
  font-size: 16px;
  line-height: 60px;
  text-align: center;
  color: #fff;
  cursor: pointer;
  background-color: red;
}

组件写完了这时候要考虑的就是怎么让组件可以像element-ui一样 可以引入使用

step 4 打包组件 实现 import 方式使用

  1. 组件写好了,要注册才可以使用。 这个不多说 vue文档 vue组件注册
  2. 开发插件也需要有对应的方法 vue开发插件 Vue.js 的插件应该有一个公开方法 install。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
  • 拿上面的组件举例说明 前面提到1,2两点怎么使用:

src/component/button/index.js

import Button from './index.vue'

Button.install = Vue => Vue.component(Button.name, Button)

export default Button

step 5 ok 组件也有了 注册也搞定了 下面就是打包了 现在前端使用的打包工具无疑是webpack了 鉴于vue-cli初始化的项目用的webpack是3版本的,我们就要这个版本的写法来写 webpack

需要考虑的问题

  • 入口?
  • 出口?
  • 编译那些类型文件?
  • 压缩混淆?
  • style独立?

这里我就不多说了,网上文章很多。自己看文档也不难, 这是我的写法。不懂的直接搜相关的字段就可以得到相应的文章

let path = require('path')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
  context: path.resolve(__dirname, './src/components/'),
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, './packages/lib'),
    filename: 'index.js',
    library: 'name', // 指定的就是你使用require时的模块名
    libraryTarget: 'umd', // libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的
    umdNamedDefine: true, // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            css: ExtractTextPlugin.extract({
              use: ['css-loader', 'sass-loader'],
              fallback: 'vue-style-loader',
            }),
          },
          extractCSS: true,
        },
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]',
        },
      },
    ],
  },
  resolve: {
    alias: {
      '~': path.resolve(__dirname, '../components'),
    },
    extensions: ['*', '.js', '.vue', '.json'],
  },
  performance: {
    hints: false,
  },
  devtool: '#source-map',
  plugins: [
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false,
        },
      },
      sourceMap: true,
      parallel: true,
    }),
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true,
        map: { inline: false },
        discardComments: {
          removeAll: true,
        },
      },
    }),
    new ExtractTextPlugin('style.css'),
  ],
}

这时候当你运行打包命令 webpack --config (这里跟你的webpack.config.js路径) 就会生成对应的文件了 image

step 6 生成了包之后就是发布npm包,发包过程直接google就可以了。 图中的 index.js 文件就是作为npm包的入口文件

{
  "name": "component",
  "version": "1.0.0",
  "description": "vue component",
  "main": "./lib/index.js",
  "directories": {
    "lib": "lib"
  },
  "repository": {
    "type": "git",
    "url": "githuburl"
  },
  "keywords": [
    "组件",
  ],
  "author": "you name",
  "license": "ISC"
}

step 7 在项目中使用跟element-ui 使用完全一致 将element-ui 替换成你的包名就可以了

import Vue from 'vue'
import Element from 'element-ui'

Vue.use(Element)

// or
import {
  Select,
  Button
  // ...
} from 'element-ui'

Vue.component(Select.name, Select)
Vue.component(Button.name, Button)

wuweijia avatar Nov 28 '18 06:11 wuweijia

至此 一个组件库开发就完结了。当然过程并没有这么顺利,还是遇到了一些问题。但是都不至于非常棘手,如果你在过程中遇到问题可以在这里提问,有时间我会解答。

wuweijia avatar Nov 28 '18 08:11 wuweijia

问题 1 nuxt 中使用 ssr 服务端渲染组件的时候发现报错 window or document is not defined

这个问题排查了很久,思路陷入了怎么写一个服务端渲染的组件。 最后发现是因为打包的过程中,没有抽离css文件,导致js会使用docment动态添加样式 所以打包出来的index.js 有 window 和 document 字段,抽离css样式 就会fix

wuweijia avatar Nov 28 '18 08:11 wuweijia

问题 2 既然维护的是一个库, 就要有一个规范,包括代码规范,提交规范。

  • 代码规范 代码规范 我们直接依赖eslint 使用 eslint-config-standard 相关规则

  • 提交规范 一个代码的提交日志,是快速定位问题解决问题的关键字,所以提交规范很重要 框架中使用了husky 强制在提交之前进行commit message 检查 不符合规范直接fail 并给予提示

wuweijia avatar Nov 28 '18 08:11 wuweijia

问题 3 CHANGELOG 版本日志是有必要的,好的版本日志依赖于好的提交 所以我们前面强制了提交规范 为版本日志打下了良好的基础 conventional-changelog-cli 版本日志插件 一行命令根据提交记录生成日志

wuweijia avatar Nov 28 '18 08:11 wuweijia

问题 4 组件预览怎么做?

组件预览可以自己写一个页面来做,但是重复工作太多,还要自己定义目录结构有些麻烦就找一个了框架来帮我们干活 storybook 使用起来比较简单 而且支持搜索和自动生成目录结构

wuweijia avatar Nov 28 '18 09:11 wuweijia

should support import on demand which is like

import {Button} from 'your-lib'

xianshenglu avatar May 11 '19 10:05 xianshenglu

should support import on demand which is like

import {Button} from 'your-lib'

It's a good idea. It's very simple, you can do it yourself。

wuweijia avatar May 24 '19 09:05 wuweijia