【手把手带你搓组件库】从零开始实现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.mzph.cn/pingmian/15711.shtml

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

相关文章

面向可复用性和可维护性的设计模式 课程学习总结

什么是设计模式 设计模式&#xff1a;在软件设计中给定上下文中常见问题的通用的、可重用的解决方案。 设计模式分类 1. 创建型模式——Creational patterns 关注对象创建的过程 1.1 工厂方法模式 定义用于创建对象的接口&#xff0c;但让子类决定要实例化哪个类。工厂方…

4.Redis之Redis的通用命令

0.Redis 实战操作 通过 redis-cli 客户端和 redis 服务器交互 涉及到很多的 redis 的命令 【redis 的命令非常非常多!!! 1.掌握常用命令(多操作多练习) 2.学会使用 redis 的文档-> 阅读文档, 是程序猿的基操!! redis 的命令非常非常多!!! 1.掌握常用命令(多操作多练习…

部署CNI网络组件+k8s多master集群部署+负载均衡

一、环境部署 主机服务 192.168.91.5 K8S集群master01192.168.91.8 K8S集群master02192.168.91.6K8S集群node01192.168.91.7K8S集群node02192.168.91.9 负载均衡nginxkeepalive01&#xff08;master&#xff09;192.168.91.10 负载均衡nginxkeepalive02&#xff08;backup&am…

编程实战:自己编写HTTP服务器(系列3:处理框架)

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 系列入口&#xff1a;编程实战…

TypeScript学习日志-第三十二天(infer关键字)

infer关键字 一、作用与使用 infer 的作用就是推导泛型参数&#xff0c;infer 声明只能出现在 extends 子语句中&#xff0c;使用如下&#xff1a; 可以看出 已经推导出类型是 User 了 二、协变 infer 的 协变会返回联合类型&#xff0c;如图&#xff1a; 三、逆变 infer…

pikachu靶场中的CSRF、SSRF通关

目录 1、CSRF介绍 2、CSRF&#xff08;get&#xff09; 3、CSRF&#xff08;post&#xff09; 4、CSRF Token 5、SSRF介绍 6、SSRF&#xff08;curl&#xff09; 7、SSRF&#xff08;file_get-content&#xff09; 8、CSRF与SSRF的区别 最近在学习CSRF、SSRF漏洞&#…

Boyer-Moore投票算法

摩尔投票法&#xff0c;又称为博耶-摩尔多数投票算法&#xff0c;是一种用于在一组数据中寻找多数元素(出现次数超过一半的元素)的算法。该算法的效率非常高&#xff0c;时间复杂度为O(n)&#xff0c;空间复杂度为O(1)&#xff0c;适合处理大数据量的情况。 步骤 首先定义两个…

飞机大战游戏实现揭秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、游戏概览与核心玩法 二、游戏模块详解 1. 游戏主循环模块 2. 创建初始化模块 三、关…

美军配备人工智能武器的机器狗引发伦理争议

近日&#xff0c;美国海军陆战队特种作战司令部&#xff08;MARSOC&#xff09;的一项测试引发了全球关注&#xff1a;他们正在评估一种由“幽灵机器人”公司研发的最新型机器狗&#xff0c;并考虑为其配备“玛瑙工业”公司提供的武器系统。这一消息犹如在平静的湖面投下一颗石…

Python 之 日志巡检脚本

脚本说明 使用Paramiko库进行SSH连接的自动化脚本&#xff0c;用于检查、配置和排除设备故障。说明如下&#xff1a; 导入所需的库&#xff1a;paramiko、json、logging和concurrent.futures。定义配置文件路径&#xff08;devices.json&#xff09;和日志文件路径&#xff0…

阿里巴巴最新研究突破:自我演化大模型,打破性能天花板

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读AI论文解读 原创作者 | 柏企 引言&#xff1a;自我进化的新篇章 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的发展正迎来一场革命性的变革。传统的训练模式依赖…

006、API_单线程

Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库 服务&#xff0c;本节首先通过多个客户端命令调用的例子说明Redis单线程命令处理 机制&#xff0c;接着分析Redis单线程模型为什么性能如此之高&#xff0c;最终给出为什么理 解单线程模型是使用和运维Redis的…

WordPress国外超人气主题Vikinger汉化版

WordPress国外超人气主题Vikinger汉化版 前言效果图安装教程领取主题下期更新预报 前言 我们在上一个教程已经学过如何安装WordPress&#xff0c;所以现在不用多说。 效果图 安装教程 下载后先本地解压&#xff0c;找到vikinger.zip文件&#xff0c;上传安装并启用主题。 访…

EasyMR 基于国产化信创的适配实践技术详解

国产化信创&#xff0c;即采用国产信息技术产品和服务&#xff0c;构建自主可控的信息技术体系。近年来&#xff0c;随着国家对网络安全和信息安全的重视程度不断提高&#xff0c;国产化信创已经成为国家战略的重要组成部分&#xff0c;并呈现出以下大趋势&#xff1a; ● 政策…

【C语言】C语言-学生选修课程系统(源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

分享几张漂亮的linux kde主题

分享几张漂亮的linux kde主题&#xff1a;在系统设置的全局主题内下载。

开源大模型与闭源大模型

概述 开源大模型和闭源大模型是两种常见的大模型类型&#xff0c;它们在以下方面存在差异&#xff1a; 开放性&#xff1a; 开源大模型&#xff1a;代码和模型结构是公开可用的&#xff0c;任何人都可以访问、修改和使用。闭源大模型&#xff1a;模型的代码和结构是私有的&…

求斐波那契数列第n项的值

本期介绍&#x1f356; 主要介绍&#xff1a;什么是斐波那契数列&#xff0c;递归实现求斐波那契数列第n项值&#xff0c;递归法为什么不适合求斐波那契数&#xff0c;用迭代法实现求斐波那契数列的值&#x1f440;。 文章目录 1. 斐波那契数列是什么&#xff1f;2. 题目2. 递归…

iOS17闪退问题 *** Assertion failure in void _UIGraphicsBeginImageContextWithOptions(CGSize, BOOL, CGFloa

ios升级17以后运行闪退。报错日志为*** Assertion failure in void _UIGraphicsBeginImageContextWithOptions(CGSize, BOOL, CGFloat, BOOL)(), UIGraphics.m:410 根据相关断点跟踪&#xff0c;具体报错位置 查看相关api发现iOS17api已经被替代。 替代方式为将UIGraphicsBegin…

9.Docker网络

文章目录 1、Docker网络简介2、常用基本命令3、网络模式对比举例3.1、bridge模式3.2、host模式3.3、none模式3.4、container模式3.5、自定义网络 1、Docker网络简介 作用&#xff1a; 容器间的互联和通信以及端口映射容器IP变动时候可以通过服务名直接进行网络通信而不受到影…