【手把手带你搓组件库】从零开始实现Element Plus

从零开始实现Element Plus

  • 前言
  • 亮点
  • 项目搭建
    • 1、创建项目
      • 初始化
      • monorepo
      • 创建 .gitignore
      • 目录结构
      • 安装基础依赖
      • 配置文件
      • 创建各个分包入口
        • utils
        • components
        • core
        • play
        • theme
    • 2、创建VitePress文档
    • 3、部署到Github Actions
        • 生成 GH_TOKEN
        • GitHub Page 演示
    • 4、总结

前言

在本文中,将手把手带你从零开始实现一个类似于Element Plus 的组件库。Element Plus 是一个非常流行的Vue UI 组件库,我们将尝试实现一些常见的组件,如基础组件、反馈组件、表单组件等。让我们开始吧!

亮点

  • Vite+Vitest+Vitepress 工具链 (项目构建+测试+项目文档)
  • monorepo 分包管理
  • GitHub actions 实现 CI/CD 自动化部署
  • 大模型辅助:使用大模型辅助完成需求分析,设计思路,快速实现组件,提升开发效率
  • 发布开箱即用的npm包

项目搭建

1、创建项目

初始化

mkdir Wannaer-element
cd Wannaer-element
git init
pnpm init

在这里插入图片描述

monorepo

monorepo ,那就先创建一个 pnpm-workspace.yaml 文件。

mkdir packages
echo -e 'packages:\n  - "packages/*"' > pnpm-workspace.yaml
// 在Windows系统中,echo命令默认不支持像在Linux系统中那样使用"-e"参数来表示换行符
// 创建完成后,手动操作换行
echo 'packages:\n  - "packages/*"' > pnpm-workspace.yaml// 如果出现 pnpm: null byte is not allowed in input (1:4) 可能是有隐藏字符问题
packages:- "packages/*"

在这里插入图片描述

创建 .gitignore

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*node_modules
coverage
dist
dist-ssr
*.local/cyperss/videos/
/cypress/srceenshots/.vitepress/dist
.vitepress/cache# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

目录结构

为了目录扁平,就只创建 packages 这么一个 pnpm 工作区,下面大概说一下这个项目计划的分包结构

- components # 组件目录
- core # npm 包入口
- docs # 文档目录
- hooks # 组合式API hooks 目录
- play # 组件开发实验室
- theme # 主题目录
- utils # 工具函数目录
// 创建这些目录
cd packages
// 分别初始化这些目录 在 packages 目录下创建 init.shell 内容如下
for i in components core docs hooks theme utils; domkdir $icd $ipnpm initcd ..
done
// 执行后删除 init.shell

在这里插入图片描述
这波 play 目录先留着,我们用 vite 来创建一个 vue 开发项目

pnpm create vite play --template vue-ts

创建完成后分别到 各个分包目录中修改 package.json 中的 name,防止重名

- core # npm 包入口"name": "Wannaer-element",
- components # 组件目录"name": "@Wannaer-element/components",
- docs # 文档目录"name": "@Wannaer-element/docs",
- hooks # 组合式API hooks 目录"name": "@Wannaer-element/hooks",
- play # 组件开发实验室"name": "@Wannaer-element/play",
- theme # 主题目录"name": "@Wannaer-element/theme",
- utils # 工具函数目录"name": "@Wannaer-element/utils",
- 根目录“name”: "@Wannaer-element/workspace"

安装基础依赖

在根目录 安装
-Dw表示在package.json文件中配置的scripts中运行特定的脚本命令,xxx为脚本命令的名称。
-w表示在指定的工作区目录中运行特定的脚本命令,xxx为脚本命令的名称。// 开发依赖
pnpm add -Dw typescript@^5.2.2 vite@^5.1.4 vitest@^1.4.0 vue-tsc@^1.8.27 postcss-color-mix@^1.1.0 postcss-each@^1.1.0 postcss-each-variables@^0.3.0 postcss-for@^2.1.1 postcss-nested@^6.0.1 @types/node@^20.11.20 @types/lodash-es@^4.17.12 @vitejs/plugin-vue@^5.0.4 @vitejs/plugin-vue-jsx@^3.1.0 @vue/tsconfig@^0.5.1// 非开发依赖
pnpm add -w lodash-es@^4.17.21 vue@^3.4.19

