手把手带你撸一个vue组件库!

13,369

前沿

目前,我们能接触到的模块化组件库真可谓是数不胜数,尤其是vue相关的那更是多如牛毛,譬如element,iview等等,虽然有很多,但是轮子虽多,合不合适自己还不好说,就像买衣服,有件衣服虽然好看,但是貌似容纳不了自身肥胖的身躯那也只能望洋兴叹。当然这时候,量身定做就显得那么重要!今天,就一起从头开始定制一套符合自身的vue组件库。

起步

首先,就已了解的组件库的写法也就那么回事,但是编写组件库的同事要写一套完善的文档也是尤为重要,经过各种方式了解到,目前我所熟知的有三种:

  • storybook 相信做react开发的筒子很熟悉,但自从v3.2版本之后开始支持vue,这消息可谓是喜大普奔。
  • vue-mark-down 用这个可以在markdown中演示代码案例,以及代码展示。
  • docsify 这是一个快速生成doc文档的库,也是最近才了解到的。

项目初始化

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

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

不出意外,打开浏览器: http://localhost:8080,便能看到如下:

组件库结构

接下来的结构都会以一个最简单的按钮组件做讲述!所有的组件都放在src>components里面,每个组件都新建一个目录,譬如button组件,目录结构如下:

src/components/index.js

import Button from './button'

const components = {
  Button
}

const install = function (Vue) {
  if (install.installed) return
  // components.map(component => Vue.component(component.name, component))
  Object.keys(components).forEach(key => {
    Vue.component(components[key].name, components[key])
  })
}

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

const API = {
  install,
  ...components
}

export default API

src/components/button/index.js

import Button from './src/button.vue'
Button.install = function (Vue) {
  Vue.component(Button.name, Button)
}
export default Button

src/components/button/src/button.vue

<template>
  <button>
    <slot></slot>
  </button>
</template>
<script>
export default {
  name: 'ly-button'
}
</script>

当然,我这里的button组件只是一个基本架构。

组件库的引用

上面写了一个按钮组件,我们当然是要把他用到我们的项目中,不知道大家是否还记得element的用法,其实我们这里的用法类似。

src/app.js添加如下代码:

import lyui from './components/index'
Vue.use(lyui)

现在我们的项目还没有发布到npm,就这样简单的项目内引用了,后面我们发布到npm了之后就完全可以跟element一样的使用方法了。ui库已经引用到项目中,有点迫不及待要去测试一下效果了。 src/docs/test.md

在markdown中添加如上代码!
好了,到这里,一个简简单单的按钮组件就成功了一半了!但是一个没有样式的按钮,总觉得很鸡肋!

脱胎换骨

房子已经买了,总不能直接住毛坯,接下来我们就一起简单的装修一番,材料多种多样,有scss,css,postcss,看个人喜好了,这里为了方便起见,我就直接用scss了,其实更简单的是直接写在style里面,但是不推荐那样做,因为可定制性不强。 src/components/button/button.scss

.ly-button{
  border:none;
  padding:5px 15px;
  background:#41B883;
  color:#fff;
}

src/components/index.scss

@import './button/src/button.scss'

src/main.js

import './components/index.scss'

上面我们简单的写了一下button的样式,并且在main.js进行引用,是不是就这样就可以了呢?答案是否定的,由于上面我们的样式是用的sass,然而我们的项目并没有任何关于sass的loader,这个简单,我们添加个sass的loader不就是了

cnpm install --save-dev sass-loader node-sass  
  • 因为sass-loader依耐于node-sass,这里给个友情提示,一般情况下,我们npm又或者是yarn都不能很顺利的安装node-sass,唯一的一步到位的方式便是使用淘宝的cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org

安装完sass-loader、node-sass之后直接重启服务就能看到我们加的样式已经生效了!

打包发布

一个组件库写完了,最后肯定是要打包发布的,不然我们写出来就毫无意义了。新建三个配置文件: config/package.config.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    'lyui': './src/components/index.js'
  },
  output: {
    path: path.resolve(__dirname, '../package'),
    publicPath: '/package/',
    library: 'lyui',
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  externals: {
    vue: {
      root: 'Vue',
      commonjs: 'vue',
      commonjs2: 'vue',
      amd: 'vue'
    }
  },
  resolve: {
    extensions: ['.js', '.vue']
  },
  module: {
    loaders: [{
      test: /\.vue$/,
      loader: 'vue-loader',
      options: {
        loaders: {
          css: 'vue-style-loader!css-loader',
          sass: 'vue-style-loader!css-loader!sass-loader'
        },
        postLoaders: {
          html: 'babel-loader'
        }
      }
    }, {
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'autoprefixer-loader'
      ]
    }, {
      test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
      loader: 'url-loader?limit=8192'
    }]
  },
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

config/package.div.config.js

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./package.config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractScss = new ExtractTextPlugin('/lyui.min.css')

module.exports = merge(baseWebpackConfig, {
  output: {
    filename: '[name].js'
  },
  module: {
    loaders: [{
      test: /\.scss$/i,
      loader: extractScss.extract(['css-loader', 'sass-loader'])
    }]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"development"'
      }
    }),
    extractScss
  ]
})

config/package.prod.config.js

const webpack = require('webpack')
const merge = require('webpack-merge')
const config = require('../config')
const baseWebpackConfig = require('./package.config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const extractScss = new ExtractTextPlugin('/lyui.min.css')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = merge(baseWebpackConfig, {
  output: {
    filename: '[name].min.js'
  },
  module: {
    loaders: [{
      test: /\.scss$/i,
      loader: extractScss.extract(['css-loader', 'sass-loader'])
    }]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    //new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      uglifyOptions: {
        ie8: false,
        output: {
          comments: false,
          beautify: false,
        },
        mangle: {
          keep_fnames: true
        },
        compress: {
          warnings: false,
          drop_console: true
        }
      }
    }),
    extractScss,
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap ?
        {
          safe: true,
          map: {
            inline: false
          }
        } :
        {
          safe: true
        }
    }),
    new CopyWebpackPlugin([
      // {output}/file.txt
      {
        from: `./src/components`,
        to: `./components`
      }
    ]),
  ]
})

接下来需要在package.json中添加三条script

"package:dev": "webpack --config build/package.dev.config.js",
"package:prod": "webpack --config build/package.prod.config.js",
"package": "npm run package:prod && npm publish && npm run build"

并且添加mian

"main": "package/lyui.min.js",

最后需要在 src/components/index.js中引用 index.scss

import './index.scss'
npm run package   \  npm run package:prod

执行完上面的命令之后会生成package目录,里面包含打包之后的所有文件。

文档编写

上面介绍了那么多,最后写出来的东西也只有你自己看得懂,用户可是看得一脸懵逼,这时候一个好的文档就显得尤为重要,接下来开始介绍今天的主角:docsify

全局安装dosify

npm i docsify-cli -g

初始化docsify

docsify init ./docs

启动文档服务器

docsify serve ./docs

启动成功之后,就可以直接打开:http://localhost:3000 接下来的很多配置的东西在这里就不累述了,docsify.js.org/,大家自行查看文档,而且配置也很简单!

结尾

虽然写了好多这种技术性文章,然而到目前为止还没有真正意义上的写一个属于自己的组件库,实在惭愧,希望大家能把这篇文章用到实处,真正意义上学有所用!