组件库创建是一个复杂繁琐有很多规范化的事情,这样在学习组件库时会增加很多额外负担。本文采用最简单最基础的创建方式,演示出一个简单版本的UI库,能够让你在一小时内了解到UI组件库的运行原理和创建流程。理解了这些在带着问题查看有名的组件库源码,会让你事半功倍。
创建目录结构
# 创建 coolui目录
$ mkdir coolui
# 初始化npm环境,创建出package.json文件
$ npm init -y
设置package.json
{
"name": "coolui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
创建出来的package.json文件,并没有显示type,但是默认为commonjs
模式,项目中我们使用es开发,所以需要修改添加上"type": "module",
设置rollup配置
创建 rollup.config.js ,配置rollup打包所需要的参数。
import resolve from "rollup-plugin-node-resolve";
import vue from "rollup-plugin-vue";
import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import scss from "rollup-plugin-scss";
import json from "@rollup/plugin-json";
const formatName = "CoolUI";
const config = {
input: "./main.js",
output: [
{
file: "./lib/bundle.cjs.js",
format: "cjs",
name: formatName,
exports: "named",
},
{
file: "./lib/bundle.umd.js",
format: "umd",
name: formatName,
globals: {
vue: "Vue",
},
exports: "named",
},
{
file: "./lib/bundle.es.js",
format: "es",
name: formatName,
exports: "named",
},
{
file: "./lib/bundle.js",
format: "iife",
name: formatName,
globals: {
vue: "Vue",
},
exports: "named",
},
],
external: ["vue"],
plugins: [
json(),
resolve(),
vue({
css: true,
compileTemplate: true,
}),
babel({
exclude: "**/node_modules/**",
}),
commonjs(),
scss(),
],
};
export default config;
rollup打包出来的4种格式:
- cjs:commonjs模式,node后端使用
- es:esModel规范,包含新规范的es6,import和export
- umd:通用的模式,兼容了amd和commonjs
- iife:立即执行函数,可以把导出的对象挂到window全局对象上。
创建入口文件main.js
main.js文件用来注册所有组件,并导出所有组件。
package.json完整内容
{
"name": "coolui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"build": "rollup -c ./rollup.config.js"
},
"keywords": ["rollup", "vue3", "UI"],
"author": "shenshuai89@qq.com",
"license": "ISC",
"dependencies": {
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-scss": "^4.0.0",
"rollup-plugin-vue": "^6.0.0"
}
}
创建2个基础组件
在src下创建components目录
src
└── components
├── CoolButton
│ ├── index.js
│ ├── index.scss
│ └── index.vue
└── CoolInput
├── index.js
├── index.scss
└── index.vue
CoolButton组件
vue内容
<template>
<button class="btn">CoolButton</button>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: "CoolButton",
});
</script>
<style lang="scss" scoped></style>
js内容
import CoolButton from "./index.vue";
// 给组件设置install方法,这样在main中才可以使用use,或者在引入组件时,可以使用use调用组件
CoolButton.install = function (app) {
app.component(CoolButton.name, CoolButton);
};
export { CoolButton };
scss内容
.btn{
color: red;
}
CoolInput组件
vue内容
<template>
<div class="ipt">CoolInput</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: "CoolInput",
});
</script>
<style lang="scss" scoped></style>
js内容
import CoolInput from "./index.vue";
CoolInput.install = function (app) {
app.component(CoolInput.name, CoolInput);
};
export { CoolInput };
scss内容
.ipt{
font-size: 24px;
}
在main.js中注册组件
import { CoolButton } from "./src/components/CoolButton/index.js";
import { CoolInput } from "./src/components/CoolInput/index.js";
import { version } from "./package.json";
import "./src/components/CoolButton/index.scss";
import "./src/components/CoolInput/index.scss";
const components = [CoolButton, CoolInput];
// vue3中通过use注册组件
const install = function (app) {
components.forEach((component) => {
//这里可以使用use,也可以使用component。
// use内部调用了install方法,定义的所有component组件都要实现下这个install方法
app.use(component);
// app.component是通过name和组件直接定义的全局组件
// app.component(component.name, component);
});
};
export { CoolButton, CoolInput, install };
export default { version, install };
安装需要的依赖:
yarn add @babel/core @rollup/plugin-babel @rollup/plugin-commonjs@21.0.0 @rollup/plugin-json
yarn add vue node-sass rollup-plugin-node-resolve rollup-plugin-scss rollup-plugin-vue
打包编译出库js文件
执行打包命令 yarn run build
,可以看到输出的目录lib生成了4个打包出来的文件。
├── assets
│ └── output-96704ed1.css
├── bundle.cjs.js
├── bundle.es.js
├── bundle.js
└── bundle.umd.js
在脚手架项目中安装使用
使用vite创建出vue项目,在mian中引入CoolUI。
import { createApp } from 'vue'
import App from './App.vue'
import CoolUI from "./lib/bundle.es.js";
createApp(App).use(CoolUI).mount('#app')
接下来就可以在全局中使用CoolUI中的组件。
<template>
<div>
<Cool-Button></Cool-Button>
<Cool-Input></Cool-Input>
</div>
</template>
通过src路径引入使用
在html通过srcipt标签的src链接引入。引入的格式应为umd或iife。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>test ui</title>
<script src="https://unpkg.com/vue@3.2.47/dist/vue.global.js"></script>
<script src="./lib/bundle.js"></script>
</head>
<body>
<div id="app">
<Cool-Button></Cool-Button>
<Cool-Input></Cool-Input>
</div>
<script>
const { createApp } = Vue;
const app = createApp().use(CoolUI).mount("#app");
</script>
</body>
</html>
打包编译样式文件
到目前为止,打包出来的组件并没有看到样式生效,在上面示例中并没有引入css,也没有单独处理css。接下来就出来样式文件。样式文件可以单独打包出来一个库,给全局组件使用,也可以每个组件打包出来一个样式文件,给单独使用组件时添加样式。
在src下创建theme目录
初始化环境 npm init -y
,新增出package.json
创建gulpfile.js文件,执行打包
{
"name": "theme",
"version": "1.0.0",
"description": "",
"main": "gulpfile.js",
"scripts": {
"build": "gulp build"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"gulp": "^4.0.2",
"gulp-autoprefixer": "^8.0.0",
"gulp-cssmin": "^0.2.0",
"gulp-sass": "^5.1.0",
"sass": "^1.58.0"
}
}
const { series, src, dest } = require("gulp");
const sass = require("gulp-sass")(require('sass'));
const autoprefixer = require("gulp-autoprefixer");
const cssmin = require("gulp-cssmin");
//编译scss文件,并拷贝到指定目录下
function compile() {
return src("./src/components/*.scss")
.pipe(sass.sync())
.pipe(
autoprefixer({
overrideBrowserslist: ["ie > 9", "last 2 versions"],
cascade: false,
})
)
.pipe(cssmin())
.pipe(dest("./lib/style"));
}
// 拷贝字体文件
function copyfont() {
return src("./src/fonts/**").pipe(cssmin()).pipe(dest("./lib/fonts"));
}
exports.build = series(compile, copyfont);
安装依赖:yarn add gulp gulp-autoprefixer gulp-cssmin gulp-sass sass
然后执行打包命令 yarn run build
,实际上执行的是gulp build
打包结果lib目录
└── style
├── button.css
├── index.css
└── input.css
可以将index.css引入到项目中
在vite项目中的main.js
// 引入样式
import "./lib/style/index.css";
在html中通过link引入
<link rel="stylesheet" href="./src/lib/style/index.css">
创建函数调用全局组件 CoolMsg
在创建组件库时,有一类组件是通过js全局引入的,比如$message,并将组件挂载到body上。该类组件主要是通过 createVNode 和 render 方法创建,然后添加并渲染到body
主要是要 createVNode 和 render 方法
新建CoolMsg目录,创建index.js和index.vue
<template>
<div class="msg" v-if="visible">
message
<button @click="close">close</button>
</div>
</template>
<script>
export default {
name: "CoolMsg",
};
</script>
<script setup>
import { onMounted, ref } from "vue";
const visible = ref(false);
onMounted(() => {
visible.value = true;
});
function close() {
visible.value = false;
}
defineExpose({
visible,
close,
});
</script>
<style lang="scss" scoped></style>
import { render, createVNode } from "vue";
import CoolMsgCom from "./index.vue";
// 消息实例数组
const instances = [];
const removeContainer = (ele) => {
const index = instances.findIndex((item) => item === ele);
if (~index) {
instances.splice(index, 1);
}
};
const addContainer = (ele) => {
instances.push(ele);
};
const createMount = (opts) => {
const { duration } = opts;
// 创建一个 div 容器
const container = document.createElement("div");
// 创建 CoolMsgCom 实例,createVNode 的性能比 h 更好
const vm = createVNode(CoolMsgCom, opts);
// 把实例 render 到容器上
render(vm, container);
addContainer(container);
// 把容器渲染到 body 上
document.body.appendChild(container);
const destory = () => {
const timer = setTimeout(() => {
render(null, container);
removeContainer(container);
document.body.removeChild(container);
clearTimeout(timer);
}, 500); // 500 为动画结束时间,根据情况修改
};
const timer = setTimeout(() => {
destory();
clearTimeout(timer);
}, 2000);
return { destory };
};
function CoolMsg(options) {
if (typeof options === "string") {
// eslint-disable-next-line
options = {
message: options || "",
};
} else {
// eslint-disable-next-line
options = {
...options,
};
}
return createMount(options);
}
CoolMsg.install = function (app) {
app.component("CoolMsg", CoolMsgCom);
};
export { CoolMsg };
在main.js中引入CoolMsg
import { CoolButton } from "./src/components/CoolButton/index.js";
import { CoolInput } from "./src/components/CoolInput/index.js";
import { CoolMsg } from "./src/components/CoolMsg/index.js";
import { version } from "./package.json";
const components = [CoolButton, CoolInput, CoolMsg];
// vue3中通过use注册组件
const install = function (app) {
components.forEach((component) => {
//这里可以使用use,也可以使用component。
// use内部调用了install方法,定义的所有component组件都要实现下这个install方法
app.use(component);
// app.component是通过name和组件直接定义的全局组件
// app.component(component.name, component);
applyOptions(app);
});
};
// 用来挂载 CoolMsg 这样用 js 来调用的组件, this.$CoolMsg()
const applyOptions = (app) => {
app.config.globalProperties.$CoolMsg = CoolMsg;
};
export { CoolButton, CoolInput, CoolMsg, install };
export default { version, install };
创建函数组件,vue2和vue3区别
- vue2中通过vue.extend进行创建
- vue3中通过createVNode,然后render到container挂载点上。