在 根目录 package.json 中添加如下内容 添加一下子包的依赖

{"dependencies": {"Wannaer-element": "workspace:*","@Wannaer-element/hooks": "workspace:*","@Wannaer-element/utils": "workspace:*","@Wannaer-element/theme": "workspace:*"}
}
  • components
pnpm add -D @vue/test-utils@^2.4.5 @vitest/coverage-v8@^1.4.0 jsdom@^24.0.0 --filter @Wannaer-element/components
pnpm add @popperjs/core@^2.11.8 async-validator@^4.2.5 --filter @Wannaer-element/components
  • core
// 在 core/package.json 中添加如下内容
{"dependencies": {"@Wannaer-element/components": "workspace:*"}
}
  • docs
pnpm add -D vitepress@1.0.0-rc.44 --filter @Wannaer-element/docs
  • play
    将 play/package.json 中冗余部分删除, 并且删除掉tsconfig.jsontsconfig.node.json
{"name": "@Wannaer-element/play","private": true,"version": "0.0.0","type": "module","scripts": {"dev": "vite","build": "vue-tsc && vite build","preview": "vite preview"},"devDependencies": {"@vitejs/plugin-vue": "^5.0.4"}
}

配置文件

在根目录创建一些必要额配置文件,比如刚才删除play中的ts配置,我们在根目录配置

  • tsconfig.json
{"extends": "@vue/tsconfig/tsconfig.dom.json","compilerOptions": {"target": "ES2020","useDefineForClassFields": true,"module": "ESNext","lib": ["ES2020", "DOM", "DOM.Iterable"],"skipLibCheck": true,/* Bundler mode */"moduleResolution": "bundler","allowImportingTsExtensions": true,"resolveJsonModule": true,"isolatedModules": true,"noEmit": true,"jsx": "preserve","jsxImportSource": "vue",/* Linting */"strict": true,"noUnusedLocals": true,"noUnusedParameters": true,"noFallthroughCasesInSwitch": true},"include": ["packages/**/*.ts", "packages/**/*.tsx", "packages/**/*.vue"]
}
  • tsconfig.node.json
{"extends": "@tsconfig/node18/tsconfig.json","include": ["packages/**/**.config.ts"],"compilerOptions": {"composite": true,"module": "ESNext","moduleResolution": "Bundler","types": ["node"]}
}
  • postcss.config.cjs
/* eslint-env node */
module.exports = {plugins: [require("postcss-nested"),require("postcss-each-variables"),require("postcss-each")({plugins: {beforeEach: [require("postcss-for"), require("postcss-color-mix")],},}),],
};

配置完成后,重新安装一下依赖 pnpm install 执行之前更新的部分操作

创建各个分包入口

utils

在utils文件夹 新建一个文件 install.ts 用于 vue plugin 安装的一系列操作

import type { App, Plugin } from "vue";
import { each } from "lodash-es";type SFCWithInstall<T> = T & Plugin;export function makeInstaller(components: Plugin[]) {const install = (app: App) =>each(components, (c) => {app.use(c);});return install;
}export const withInstall = <T>(component: T) => {(component as SFCWithInstall<T>).install = (app: App) => {const name = (component as any)?.name || "UnnamedComponent";app.component(name, component as SFCWithInstall<T>);};return component as SFCWithInstall<T>;
};

创建一个utils入口 index.ts 文件 用于导出utils所有方法

export * from "./install";

在这里插入图片描述

components

创建 index.ts 以及第一个基础组件 Button 组件目录

