Vue2開(kāi)發(fā)插件并發(fā)布到npm?
使用VitePress靜態(tài)網(wǎng)站生成器創(chuàng)建組件庫(kù)文檔網(wǎng)站并部署到GitHub
目標(biāo):創(chuàng)建 vue-amazing-ui 組件庫(kù),并發(fā)布到npm
該組件庫(kù)已發(fā)布到 npm,直接安裝即可使用:
pnpm i vue-amazing-ui
#or
yarn add vue-amazing-ui
#or
npm install vue-amazing-ui
目前已包含35個(gè)常用UI組件和8個(gè)常用API功能函數(shù),持續(xù)更新中...
Vue Amazing UI 在線預(yù)覽
擁有的 Components 組件和工具函數(shù) Functions:??
-
面包屑、按鈕、走馬燈、級(jí)聯(lián)選擇、多選框、折疊面板、倒計(jì)時(shí)、日期選擇、對(duì)話框、分割線、空狀態(tài)、圖片、數(shù)字輸入框、全局提示、信息提示、通知提醒框、分頁(yè)器、進(jìn)度條、二維碼、單選框、評(píng)分、選擇器、滑動(dòng)輸入條、加載中、步驟條、觸摸滑動(dòng)插件、開(kāi)關(guān)、表格、標(biāo)簽頁(yè)、文字滾動(dòng)、時(shí)間軸、文字提示、上傳、播放器、瀑布流
-
dateFormat(日期格式化函數(shù))、requestAnimationFrame(已兼容處理)、cancelAnimationFrame(已兼容處理)、rafTimeout(使用raf實(shí)現(xiàn)的定時(shí)器,等效替代setTimeout和setInterval)、cancelRaf(用于取消rafTimeout)、throttle(節(jié)流函數(shù))、debounce(防抖函數(shù))、add(消除js加減精度的加法函數(shù))
①創(chuàng)建vue3+ts+vite項(xiàng)目:
npm init vue@latest(輸入項(xiàng)目名稱,并依次選擇需要安裝的依賴項(xiàng))
②項(xiàng)目目錄結(jié)構(gòu)截圖如下:
③在項(xiàng)目根目錄新建 packages/ 文件夾用于存放組件 (以Breadcrumb為例,其他類似)
④在項(xiàng)目根目錄中的 vite.config.ts 中寫(xiě)入相關(guān)配置項(xiàng):
import { fileURLToPath, URL } from 'node:url'
import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// ant-desing按需引入
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
// 打包體積可視化插件
// import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// visualizer({ // 生成的分析圖文件名,默認(rèn)stats.html
// file: 'stats.html',
// open: true // 打包后自動(dòng)打開(kāi)分析圖
// }),
Components({
resolvers: [AntDesignVueResolver()]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'images': fileURLToPath(new URL('./src/assets/images', import.meta.url))
}
},
css: {
preprocessorOptions: {
less: {
modifyVars: { // 或者globalVars
// `themeColor` is global variables fields name
themeColor: '#1677FF' // #1890FF
},
javascriptEnabled: true
},
},
},
// 構(gòu)建為庫(kù)
build: {
lib: { // 構(gòu)建為庫(kù)。如果指定了 build.lib,build.cssCodeSplit 會(huì)默認(rèn)為 false。
// __dirname的值是vite.config.ts文件所在目錄
entry: resolve(__dirname, 'packages/index.ts'), // entry是必需的,因?yàn)閹?kù)不能使用HTML作為入口。
name: 'VueAmazingUI', // 暴露的全局變量
fileName: 'vue-amazing-ui' // 輸出的包文件名,默認(rèn)是package.json的name選項(xiàng)
},
rollupOptions: { // 自定義底層的Rollup打包配置
// https://rollupjs.org/configuration-options/
// 確保外部化處理那些你不想打包進(jìn)庫(kù)的依賴
external: ['vue', 'swiper', '@vuepic/vue-datepicker', 'qrcode'],
output: {
// format: 'es', // 默認(rèn)es,可選 'amd' 'cjs' 'es' 'iife' 'umd' 'system'
exports: 'named', // https://rollupjs.org/configuration-options/#output-exports
// // 在 UMD 構(gòu)建模式下為這些外部化的依賴提供一個(gè)全局變量
globals: {
vue: 'Vue',
// 'vue-router': 'VueRouter', // 引入vue-router全局變量,否則router.push將無(wú)法使用
swiper: 'Swiper',
'@vuepic/vue-datepicker': 'VueDatePicker',
qrcode: 'qrcode'
}
}
},
/** 設(shè)置為 false 可以禁用最小化混淆,或是用來(lái)指定使用哪種混淆器。
默認(rèn)為 Esbuild,它比 terser 快 20-40 倍,壓縮率只差 1%-2%。
注意,在 lib 模式下使用 'es' 時(shí),build.minify 選項(xiàng)不會(huì)縮減空格,因?yàn)闀?huì)移除掉 pure 標(biāo)注,導(dǎo)致破壞 tree-shaking。
當(dāng)設(shè)置為 'terser' 時(shí)必須先安裝 Terser。(yarn add terser -D)
*/
minify: 'terser', // Vite 2.6.x 以上需要配置 minify: "terser", terserOptions 才能生效
terserOptions: { // 在打包代碼時(shí)移除 console、debugger 和 注釋
compress: {
/* (default: false) -- Pass true to discard calls to console.* functions.
If you wish to drop a specific function call such as console.info and/or
retain side effects from function arguments after dropping the function
call then use pure_funcs instead
*/
drop_console: true, // 生產(chǎn)環(huán)境時(shí)移除console
drop_debugger: true
},
format: {
comments: false // 刪除注釋comments
}
}
}
})
⑤在 packages/ 文件夾下創(chuàng)建UI組件,例如:新建 breadcrumb/ 和 pagination/ 文件夾,截圖如下:
⑥在 breadcrumb/ 文件夾下新建 Breadcrumb.vue 組件文件和 index.ts 文件,截圖如下:?
?⑦在Breadcrumb.vue 中編寫(xiě)組件代碼:
<script setup lang="ts">
import { computed } from 'vue'
interface Query {
[propName: string]: any // 添加一個(gè)字符串索引簽名,用于包含帶有任意數(shù)量的其他屬性
}
interface Route {
path: string // 路由地址
query?: Query // 路由查詢參數(shù)
name: string // 路由名稱
}
interface Props {
routes: Array<Route> // 或者Route[] router的路由數(shù)組,沒(méi)有 ? 時(shí),即表示 required: true
fontSize: number // 字體大小
height?: number // 面包屑高度
maxWidth?: number // 文本最大顯示寬度,超出后顯示省略號(hào)
separator?: string // 自定義分隔符
target?: '_self'|'_blank' // 如何打開(kāi)目標(biāo)URL,當(dāng)前窗口或新窗口
}
const props = withDefaults(defineProps<Props>(), {
routes: () => [],
fontSize: 14,
height: 21,
maxWidth: 180,
separator: '',
target: '_self'
})
const len = computed(() => {
return props.routes.length
})
function getUrl (route: Route) {
var targetUrl = route.path
if (route.query && JSON.stringify(route.query) !== '{}') {
const query = route.query
Object.keys(query).forEach((param, index) => {
if (index === 0) {
targetUrl = targetUrl + '?' + param + '=' + query[param]
} else {
targetUrl = targetUrl + '&' + param + '=' + query[param]
}
})
}
return targetUrl
}
</script>
<template>
<div class="m-breadcrumb" :style="`height: ${height}px;`">
<div class="m-bread" v-for="(route, index) in routes" :key="index">
<a
:class="['u-route',{ active: index===len-1 }]"
:style="`font-size: ${fontSize}px; max-width: ${maxWidth}px;`"
:href="index === len - 1 ? 'javascript:;' : getUrl(route)"
:title="route.name"
:target="index === len - 1 ? '_self' : target">
{{ route.name || '--' }}
</a>
<template v-if="index !== len - 1">
<span v-if="separator" class="u-separator">{{ separator }}</span>
<svg v-else class="u-arrow" viewBox="64 64 896 896" data-icon="right" aria-hidden="true" focusable="false"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path></svg>
</template>
</div>
<div class="assist"></div>
</div>
</template>
<style lang="less" scoped>
.m-breadcrumb {
display: flex;
align-items: center;
.m-bread {
display: inline-flex;
align-items: center;
line-height: 1.5;
.u-route {
color: rgba(0, 0, 0, 0.45);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
padding: 0 4px;
border-radius: 4px;
transition: color 0.2s, background-color 0.2s;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
color: rgba(0, 0, 0, 0.88);
}
}
.active {
color: rgba(0, 0, 0, 0.88);
cursor: default;
&:hover {
background-color: transparent;
}
}
.u-separator {
margin: 0 4px;
color: rgba(0, 0, 0, 0.45);
}
.u-arrow {
width: 12px;
height: 12px;
fill: rgba(0, 0, 0, 0.45);
}
}
.assist {
height: 100%;
width: 0;
display: inline-block;
vertical-align: middle;
}
}
</style>
⑧在 breadcrumb/index.ts 中導(dǎo)出組件
import type { App } from 'vue'
import Breadcrumb from './Breadcrumb.vue'
// 使用install方法,在app.use掛載
Breadcrumb.install = (app: App) => {
app.component(Breadcrumb.__name as string, Breadcrumb)
}
export default Breadcrumb
⑨在 packages/index.ts 文件中對(duì)整個(gè)組件庫(kù)進(jìn)行導(dǎo)出:
import type { App } from 'vue'
import Breadcrumb from './breadcrumb'
import Pagination from './pagination'
// 所有組件列表
const components = [
Breadcrumb,
Pagination
]
// 定義 install 方法
const install = (app: App): void => {
// 遍歷注冊(cè)所有組件
/*
component.__name ts報(bào)錯(cuò)
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2345)
解決方式一:使用// @ts-ignore
解決方式二:使用類型斷言 尖括號(hào)語(yǔ)法(<string>component.__name) 或 as語(yǔ)法(component.__name as string)
*/
components.forEach(component => app.component(component.__name as string, component))
}
export {
Breadcrumb,
Pagination
}
const VueAmazingUI = {
install
}
export default VueAmazingUI
⑩在 src/main.ts 中導(dǎo)入剛創(chuàng)建的組件,檢測(cè)是否正??捎?/span>
// import VueAmazingUI from '../packages'
import VueAmazingUI from '../dist/vue-amazing-ui.js'
import '../dist/style.css'
// import { Breadcrumb } from '../dist/vue-amazing-ui.js'
const app = createApp(App)
app.use(VueAmazingUI)
// app.use(Breadcrumb)
app.mount('#app')
?在終端執(zhí)行 npm init 初始化包,選填并配置package.json:
{
"name": "vue-amazing-ui",
"version": "0.0.30",
"private": false,
"type": "module", // 如果 package.json 不包含 "type": "module",Vite 會(huì)生成不同的文件后綴名以兼容 Node.js。.js 會(huì)變?yōu)?.mjs 而 .cjs 會(huì)變?yōu)?.js
"files": ["dist"], // 檢測(cè)dist打包目錄的所有文件
"main": "./dist/vue-amazing-ui.umd.cjs",
"module": "./dist/vue-amazing-ui.js",
"exports": {
"./dist/style.css": "./dist/style.css", // 子目錄別名,方便樣式引入
"./css": "./dist/style.css",
".": { // 模塊的主入口,優(yōu)先級(jí)高于main字段,利用.這個(gè)別名,為 ES6 模塊(import)和 CommonJS (require)指定不同的入口
"import": "./dist/vue-amazing-ui.js",
"require": "./dist/vue-amazing-ui.umd.cjs"
}
},
"scripts": {
"dev": "vite --port 9000 --open --force",
"build": "run-p type-check build-only",
"docs:dev": "vitepress dev docs --port 8000 --open",
"docs:build": "vitepress build docs",
"docs:deploy": "sh script/deploy.sh",
"pub": "sh script/publish.sh",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@vuepic/vue-datepicker": "^4.5.1",
"@vueuse/core": "^10.1.2",
"@vueuse/integrations": "^10.1.2",
"ant-design-vue": "^3.2.20",
"core-js": "^3.30.2",
"date-fns": "^2.30.0",
"qrcode": "^1.5.3",
"swiper": "^9.3.2",
"vue": "^3.3.4",
"vue-amazing-ui": "^0.0.30",
"vue-router": "^4.2.1"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.0",
"@types/node": "^18.16.14",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.41.0",
"eslint-plugin-vue": "^9.14.0",
"less": "^4.1.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.0",
"terser": "^5.17.6",
"typescript": "~4.7.4",
"unplugin-vue-components": "^0.25.0",
"vite": "^4.3.8",
"vitepress": "1.0.0-beta.1",
"vue-tsc": "^1.6.5"
},
"description": "This template should help get you started developing with Vue Amazing UI in Vue 3.",
"repository": {
"type": "git",
"url": "git+https://github.com/themusecatcher/vue-amazing-ui.git"
},
"keywords": [
"Vue3",
"TS",
"Vite",
"Amazing",
"UI",
"Components"
],
"author": "theMuseCatcher",
"license": "ISC",
"bugs": {
"url": "https://github.com/themusecatcher/vue-amazing-ui/issues"
},
"homepage": "https://github.com/themusecatcher/vue-amazing-ui#readme"
}
name: 包名,該名字是唯一的。可在 npm 官網(wǎng)搜索名字,不可重復(fù)。
version: 版本號(hào),每次發(fā)布至 npm 需要修改版本號(hào),不能和歷史版本號(hào)相同。
private:是否私有,需要修改為 false 才能發(fā)布到 npm
description: 關(guān)于包的描述。
main: 入口文件,需指向最終編譯后的包文件。
keywords:關(guān)鍵字,以空格分離希望用戶最終搜索的詞。
author:作者
license: 開(kāi)源協(xié)議
vite build --watch:當(dāng)啟用 --watch 標(biāo)志時(shí)(啟用 rollup 的監(jiān)聽(tīng)器),對(duì) vite.config.ts 的改動(dòng),以及任何要打包的文件,都將觸發(fā)重新構(gòu)建
vite --port 9000 --open --force:指定端口9000,啟動(dòng)時(shí)打開(kāi)瀏覽器,強(qiáng)制優(yōu)化器忽略緩存并重新構(gòu)建。
?執(zhí)行編譯命令
yarn build(或npm run build)
執(zhí)行結(jié)果如下圖:
??在項(xiàng)目根目錄創(chuàng)建 .npmignore 文件,設(shè)置忽略發(fā)布的文件,類似 .gitignore 文件
# 只有編譯后的 dist 目錄、package.json、README.md是需要被發(fā)布的
# 忽略目錄
.DS_Store
.vscode/
node_modules
packages/
public/
src/
# 忽略指定文件
.eslintrc.cjs
.gitignore
.npmignore
.npmrc
.prettierrc.json
components.d.ts
env.d.ts
index.html
pnpm-lock.yaml
stats.html
tsconfig.config.json
tsconfig.json
vite.config.ts
?編寫(xiě)README.md文件(使用markdown格式)
# vue-amazing-ui
## Document & Online preview
[Vue Amazing UI](https://themusecatcher.github.io/vue-amazing-ui/)
## Install & Use
```bash
pnpm i vue-amazing-ui
# or
npm install vue-amazing-ui
# or
yarn add vue-amazing-ui
```
Import and register component
**Global**
```ts
import { createApp } from 'vue'
import App from './App.vue'
import VueAmazingUI from 'vue-amazing-ui'
import 'vue-amazing-ui/css'
const app = createApp(App)
app.use(VueAmazingUI)
```
**Local**
```vue
<script setup lang="ts">
import { Button } from 'vue-amazing-ui'
import 'vue-amazing-ui/css'
</script>
```
## Project
- Get the project code
```sh
git clone https://github.com/themusecatcher/vue-amazing-ui.git
```
- Install dependencies
```sh
cd vue-amazing-ui
pnpm i
```
- Run project
```sh
pnpm dev
```
## Components
| Component name | Descriptions | Component name | Descriptions |
| :--- | :--- | :--- | :--- |
Breadcrumb | 面包屑 | Button | 按鈕
Carousel | 走馬燈 | Cascader | 級(jí)聯(lián)選擇
Checkbox | 多選框 | Collapse | 折疊面板
Countdown | 倒計(jì)時(shí) | DatePicker | 日期選擇
Dialog | 對(duì)話框 | Divider | 分割線
Empty | 空狀態(tài) | Image | 圖片
InputNumber | 數(shù)字輸入框 | Message | 全局提示
Modal | 信息提示 | Notification | 通知提醒框
Pagination | 分頁(yè)器 | Progress | 進(jìn)度條
QRCode | 二維碼 | Radio | 單選框
Rate | 評(píng)分 | Select | 選擇器
Slider | 滑動(dòng)輸入條 | Spin | 加載中
Steps | 步驟條 | Swiper | 觸摸滑動(dòng)插件
Switch | 開(kāi)關(guān) | Table | 表格
Tabs | 標(biāo)簽頁(yè) | TextScroll | 文字滾動(dòng)
Timeline | 時(shí)間軸 | Tooltip | 文字提示
Upload | 上傳 | Video | 播放器
Waterfall | 瀑布流
## Details
[My CSDN Blogs](https://blog.csdn.net/Dandrose)
## Functions
| Function name | Descriptions | Arguments
| :--- | :--- | :--- |
dateFormat | 簡(jiǎn)單易用的日期格式化函數(shù)! | (timestamp: number|string|Date, format = 'YYYY-MM-DD HH:mm:ss') => string
requestAnimationFrame | 針對(duì)不同瀏覽器進(jìn)行兼容處理! | 使用方式不變
cancelAnimationFrame | 針對(duì)不同瀏覽器進(jìn)行兼容處理! | 使用方式不變
rafTimeout | 使用 requestAnimationFrame 實(shí)現(xiàn)的定時(shí)器函數(shù),等效替代 (setTimeout 和 setInterval)! | (func: Function, delay = 0, interval = false) => object
cancelRaf | 用于取消 rafTimeout 函數(shù)! | (raf: { id: number }) => void
throttle | 使用 rafTimeout 實(shí)現(xiàn)的節(jié)流函數(shù)! | (fn: Function, delay = 300) => any
debounce | 使用 rafTimeout 實(shí)現(xiàn)的防抖函數(shù)! | (fn: Function, delay = 300) => any
add | 消除js加減精度問(wèn)題的加法函數(shù)! | (num1: number, num2: number) => number
downloadFile | 下載文件并自定義文件名! | (url: string, name: string) => void
?登錄npm
如果沒(méi)有npm賬號(hào),可以去npm官網(wǎng)( npm) 注冊(cè)一個(gè)賬號(hào)
注冊(cè)成功后在本地查看pnpm / npm鏡像:
pnpm/npm config get registry
輸出:http://registry.npmjs.org 即可
如果不是則需要設(shè)置為npm鏡像:
pnpm/npm config set registry https://registry.npmjs.org
然后在終端執(zhí)行:
pnpm/npm login
依次輸入用戶名,密碼,郵箱
輸出Logged in as…即可
pnpm/npm whoami // 查看當(dāng)前用戶是否已登錄
?發(fā)布組件到npm
在終端執(zhí)行:pnpm/npm publish
發(fā)布成功后即可在npm官網(wǎng)搜索到該組件,如下圖
并可以通過(guò) pnpm/npm install vue-amazing-ui(或yarn add vue-amazing-ui)進(jìn)行安裝
?為方便打包構(gòu)建、發(fā)布、提交代碼到github等操作,可以通過(guò)腳步一次性執(zhí)行以上操作:
在項(xiàng)目中新建 script/ 文件夾,并創(chuàng)建 publish.sh 腳本文件,如下圖:
在 publish.sh 中創(chuàng)建以下腳本:
# /bin/bash
# 確保腳本拋出遇到的錯(cuò)誤
set -e
# 讀取package.json中的version
version=`jq -r .version package.json`
# 打包構(gòu)建
pnpm build
# 提交代碼到github
git add .
git commit -m "update $version"
git push
# 發(fā)布到npm,pnpm(高性能的npm)
pnpm publish
# 升級(jí) vue-amazing-ui 依賴版本
pnpm up vue-amazing-ui@$version
# 提交版本更新代碼到github
git add .
git cm -m "update $version"
git push
?之后打包構(gòu)建、發(fā)布、提交代碼到github 只需新增version版本號(hào)之后執(zhí)行:sh publish.sh 即可!
?在要使用的項(xiàng)目中安裝并注冊(cè)插件:
pnpm i vue-amazing-ui
#or
yarn add vue-amazing-ui
# or
npm install vue-amazing-ui
然后在 main.ts 文件中引入并注冊(cè):文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-413922.html
import VueAmazingUI from 'vue-amazing-ui'
// import { Pagination, Breadcrumb } from 'vue-amazing-ui'
import 'vue-amazing-ui/css'
app.use(VueAmazingUI)
// app.use(Pagination).use(Breadcrumb)
在要使用組件的頁(yè)面直接使用即可:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-413922.html
<script setup lang="ts">
const routes = [
{
path: '/first', // 路由地址
query: { id: 1, tab: 2 }, // 路由參數(shù)
name: '一級(jí)路由' // 路由名稱
},
{
path: '/second',
name: '二級(jí)路由'
},
{
path: '/third',
name: '三級(jí)路由三級(jí)路由三級(jí)路由三級(jí)路由'
}
]
</script>
<template>
<Breadcrumb :routes="routes" />
</template>
到了這里,關(guān)于Vue3+TS+Vite開(kāi)發(fā)插件并發(fā)布到npm的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!