vue3+ts+vite組件庫搭建

記錄一次UI組件庫搭建過程擦酌,涉及到的技術很多俱诸,也遇到很多問題,大致工程參考Element-plus倉庫搭建仑氛。其中關鍵技術點和遇到的問題乙埃,大量借鑒各社區(qū)大佬文章及解決方案,最終得以實現(xiàn)锯岖,站在巨人肩膀上介袜,致敬,學習出吹。

下面內(nèi)容遇伞,你可以跳過直接去github查看源碼,如果對你有幫助捶牢,希望start一下 謝謝鸠珠!github:github地址

搭建組件庫-環(huán)境包管理

我們使用pnpm當做包管理工具,用pnpm workspace來實現(xiàn)monorepo秋麸。

當使用 npm 或 Yarn 時渐排,如果你有 100 個項目使用了某個依賴(dependency),就會有 100 份該依賴的副本保存在硬盤上灸蟆。 ?而在使用 pnpm 時驯耻,依賴會被存儲在內(nèi)容可尋址的存儲中,所以:

  1. 如果你用到了某依賴項的不同版本炒考,只會將不同版本間有差異的文件添加到倉庫寥茫。 例如藕赞,如果某個包有100個文件神年,而它的新版本只改變了其中1個文件玄货。那么?pnpm update?時只會向存儲中心額外添加1個新文件,而不會因為僅僅一個文件的改變復制整新版本包的內(nèi)容瓤帚。
  2. 所有文件都會存儲在硬盤上的某一位置描姚。 當軟件包被被安裝時涩赢,包里的文件會硬鏈接到這一位置,而不會占用額外的磁盤空間轩勘。 這允許你跨項目地共享同一版本的依賴谒主。

因此,您在磁盤上節(jié)省了大量空間赃阀,這與項目和依賴項的數(shù)量成正比霎肯,并且安裝速度要快得多!詳細了解點擊這里查看

不多bobo開整

首先需要全局安裝pnpm

npm install pnpm -g // 全局安裝pnpm

在你的桌面新增一個文件夾手動或者復制代碼

mkdir xlz-ui  //創(chuàng)建項目文件cd xlz-ui  //進入目錄pnpm init  //初始化package.json配置?件 私有庫

修改package.json刪除掉無用配置

{  "private": true,  "scripts": {    "test": "echo \"Error: no test specified\" && exit 1"  },  "keywords": [],  "author": "",  "license": "ISC",  "devDependencies": {    "typescript": "^4.8.4",    "vue": "^3.2.41"  }}

安裝vue3typescript依賴

pnpm install vue@next typescript -D // 全局下添加依賴

創(chuàng)建 .npmrc

touch .npmrc

.npmrc內(nèi)容添加 .npmrc配置更多詳情

shamefully-hoist = true  // 作用依賴包都扁平化的安裝在node_modules下面

創(chuàng)建tsconfig.json文件

touch tsconfig.json //創(chuàng)建tsconfig.jsonnpx tsc --init // 初始化ts配置文件

配置如下 如果需要了解全部配置請看這里

