写在前面
小小的前端有大大的梦想. 做一个属于我们自己的 UI 组件库应该是个不错的小目标了. 那... 组件是什么? 我想应该不用多说, 在日常开发中我们天天与之打交道, 你所写的每一个 vue 文件都可以当做是一个组件, 只不过通用性有所区别而已. 组件写得多了, 随着时间的推移, 你就会有写一个组件库的冲动了. 其实更多的为了提升 B 格, 当和别人谈论的时候, 你说你写过一个 UI 库, 别人就觉得你可能有点吊. 好了, 不吹 B, 赶紧撸吧
源码地址: https://github.com/lgq627628/xr-ui
知识前置
我们可能习惯了在一个 vue 里面引入组件的方式, 所以先这里要巩固一下全局引入组件的方式. 举个栗子, 一般我们的用法是这样的:
- import Loading from '../components/loading'
- // 方法一: name 是组件的名字
- Vue.component(Loading.name, Loading)
- // 方法二: 前提是 Loading 有提供 install 这个方法
- Vue.use(Loading);
上面两种方式都可以用来全局注册组件, 但是参数不一样, 选择哪种方法都可以, 但要留意一下第二种方法, 这种方法需要组件本身有个 install 的方法. 额... 为啥要有 install 的方法? 这个你就当做是规定就好, 你用人家的框架就得遵循人家的规则. 扯犊子吧, 其实是因为执行 Vue.use 的时候会执行里面的 install 方法, 所以我们需要写个 install. 对此你不用较真, 寄人篱下是这样的, 哦不对♀, 应该是站在大神的肩膀上.
搭建目录
快速创建项目
这步应该不用多说吧, 执行一下 vue create xr-ui(前提是你安装了 vue-cli3), 然后看自己喜好选择一些配置, 几下回车之后一个清爽的初始项目就有了. 这里我没有选择用 typescript(主要是我还没习惯用它), 所以选用 typescript 的同学们注意啦, 可能写法会有少许不同, 有劳大伙看着改下吧.
修改目录结构
把 src 目录名字改成 examples, 这是用于展示组件示例的
在根目录下新建一个 packages 文件夹, 这是用来放组件的
你可能会问为什么要建这样的目录结构, 问得好, 原因很简单, 因为别人是这样做的, 所以借鉴 (模仿) 罗... 我们可以看到 Element 的源码也是这样的结构:
当我们水平不够的时候, 模仿是一种强大的学习能力.
添加配置文件
小改了一下目录之后, 你会惊奇的发现项目运行不了了. 没关系, 这很正常, 毕竟 src 都不见了, 路径啥的肯定得报错. 所以现在我们来解决这个问题. 在根目录下新建一个 vue.config.JS 文件(新项目是没有这个文件的), 并写入以下内容:
- const path = require('path')
- module.exports = {
- // 修改 pages 入口
- pages: {
- index: {
- entry: 'examples/main.js', // 入口
- template: 'public/index.html', // 模板
- filename: 'index.html' // 输出文件
- }
- },
- // 扩展 webpack 配置
- chainWebpack: config => {
- // @ 默认指向 src 目录, 这里要改成 examples
- // 另外也可以新增一个 ~ 指向 packages
- config.resolve.alias
- .set('@', path.resolve('examples'))
- .set('~', path.resolve('packages'))
- // 把 packages 和 examples 加入编译, 因为新增的文件默认是不被 webpack 处理的
- config.module
- .rule('js')
- .include.add(/packages/).end()
- .include.add(/examples/).end()
- .use('babel')
- .loader('babel-loader')
- .tap(options => {
- // 修改它的选项...
- return options
- })
- }
- }
上面的注释应该都写的挺明了, 主要就是修改别名, 修改入口文件以及把新文件加入 webpack 编译这几个步骤. 然后我们再运行一下程序就可以跑得通了. 至于为什么这么配置, 或者怎么配置, 不了解的同学可以去 Vue Cli 官网看下, 上面写的是清清楚楚, 明明白白, 然而我也只是懂那么一两个配置而已, 还没学会 webpack 的套路, 因为常常是用的时候看一眼, 一阵子不用就又忘了, 没办法♀脑子不行.
编写组件
一个组件库没有组件怎么行呢, 所以我们要先写个 test 组件(你可以随便写, 这不重要).ok, 我们先在 packages 目录下新建一个 test 文件夹, 再在 test 文件夹下下面新建一个 src 文件夹, 在 src 文件夹下面新建一个 test.vue 组件, 大概长下面这样子:
- <!--test.vue-->
- <template>
- <div class="xr-test" @click="handleClick">{{ num }}</div>
- </template>
- <script>
- export default {
- name: 'XrTest', // 这个名字很重要, 它就是未来的标签名 < xr-test></xr-test>, 坑了我一下
- data () {
- return {
- num: 0
- }
- },
- methods: {
- handleClick () {
- this.num++
- }
- }
- }
- </script>
- <style lang="sCSS" scoped>
- .xr-test {
- width: 100px;
- height: 100px;
- line-height: 100px;
- border-radius: 50%;
- font-size: 30px;
- text-align: center;
- background: #24292e;
- color: white;
- }
- </style>
应该都能看懂吧, 不过多解释.这里主要强调一点, 就是 name 这个名字尤为重要, 我就在这个坑里呆了挺久. 首先它是必须要写的, 为啥呢, 你可以把它理解为 id, 具有唯一标识组件的作用, 将来我们可是要通过这个 name 来找到和判定这是什么组件, 所以你写的所有组件应该是不重名的; 其次这个 name 就是我们最终的标签名, 比如这里我们的 name 是 XrTest, 到时候我们写的标签就长这样 <xr-test></xr-test>, 就像 Element 一样, name 是 ElButton, 用的时候就是 <el-button></el-button>.
暴露组件
让我们在 packages/test 下面新建一个 index.JS 文件, 具体代码如下:
- // 为组件提供 install 方法, 供组件对外按需引入
- import XrTest from './src/test'
- XrTest.install = Vue => {
- Vue.component(XrTest.name, XrTest)
- }
- export default XrTest
这步的精髓就在于给组件扩展一个 install 方法, 至于为什么要扩展这个方法, 文章开头已经说到了, 是因为 Vue.use() 的需要, use 会默认调用 install 方法安装, 仅此而已. 接着我们在 packages 下面也新建一个 index.JS 文件, 注意和上面那个 index.JS 区别开, 上面那个是针对单个组件安装的, 这个是针对所有组件全局安装的, 先看代码:
- import XrTest from './test'
- // 所有组件列表
- const components = [
- XrTest
- ]
- // 定义 install 方法, 接收 Vue 作为参数
- const install = function (Vue) {
- // 判断是否安装, 安装过就不继续往下执行
- if (install.installed) return
- install.installed = true
- // 遍历注册所有组件
- components.map(component => Vue.component(component.name, component))
- // 下面这个写法也可以
- // components.map(component => Vue.use(component))
- }
- // 检测到 Vue 才执行, 毕竟我们是基于 Vue 的
- if (typeof Windows !== 'undefined' && Windows.Vue) {
- install(Windows.Vue)
- }
- export default {
- install,
- // 所有组件, 必须具有 install, 才能使用 Vue.use()
- ...components
- }
这步的主要作用就是统一导出所有组件及暴露 install 方法. 之前的 index.JS 只是安装单个组件, 而现在这个 index.JS 是循环安装所有组件, 具体使用就看你是不是要按需引用了. 这里给个目录结构方便大家观看:
因为这步挺重要的, 所以建议好好停下来理解消化一下...
当然你可能会问道, 为什么这样建目录? 还能什么原因, 因为 Element 是这样(如下图), 所以我们这样写, 仅此而已.
组件测试
ok, 组件写完了, 接下来我们就在 examples 下面测试一下, 看看能不能引用成功. 首先在 examples 下的 main.JS 中引入刚刚写好的包, 就像下面这样:
然后把 examples/views 下面的 Home.vue 里面的内容删了, 写入自己标签组件, 就像下面这样:
好了, 最后让我们运行一下项目 yarn serve, 看看效果, 嗯, 还凑合吧.
库模式打包
在 vue-cli3 中我们通过以下命令可以将一个单独的入口打包成一个库:
- // target: 默认为构建应用, 改为 lib 即可启用构建库模式
- // name: 输出文件名
- // dest: 输出目录, 默认为 dist, 这里我们改为 lib
- // entry: 入口文件路径
- vue-cli-service build --target lib --name lib [entry]
要注意的是在库模式中, 打包出来的库中是不包含 Vue 的. 然后我们修改一下 package.JSON 文件, 就像下面这样:
接着执行 NPM run lib 就能生成库啦, 看看左侧的目录是不是多了个 lib 文件夹, 那个就是我们要发布的东西.
补充下, lib 目录下面的 JS 之所以有好几种, 是因为有两种规范 (common 和 umd), 是否压缩(min) 和映射 (map) 的区别, 暂且知道有这么回事就行, 不用深究.
发布到 NPM
万事俱备, 只欠发布.
完善一下 README.md 文档, 这个随便写两句就好
修改一下 package.JSON 文件:
- {
- "name": "xr-ui",
- "version": "0.3.0",
- "description": "基于 vue-cli3 的 UI 组件库",
- "main": "lib/xr-ui.umd.min.js", // 这是 lib 目录下的其中一个
- "keywords": "xr-ui",
- "private": false,
- "license": "MIT"
- }
在根目录下新建一个 .npmignore 文件, 内容和 .gitignore 差不多:
- # 这是复制 .gitignore 里面的
- .DS_Store
- node_modules
- /dist
- # local env files
- .env.local
- .env.*.local
- # Log files
- NPM-debug.log*
- yarn-debug.log*
- yarn-error.log*
- # Editor directories and files
- .idea
- .vscode
- *.suo
- *.ntvs*
- *.njsproj
- *.sln
- *.sw*
- # 以下是新增的
- # 要忽略目录和指定文件
- examples/
- packages/
- public/
vue.config.JS
babel.config.JS
*.map
*.HTML
最后执行 NPM login 登入 NPM 账号, 再执行 NPM publish 发布即可, 就这么简单的两步就可以, 过一会在 NPM 上就能搜到了. 当然前提是你有个 NPM 账号, 没有的话去注册一个吧, 很 easy 的, 然后还要搜下你的 NPM 包名是否有人用, 有的话就换一个.
小试牛刀
终于, 历尽千辛万苦, 我们可以引用自己写的库拉, 想想就牛叉. 别激动, 让我们试验一下, 用 vue create new 另起一个新项目, 然后 NPM i xr-ui -S, 可以在 node_modules 里面看到我们的包大概长这样:
然后在 main.JS 引入:
- import Vue from "vue"
- import XrUI from 'xr-ui'
- import 'xr-ui/lib/xr-ui.css'
- Vue.use(XrUI)
这样我们就能在页面中引入组件啦, 哈哈哈哈, 贼开心, 喜上眉梢...
<xr-test></xr-test>
小结
想想如果你为团队维护一个组件库, 那是多么牛叉的事情. 当然, 好记性不如烂键盘, 看懂并不代表掌握, 只有自己体验过才是真的难忘,额.. 怎么突然抒情了起来.
最后还是强调一点吧, 水平不够的时候, 模仿是一种必备的技能, 就像我. 不过没关系, 这只是个过程, 当你写了几年的代码后, 总会有一些自己的想法, 然后也能慢慢沉淀出一些属于自己的东西, 我们只是站在巨人的肩膀上前行, 仅此而已, 回见
来源: https://juejin.im/post/5c95c61f6fb9a070c40acf65