// index.ts
export * from './Button'
//  Button 目录 Button.vue
<template><button style="color: red">this is a button</button>
</template><script setup lang="ts">
defineOptions({name: "WButton",
});
</script><style scoped></style>
// Button 目录 index.ts
import Button from "./Button.vue";
import { withInstall } from "@Wannaer-element/utils";export const WButton = withInstall(Button);

在这里插入图片描述

core

创建 index.ts 、components.ts

// components.tsimport { ErButton } from "@toy-element/components";
import type { Plugin } from "vue";export default [ErButton] as Plugin[];
import { makeInstaller } from "@toy-element/utils";
import components from "./components";const installer = makeInstaller(components);export * from "@toy-element/components";
export default installer;

在这里插入图片描述

play

在main.ts 中 引入了我们刚刚写好的"Wannaer-element"的自定义元素库,并在App.vue中使用。
通过createApp(App).use(WElement).mount(“#app”)这行代码,将"Wannaer-element"库应用到了Vue实例中,并挂载到了id为"app"的DOM元素上。
在这里插入图片描述
在根目录的package.json中配置

  "scripts": {"dev": "pnpm --filter @Wannaer-element/play dev","test": "echo \"Error: no test specified\" && exit 1"}

它定义了一个名为"dev"的脚本命令。在这个命令中,使用了pnpm工具,并通过"–filter @Wannaer-element/play"参数指定了要过滤的包,然后执行"dev"命令。这段代码的作用是在开发过程中使用pnpm工具来过滤特定的包并执行相应的开发命令。

配置完成后运行 pnpm dev 可以查看到我们刚刚封装好的 Button 虽然很简陋 接下来我们进行样式的修改,让他变得更加美观
在这里插入图片描述

theme

创建 index.css 、reset.css 在 theme/index.css 中导入 reset.css

/** index.css */
@import "./reset.css";
/** reset.css */
body {font-family: var(--wan-font-family);font-weight: 400;font-size: var(--wan-font-size-base);line-height: calc(var(--wan-font-size-base) * 1.2);color: var(--wan-text-color-primary);-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;-webkit-tap-highlight-color: transparent;
}a {color: var(--wan-color-primary);text-decoration: none;&:hovwan,&:focus {color: var(--wan-color-primary-light-3);}&:active {color: var(--wan-color-primary-dark-2);}
}h1,
h2,
h3,
h4,
h5,
h6 {color: var(--wan-text-color-regular);font-weight: inhwanit;&:first-child {margin-top: 0;}&:last-child {margin-bottom: 0;}
}h1 {font-size: calc(var(--wan-font-size-base) + 6px);
}h2 {font-size: calc(var(--wan-font-size-base) + 4px);
}h3 {font-size: calc(var(--wan-font-size-base) + 2px);
}h4,
h5,
h6,
p {font-size: inhwanit;
}p {line-height: 1.8;&:first-child {margin-top: 0;}&:last-child {margin-bottom: 0;}
}sup,
sub {font-size: calc(var(--wan-font-size-base) - 1px);
}small {font-size: calc(var(--wan-font-size-base) - 2px);
}hr {margin-top: 20px;margin-bottom: 20px;bordwan: 0;bordwan-top: 1px solid var(--wan-bordwan-color-lightwan);
}

最后改 package.json 中 入口为 index.css 在 core/index.ts 中导出我们的 theme
在这里插入图片描述

2、创建VitePress文档

可以直接参考官方文档

npx vitepress init

在这里插入图片描述

// 运行查看效果
pnpm docs:dev

在这里插入图片描述
我们改一下package.json指令 配置后统一可以从根目录运行

// docs目录 package.json"scripts": {"dev": "vitepress dev","build": "vitepress build","preview": "vitepress preview"},
// 根目录 package.js"scripts": {"dev": "pnpm --filter @Wannaer-element/play dev","docs:dev": "pnpm --filter @Wannaer-element/docs dev","docs:build": "pnpm --filter @Wannaer-element/docs build","docs:preview": "pnpm --filter @Wannaer-element/docs preview","test": "echo \"Error: no test specified\" && exit 1"},

接下来我们需要将 VitePress文档部署到 GitHub Actions
所以需要配置一下 docs目录下vitepress => config.mts 添加一个 base: “/wan-element”,解决部署后样式丢失问题

import { defineConfig } from "vitepress";// https://vitepress.dev/reference/site-config
export default defineConfig({title: "Wan-Element",description: "高仿 ElementPlus 组件库",base: "/wan-element",themeConfig: {// https://vitepress.dev/reference/default-theme-confignav: [{ text: "Home", link: "/" },{ text: "Examples", link: "/markdown-examples" },],sidebar: [{text: "Examples",items: [{ text: "Markdown Examples", link: "/markdown-examples" },{ text: "Runtime API Examples", link: "/api-examples" },],},],socialLinks: [{ icon: "github", link: "https://github.com/vuejs/vitepress" },],},
});

3、部署到Github Actions

创建一个 .github/workflows/deploy.yml 文件,内容如下

name: deployon:push:branches:- masterjobs:test:name: Run Lint and Testruns-on: ubuntu-lateststeps:- name: Checkout repouses: actions/checkout@v3- name: Setup Nodeuses: actions/setup-node@v3- name: Install pnpm run: npm install -g pnpm- name: Install dependenciesrun: pnpm install --frozen-lockfile- name: Run testsrun: npm run testbuild:name: Build docsruns-on: ubuntu-latestneeds: teststeps:- name: Checkout repouses: actions/checkout@v3- name: Setup Nodeuses: actions/setup-node@v3- name: Install pnpmrun: npm install -g pnpm- name: Install dependenciesrun: pnpm install --frozen-lockfile- name: Build docsrun: npm run docs:build- name: Upload docsuses: actions/upload-artifact@v3with:name: docspath: ./packages/docs/.vitepress/distdeploy:name: Deploy to GitHub Pagesruns-on: ubuntu-latestneeds: buildsteps:- name: Download docsuses: actions/download-artifact@v3with:name: docs- name: Deploy to GitHub Pagesuses: peaceiris/actions-gh-pages@v3with:github_token: ${{ secrets.GH_TOKEN }}publish_dir: .

secrets.GH_TOKEN 需要到Github 上面去生成

接下来去 github 创建一个仓库
在这里插入图片描述
复制仓库地址

https://github.com/Manba0/wan-element.git
git remote add origin https://github.com/Manba0/wan-element.gitgit add .git commit -m ":data: first commit"
生成 GH_TOKEN

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最后将刚刚提交的代码 push到Github仓库

git push origin master

如果 push 出现一下报错
fatal: unable to access ‘https://github.com/XXXX/XXXX.git/’: Failed to connect to github.com port 443 after 21067 ms: Couldn’t connect to server

有可能你的gitbub之前设置过代理,只需分别执行如下代码即可:

git config --global --unset http.proxy
git config --global --unset https.proxy

提交成功后 发现 Settings 中Page 没有找到访问的链接,我们查看 Actions 发现 Run tests 没有通过, 因为我们根目录下 package.json 中的 test 指令 "test": "echo \"Error: no test specified\" && exit 1",修改成 "test": "echo 'todo'"重新提交
在这里插入图片描述
在这里插入图片描述

这样就是成功了 我们直接去看Settings中的page https://manba0.github.io/wan-element/
在这里插入图片描述
在这里插入图片描述

GitHub Page 演示

在这里插入图片描述

4、总结

到此我们就已经全流程跑通了 接下来就是完善组件内容了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/331766.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《计算机网络微课堂》3-11 虚拟局域网 VLAN

本节课我们介绍虚拟局域网 VLAN 的基本概念。 ‍ 3.11.1 虚拟局域网 VLAN 概述 在之前课程中我们已经介绍过了以太网交换机自学习和转发帧的流程&#xff0c;‍‍以及为避免网络环路而产生的生成树协议。 以太网交换机工作在数据链路层&#xff0c;‍‍也包括物理层&#xf…

ftp是什么,ftp能做什么,ftp有什么用 -----ftp介绍

大家好&#xff0c;我是风屿&#xff0c;今天开始我会给大家介绍一些关于网络方面的配置以及介绍等等&#xff0c;今天是ftp FTP中文名字叫做文件传输协议&#xff0c;英文名字叫做File Transfer Protocol&#xff08;简称为ftp&#xff09; FTP 是因特网网络上历史最悠久的网…

Textual for Mac:轻量级IRC客户端

在寻找一款高效、轻量级的IRC客户端时&#xff0c;Textual for Mac无疑是你的不二之选。它集成了众多现代技术&#xff0c;如本机IPv6、最新的IRCv3规范&#xff0c;以及客户端证书身份验证&#xff0c;让你的聊天体验更加顺畅和安全。 Textual for Mac v7.2.2免激活版下载 Tex…

4、PHP的xml注入漏洞(xxe)

青少年ctf&#xff1a;PHP的XXE 1、打开网页是一个PHP版本页面 2、CTRLf搜索xml&#xff0c;发现2.8.0版本&#xff0c;含有xml漏洞 3、bp抓包 4、使用代码出发bug GET /simplexml_load_string.php HTTP/1.1 补充&#xff1a; <?xml version"1.0" encoding&quo…

【C++】深入解析C++智能指针:从auto_ptr到unique_ptr与shared_ptr

文章目录 前言&#xff1a;1. 智能指针的使用及原理2. C 98 标准库中的 auto_ptr:3. C 11 中的智能指针循环引用&#xff1a;shared_ptr 定制删除器 4. 内存泄漏总结&#xff1a; 前言&#xff1a; 随着C语言的发展&#xff0c;智能指针作为现代C编程中管理动态分配内存的一种…

Win32 API

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 : C语言 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 一.Win32 API 1.Win32 API介绍 Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外&#xff0c;它同时也是…

python中的线程并行

文章目录 1. 单线程2. 线程池ThreadPoolExecutor 1. 单线程 现在有1154张图片需要顺时针旋转后保存到本地&#xff0c;一般使用循环1154次处理&#xff0c;具体代码如下所示&#xff0c;img_paths中存储1154个图片路径&#xff0c;该代码段耗时约用97ms。 t1time.time() for …

Windows安装VMware(Broadcom)

1.安装前提 1.检查BIOS中是否开启了虚拟化技术。1.1 打开任务管理器&#xff0c;查看性能&#xff0c;CPU部分&#xff0c;虚拟化处于“已启用”状态。1.2 如果没有开启&#xff0c;则需要进入BIOS系统&#xff0c;将 Intel Virtualization Technology改为Enalble。2.下载VMwa…

ROS2入门21讲__第19讲__Rviz:三维可视化显示平台

目录 前言 Rviz三维可视化平台 Rviz介绍 运行方法 彩色相机仿真与可视化 仿真插件配置 运行仿真环境 图像数据可视化 三维相机仿真与可视化 仿真插件配置 运行仿真环境 点云数据可视化 激光雷达仿真与可视化 仿真插件配置 运行仿真环境 点云数据可视化 Rviz v…

【HCIP学习】RSTP和MSTP

一、RSTP&#xff08;Rapid Spanning Tree Protocol&#xff0c;快速生成树&#xff09; 1、背景&#xff1a;RSTP从STP发展而来&#xff0c;具备STP的所有功能&#xff0c;可以兼容stp运行 2、RSTP与STP不同点 &#xff08;1&#xff09;减少端口状态 STP:disabled\blockin…

【Python搞定车载自动化测试】——Python实现CAN总线Bootloader刷写(含Python源码)

系列文章目录 【Python搞定车载自动化测试】系列文章目录汇总 文章目录 系列文章目录&#x1f4af;&#x1f4af;&#x1f4af; 前言&#x1f4af;&#x1f4af;&#x1f4af;一、环境搭建1.软件环境2.硬件环境 二、目录结构三、源码展示1.诊断基础函数方法2.诊断业务函数方法…

《最新出炉》系列入门篇-Python+Playwright自动化测试-40-录制生成脚本

宏哥微信粉丝群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 各种自动化框架都会有脚本录制功能&#xff0c; playwright这么牛叉当然也不例外。很早之前的selenium、Jmeter工具&#xff0c;发展到每种浏览器都有对应的录制插件。今天我们…

python机器学习及深度学习在空间模拟与时间预测

原文链接https://mp.weixin.qq.com/s?__bizMzUyNzczMTI4Mg&mid2247628504&idx2&sn6fe3aeb9f63203cfe941a6bb63b49b85&chksmfa77a9e5cd0020f3aa4f01887e75b15096a182c2b5b42c1044787aa285c650f1469a0ef28aec&token2124656491&langzh_CN&scene21#we…

C++语法|虚函数与多态详细讲解(六)|如何解释多态?(面试向)

系列汇总讲解&#xff0c;请移步&#xff1a; C语法&#xff5c;虚函数与多态详细讲解系列&#xff08;包含多重继承内容&#xff09; 多态分为了两种&#xff0c;一种是静态的多态&#xff0c;一种是动态的多态。 静态&#xff08;编译时期&#xff09;的多态 函数重载 boo…

基于51单片机温度报警系统—数码管显示

基于51单片机温度报警系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.DS18B20采集温度&#xff0c;数码管显示温度&#xff1b; 2.温度测量范围&#xff1a;0-99度&#xff1b; 3.当温度低于…

NDIS小端口驱动开发(三)

微型端口驱动程序处理来自过度驱动程序的发送请求&#xff0c;并发出接收指示。 在单个函数调用中&#xff0c;NDIS 微型端口驱动程序可以指示具有多个接收 NET_BUFFER_LIST 结构的链接列表。 微型端口驱动程序可以处理对每个NET_BUFFER_LIST结构上具有多个 NET_BUFFER 结构的多…

阻塞信号集和未决信号集_代码实现

1. 程序验证内容 将编号为0,1,2添加到阻塞信号集中&#xff0c;i<信号编号时&#xff0c;发出信号&#xff0c;观察未决信号集状态 当解除阻塞后&#xff0c;原先的信号是否执行&#xff0c;执行顺序是什么 2. 代码实现 #include <unistd.h> #include <stdlib.h…

【全开源】海报在线制作系统源码(ThinkPHP+FastAdmin+UniApp)

打造个性化创意海报的利器 引言 在数字化时代&#xff0c;海报作为一种重要的宣传媒介&#xff0c;其设计质量和效率直接影响着宣传效果。为了满足广大用户对于个性化、高效制作海报的需求&#xff0c;海报在线制作系统源码应运而生。本文将详细介绍海报在线制作系统源码的特…

Spring AI实战之二:Chat API基础知识大串讲(重要)

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos Spring AI实战全系列链接 Spring AI实战之一&#xff1a;快速体验(OpenAI)Spring AI实战之二&#xff1a;Chat API基础知识大串讲(重要)SpringAIOllama三部曲…

JavaFX学习教程二

一、JavaFX 体系结构 JavaFX 场景图(Scene Graph)是构建 JavaFX 应用程序的起点&#xff0c;一种树状数据结构&#xff0c;用于排列&#xff08;和分组&#xff09;图形对象&#xff0c;以便于逻辑表示。 stage:舞台&#xff0c;操作系统窗口的 JavaFX 表示&#xff0c;是所有…