{  "compilerOptions": {    "module": "ESNext", // 打包模塊類型ESNext    "declaration": false, // 默認不要聲明?件    "noImplicitAny": false, // ?持類型不標注可以默認any    "removeComments": true, // 刪除注釋    "moduleResolution": "node", // 按照node模塊來解析    "esModuleInterop": true, // ?持es6,commonjs模塊    "jsx": "preserve", // jsx 不轉    "noLib": false, // 不處理類庫    "target": "es6", // 遵循es6版本    "sourceMap": true,    "lib": [      // 編譯時?的庫      "ESNext",      "DOM"    ],    "allowSyntheticDefaultImports": true, // 允許沒有導出的模塊中導?    "experimentalDecorators": true, // 裝飾器語法    "forceConsistentCasingInFileNames": true, // 強制區(qū)分??寫    "resolveJsonModule": true, // 解析json模塊    "strict": true, // 是否啟動嚴格模式    "skipLibCheck": true, // 跳過類庫檢測    "types": ["unplugin-vue-define-options"] // sfc 添加 name屬性的包需要的  },  "exclude": [    // 排除掉哪些類庫    "node_modules",    "**/__tests__",    "dist/**"  ]}

在項目根目錄下面創(chuàng)建pnpm-workspace.yaml配置文件榛斯。

touch pnpm-workspace.yaml

配置如下

packages:  - "packages/**" # 存放所有組件  - docs # 文檔  - play # 測試組件

pnpm-workspace.yaml?定義了?工作空間?的根目錄观游,并能夠使您從工作空間中包含 / 排除目錄 。 默認情況下驮俗,包含所有子目錄懂缕。

創(chuàng)建組件測試環(huán)境

pnpm create vite play --template vue-tscd play pnpm install

在根目錄新建一個typings目錄王凑,用來存放項目中通用的自定義的類型搪柑,然后把用vite創(chuàng)建的play/src下面的vite-env.d.ts移動到typings下面去。

在根目錄下面的package.json下面添加scripts腳本索烹。pnpm -C?<path>, --dir?<path>在?<path>?中啟動 pnpm 工碾,而不是當前的工作目錄。

  "scripts": {    "dev": "pnpm -C play dev"   }

這樣就可以在根目錄執(zhí)行pnpm dev啟動測試服務了

創(chuàng)建組件目錄結構

+ packages //跟目錄中創(chuàng)建    - components  // 組件代碼    - theme-chalk // 樣式    - utils  // 公共方法

依次創(chuàng)建并初始化pnpm init修改package.json

# 以components為例子百姓,其它同,修改name即可{  "name": "@xlz-ui/components",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo \"Error: no test specified\" && exit 1"  },  "keywords": [],  "author": "",  "license": "ISC"}

在根目錄下安裝三個子包pnpm install @xlz-ui/components @xlz-ui/theme-chalk @xlz-ui/utils -w渊额,其它兩個包同樣的操作,-w--workspace代表允許安裝到根目錄下垒拢,不加會報錯旬迹、執(zhí)行-w 的命令可以在任意目錄下執(zhí)行都會安裝在根目錄,然后查看根目錄下的package.json已經(jīng)有了這三個包

//package.json中被添加了三個包"dependencies": {    "@xlz-ui/components": "workspace:^1.0.0",    "@xlz-ui/theme-chalk": "workspace:^1.0.0",    "@xlz-ui/utils": "workspace:^1.0.0"  }

components目錄下創(chuàng)建icon目錄求类,來編寫一個icon組件奔垦,目錄如下:

    +components        + icon            + src # 組件源代碼                - icon.ts  # 放組件的props及公共方法                - icon.vue # 組件代碼            - index.ts # 組件入口        + index.ts //組件整體拋出 后續(xù)為了全部導入做準備

icon.ts下來定義props

import { ExtractPropTypes } from 'vue';// 定義props類型聲明export const iconProps = {  name: {    type: String,  },  size: {    type: [Number,String],  },  color: {    type: String,  },} as const//as const,會讓對象的每個屬性變成只讀(readonly)export type IconProps = ExtractPropTypes<typeof iconProps>;

icon.vue中寫組件代碼

<template>  <svg :class="bem.b()" :style="style" aria-hidden="true">    <use :xlink:href="iconName"></use>  </svg></template><script lang="ts" setup>import { computed, CSSProperties } from "vue";import "./font/iconfont.js";  //這里用了阿里適量import { createNamespace } from "@xlz-ui/utils";import { iconProps } from "./icon";const bem = createNamespace("icon");defineOptions({  name: "XIcon",});const props = defineProps(iconProps);const iconName = computed(() => {  return `#xlz-${props?.name}`;});const style = computed<CSSProperties>(() => {  const { size, color } = props;  if (!color && !size) {    return {};  }  return {    ...(size ? { "font-size": size + "px" } : {}),    ...(color ? { color: color } : {}),  };});</script>

在組件入口處導出組件尸疆,index.ts

import _Icon from './src/icon.vue';import { withInstall } from '@xlz-ui/utils';const XIcon = withInstall(_Icon); // 生成帶有 install 方法的組件export {//提供按需加載  XIcon}export default XIcon; // 導出組件

在icon同級的index.ts導出icon

export  * from './icon'

接下來解決上面用的withInstallcreateNamespace方法

css-BEM命名規(guī)范

具體規(guī)則這里推薦一篇文章提供參考我也是看的這篇

Js 實現(xiàn)部分utils/src/create.ts中寫一幾個方法

/** * * @param prefixName 前綴名 * @param blockName 代碼塊名 * @param elementName 元素名 * @param modifierName 裝飾符名 * @returns  說白了 椿猎,就是提供一個函數(shù),用來拼接三個字符串仓技,并用不同的符號進行分隔開來 */ function _bem(prefixName, blockName, elementName, modifierName) {  if (blockName) {    prefixName += `-${blockName}`;  }  if (elementName) {    prefixName += `__${elementName}`;  }  if (modifierName) {    prefixName += `--${modifierName}`;  }  return prefixName;}/** * * @param prefixName 前綴 * @returns */function createBEM(prefixName: string) {  const b = (blockName?) => _bem(prefixName, blockName, "", "");  const e = (elementName) =>    elementName ? _bem(prefixName, "", elementName, "") : "";  const m = (modifierName) =>    modifierName ? _bem(prefixName, "", "", modifierName) : "";  const be = (blockName, elementName) =>    blockName && elementName      ? _bem(prefixName, blockName, elementName, "")      : "";  const bm = (blockName, modifierName) =>    blockName && modifierName      ? _bem(prefixName, blockName, "", modifierName)      : "";  const em = (elementName, modifierName) =>    elementName && modifierName      ? _bem(prefixName, "", elementName, modifierName)      : "";  const bem = (blockName, elementName, modifierName) =>    blockName && elementName && modifierName      ? _bem(prefixName, blockName, elementName, modifierName)      : "";  const is = (name, state?) => (state ? `is-${name}` : "");  return {    b,    e,    m,    be,    bm,    em,    bem,    is,  };}export function createNamespace(name: string) {  const prefixName = `xlz-${name}`;  return createBEM(prefixName);}

Bem scss 部分 根據(jù)下方創(chuàng)建文件

theme-chalk├── package.json└── src   ├── icon.scss   ├── index.scss   ├── mixins   │   ├── config.scss   │   └── mixins.scss

config.scss

$namespace: "xlz";$element-separator: "__"; // 元素連接符$modifier-separator: "--"; // 修飾符連接符$state-prefix: "is-"; // 狀態(tài)連接符* { box-sizing: border-box;}

mixins.scss

@use "config" as *;@forward "config";// xlz-icon@mixin b($block) { $B: $namespace + "-" + $block; .#{$B} {   @content; }}// xlz-icon.is-xxx@mixin when($state) { @at-root {   &.#{$state-prefix + $state} {     @content;   } }}// .xlz-icon--primary@mixin m($modifier) { @at-root {   #{& + $modifier-separator + $modifier} {     @content;   } }}// xlz-icon__header@mixin e($element) { @at-root {   #{& + $element-separator + $element} {     @content;   } }}

index.scss

@use './icon.scss';

icon.scss

@use './mixins/mixins.scss' as *;@keyframes transform { from {   transform: rotate(0deg); } to {   transform: rotate(360deg); }}@include b(icon) { width: 1em; height: 1em; line-height: 1em; display: inline-flex; vertical-align: middle; svg.loading {   animation: transform 1s linear infinite; }}

withInstall方法

utils/src/with-install.ts文件鸵贬,代碼如下:

import type { App, Plugin } from "vue"; // 只是導入類型不是導入App的值/*** 組件外部使用use時執(zhí)行install俗他,然后將組件注冊為全局*/// 類型必須導出否則生成不了.d.ts文件export type SFCWithInstall<T> = T & Plugin;/** * 定義一個withInstall方法處理以下組件類型問題 * @param comp  */export const withInstall = <T>(comp: T) => {  /**   * 直接寫comp.install = function(){} 的話會報錯脖捻,因為comp下沒有install方法   * 所以從vue中引入Plugin類型,斷言comp的類型為T&Plugin   */  (comp as SFCWithInstall<T>).install = function (app: App) {    app.component((comp as any).name, comp);  };  return comp as SFCWithInstall<T>;};

utils/index.ts中添加

export * from './src/create'export * from './src/with-install'

icon使用阿里適量字體庫搭建

登錄自己的iconfont賬號沒有的注冊一個

購物車中隨便添加幾個icon
添加到新建的項目中

下載后之需要將iconfont.js放在icon/src/font中并把icon引入play/src/app.vue中執(zhí)行pnpm dev起服務

<template>  <XIcon name="anquanchaxun" color="red"></XIcon></template><script lang="ts" setup>import "@xlz-ui/theme-chalk/src/icon.scss";import XIcon from "@xlz-ui/components/icon";</script>

不出意外控制臺會抱一個defineOptions is not defined咱們來解決一下咱們在play中安裝一下unplugin-vue-define-options

pnpm i unplugin-vue-define-options -D

配置vite.config.ts

import { defineConfig } from 'vite'import DefineOptions from 'unplugin-vue-define-options/vite'import vue from '@vitejs/plugin-vue'export default defineConfig({  plugins: [vue(), DefineOptions()],})

前面忘記了安裝sass現(xiàn)在補一下 安裝在根目錄 然后起服務

pnpm i sass -w -D

不出意外你會看到一個icon

想法

  1. icon 其實還有其他的方式字體的方式引入字體文件 然后用class的方式展示icon 咱們這里是用的svg形式 沒有想好最用用那種方式暫時用這個
  2. 組件的按需加載與全部導入方式
  3. 組件的打包方式gulp+rollup
  4. git提交規(guī)范的設計
  5. 代碼規(guī)范的設計

到此結束 歡迎一起溝通交流 歡迎大神指點????

本文使用 文章同步助手 同步

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兆衅,一起剝皮案震驚了整個濱河市地沮,隨后出現(xiàn)的幾起案子嗜浮,更是在濱河造成了極大的恐慌,老刑警劉巖摩疑,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件危融,死亡現(xiàn)場離奇詭異,居然都是意外死亡雷袋,警方通過查閱死者的電腦和手機吉殃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來楷怒,“玉大人蛋勺,你說我怎么就攤上這事○荆” “怎么了抱完?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刃泡。 經(jīng)常有香客問我巧娱,道長,這世上最難降的妖魔是什么烘贴? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任禁添,我火速辦了婚禮,結果婚禮上桨踪,老公的妹妹穿的比我還像新娘上荡。我一直安慰自己,他們只是感情好馒闷,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布酪捡。 她就那樣靜靜地躺著,像睡著了一般纳账。 火紅的嫁衣襯著肌膚如雪逛薇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天疏虫,我揣著相機與錄音永罚,去河邊找鬼。 笑死卧秘,一個胖子當著我的面吹牛呢袱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翅敌,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼羞福,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚯涮?” 一聲冷哼從身側響起治专,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤卖陵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后张峰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泪蔫,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年喘批,在試婚紗的時候發(fā)現(xiàn)自己被綠了撩荣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饶深,死狀恐怖婿滓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粥喜,我是刑警寧澤凸主,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站额湘,受9級特大地震影響卿吐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜锋华,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一嗡官、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毯焕,春花似錦衍腥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芜辕,卻和暖如春尚骄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侵续。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工倔丈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人状蜗。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓需五,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轧坎。 傳聞我的和親對象是個殘疾皇子宏邮,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容