vue3前端组件库的搭建与发布(一)

qq_38133850 2024-10-04 08:03:01 阅读 84

前言:

最近在做公司项目中,有这么一件事情,很是头疼,就是同一套代码,不同项目,要改相同bug,改好多遍,改的都想吐,于是就想做一个组件库,这样更新一下就全都可以了,当然也是第一次主导组件库的搭建,有哪些不对的,还请各位大佬指出来哈。

准备:

        1、node(18+)

        2、Verdaccio :是一个 Node.js创建的轻量的私有npm proxy registry,可以直接在你本地起一个私有库

<code>npm i verdaccio -g

启动:verdaccio

  就会出现下面的页面

可以直接创建一个用户:

<code>npm adduser --registry http://localhost:4873/

会让你输入用户名和密码,这个要记好哈,后面上传的时候要用到

开始

看到网上大佬们用的是Monorepo 方式,那咱们也用这种方式(虽然不太懂为啥要这样,总之随主流指定出错少,哈哈)

创建文件夹:

mkdir Monorepo 

# 初始化文件

pnpm init 在此目录下面创建.npmrc

# 和npm一样,将别的包的依赖都放在node_modules下,不加的话会放在.pnpm下

shamefully-hoist = true

新建pnpm-workspace.yaml文件

packages:

# 将所有的项目都放到这里

- 'packages/*'

# 示例

- 'examples'

       4.创建文件目录 packages、examples

        packages -- 将所有组件放到这里

        examples -- 测试组件

        packages文件目录,里面的所有文件夹都要进行初始化 pnpm init

 5、进入到components里面,一定要安装相应的依赖呀

<codenpm i vue typescript sass element-plus decimal.js @element-plus/icons-vue  -D -w

-D 就不用介绍了

-w 是安装在根目录下

6、配置tsconfig.json文件

{

"compilerOptions": {

"allowJs": true, //允许编译器编译JS,JSX文件

"target": "ES2015", //指定ECMAScript目标版本

"useDefineForClassFields": true,

"module": "ESNext", //设置程序的模块系统

"moduleResolution": "Node", //模块解析策略。默认使用node的模块解析策略

"strict": true, //启用所有严格类型检查选项

"jsx": "preserve", //preserve模式,在preserve模式下生成代码中会保留JSX以供后续的转换操作使用

"sourceMap": true, //生成目标文件的sourceMap文件

"resolveJsonModule": true, //允许导入扩展名为“.json”的模块

"esModuleInterop": false, //允许module.exports=xxx 导出,由import from 导入.因为很多老的js库使用了commonjs的导出方式,并且没有导出default属性

"lib": [ //TS需要引用的库

"ESNext",

"DOM"

],

"forceConsistentCasingInFileNames": true, //禁止对同一个文件的不一致的引用

"allowSyntheticDefaultImports": true, //允许从没有设置默认导出的模块中默认导入

"skipLibCheck": true, //忽略所有的声明文件( *.d.ts)的类型检查

"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录

"paths": { //模块名到基于 baseUrl的路径映射的列表

"/@/*": [

"src/*"

],

},

"types": [ //要包含的类型声明文件名列表

"vite/client",

"element-plus/global",

]

},

"include": [ //包含的文件

"src/**/*.ts",

"src/**/*.d.ts",

"src/**/*.tsx",

"src/**/*.js",

"src/**/*.jsx",

"src/**/*.vue",

]

}

7、初始化 examples 文件夹

1、初始化

pnpm init

2、安装 vite 和 @vitejs/plugin-vue

pnpm vite @vitejs/plugin-vue -D -w

3.新建vite.config.ts 并配置

import { defineConfig } from 'vite'

import vue from '@vitejs/plugin-vue'

export default defineConfig({

plugins:[vue()]

})

4、新建index.html

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8" />code>

<meta http-equiv="X-UA-Compatible" content="IE=edge" />code>

<meta name="viewport" content="width=device-width, initial-scale=1.0" />code>

<title>Document</title>

</head>

<body>

<div id="app"></div>code>

<script src="main.ts" type="module"></script>code>

</body>

</html>

注意: vite 是基于 esmodule 的 所以 type="module"code>

@vitejs/plugin-vue 会默认加载 examples 下的 index.html

5、新建app.vue

<template>

<div>

app

</div>

</template>

<script setup lang="ts">code>

</script>

<style scoped>

</style>

6、新建main.ts

import {createApp} from 'vue'

import App from './app.vue'

const app = createApp(App)

app.mount('#app')

7、因为直接引入.vue 文件 TS 会找不到对应的类型声明;所以需要新建 typings(命名没有明确规定,TS 会自动寻找.d.ts 文件)文件夹来专门放这些声明文件。

declare module '*.vue' {

import type { DefineComponent } from "vue";

const component:DefineComponent<{},{},any>

}

8、在package.json 文件中配置 scripts 脚本

"scripts": {

"dev": "vite"

},

9.pnpm run dev 启动项目

8、初始化packages/components 文件夹

components 文件夹

1.目录结构

-- components

-- src

-- index.ts

-- input-number

-- inputNumber.vue

-- index.ts

-- index.ts

-- package.json

2.inputNumber.vue 如下代码

3.input-number/index.ts

import InputNumber from './inputNumber.vue'

InputNumber.install = (app) => {

app.component(InputNumber.name, InputNumber)

}

export default InputNumber

4.components/index.ts

import InputNumber from "./src/input-number/inputNumber.vue";

// 将所有的组件都放到这里进行导出

const components = [

InputNumber

]

// 定义install方法

const install = (app) => {

// 之策所有组件

components.forEach(item => {

app.component(item.name, item)

})

}

const DHSUI = {

install

}

// 支持按需引入

export {

InputNumber

}

// 导出install方法

export default DHSUI

以input框为例:

         src/input-number/inputNumber.vue

<template>

<el-input v-model="inputValue" class="customInput" v-bind="$attrs" :maxlength="props.maxlength" @input="handleInput">code>

<template #suffix>

<span class="iconBtn add" @click="add">code>

<el-icon><ArrowUp /></el-icon>

</span>

<span class="iconBtn decrease" @click="decrease">code>

<el-icon><ArrowDown /></el-icon>

</span>

</template>

<template v-if="props.isAppend" #append>{ { props.appendText }}</template>code>

</el-input>

</template>

<script lang="ts">code>

export default {

name: 'InputNumber'

}

</script>

<script setup lang="ts">code>

import { ElInput, ElIcon } from 'element-plus'

import 'element-plus/dist/index.css'

import {ArrowUp, ArrowDown } from '@element-plus/icons-vue'

import { Decimal } from "decimal.js";

import { onlyNumOnePoint, canBeMinus } from "@dhs-ui/utils";

import { ref, watch } from 'vue';

// 根据最长字符,生成最大值

const generateMaxString = (maxLength: any) => {

const maxValue = "9".repeat(maxLength as unknown as number);

return maxValue;

};

interface Props {

modelValue: string;

isAppend?: boolean;

appendText?: string;

min?: number;

max?: number;

step?: number;

maxlength?: number | string;

precision?: number;

}

const props = withDefaults(defineProps<Props>(), {

modelValue: "",

precision: 4,

isAppend: false

});

const emits = defineEmits(["input", "update:modelValue"]);

const inputValue = ref(props.modelValue);

const add = () => {

const step = props.step || 1;

let val = inputValue.value;

if (!val) {

val = "0";

}

let decimalVal = new Decimal(val);

if (maxNum() && new Decimal(maxNum()) <= decimalVal) {

inputValue.value = decimalVal.toFixed();

} else {

inputValue.value = decimalVal.plus(step).toFixed();

}

};

const decrease = () => {

const step = props.step || 1;

let val = inputValue.value;

if (!val && parseFloat(val) !== 0) val = "0";

if (props.min || props.min === 0) {

if (parseFloat(val) <= props.min) {

val = props.min.toFixed();

inputValue.value = val;

return;

}

}

let decimalVal = new Decimal(val);

inputValue.value = decimalVal.sub(step).toFixed();

};

// number 小数点位数

const vilidateNumberInput = (value: any, number: number) => {

let result: any;

if (props.min || props.min === 0) {

result = onlyNumOnePoint(value, number, !!number);

} else {

result = canBeMinus(value, number);

}

return result;

};

const maxNum = () => {

if (props.max) {

return props.max.toFixed();

} else {

return props.maxlength ? generateMaxString(props.maxlength) : null;

}

};

watch(

() => props.modelValue,

(newValue: any) => {

inputValue.value = newValue;

},

{ deep: true }

);

watch(inputValue, (nv: any) => {

emits("update:modelValue", nv);

});

const handleInput = (val: any) => {

inputValue.value = vilidateNumberInput(val, props.precision);

emits("input", val);

};

</script>

<style scoped lang="scss">code>

.customInput {

.iconBtn {

position: absolute;

right: 1px;

display: block;

width: 32px;

background-color: #f5f7fa;

border-left: 1px solid var(--default-border-color);

height: 15px;

line-height: 15px;

cursor: pointer;

}

.add {

top: 1px;

border-radius: 0 4px 0 0;

}

.decrease {

border-top: 1px solid var(--default-border-color);

bottom: 1px;

border-radius: 0 0 4px 0;

}

&.is-disabled {

.add,

.decrease {

pointer-events: none;

}

}

}

</style>

9、在examples/app.vue测试组件

<template>

<div>

<InputNumber :modelValue="inputValue" />code>

</div>

</template>

<script setup lang="ts">code>

import { ref } from 'vue';

import {InputNumber} from '../packages/components/src/input-number/inputNumber.vue';

const inputValue = ref(0)

</script>

<style scoped>

</style>

出现了你所要的组件就说明可以进行打包了。

10、在components文件夹中打包组件

components 文件夹 新建vite.config.ts

这里我们选择打包cjs(CommonJS)和esm(ESModule)两种形式,cjs模式主要用于服务端引用(ssr),而esm就是我们现在经常使用的方式,它本身自带treeShaking而不需要额外配置按需引入(前提是你将模块分别导出),非常好用~

为了也能在ts项目中使用,还需要自动生成类型声明文件

pnpm add vite-plugin-dts@1.4.1 -D -w

import { defineConfig } from "vite";

import vue from "@vitejs/plugin-vue";

import dts from "vite-plugin-dts";

export default defineConfig({

build: {

//打包文件目录

outDir: "es",

//压缩

//minify: false,

rollupOptions: {

//忽略打包的文件

external: ["vue", "element-plus"],

input: ["index.ts"],

output: [

{

//打包格式

format: "es",

//打包后文件名

entryFileNames: "[name].mjs",

//让打包目录和我们目录对应

preserveModules: false,

exports: "named",

//配置打包根目录

dir: "../DHS-UI/es",

},

{

//打包格式

format: "cjs",

//打包后文件名

entryFileNames: "[name].js",

//让打包目录和我们目录对应

preserveModules: false,

exports: "named",

//配置打包根目录

dir: "../DHS-UI/lib",

},

],

},

lib: {

entry: "./index.ts",

},

},

plugins: [

vue(),

dts({

entryRoot: "./src",

outputDir: ["../DHS-UI/es/src", "../DHS-UI/lib/src"],

//指定使用的tsconfig.json为我们整个项目根目录下,如果不配置,你也可以在components下新建tsconfig.json

tsConfigFilePath: "../../tsconfig.json",

}),

],

});

配置同目录下的package.json文件

"scripts": {

"build": "vite build"

},

11、运行 build 进行打包,会在目录中生成打包好的包

11、打包好的文件,进行初始化

<code>pnpm init

修改package.json 文件

{

"name": "dhs-uii",

"version": "1.0.2",

"description": "",

"main": "lib/index.js",

"module": "es/index.mjs",

"files": [

"es",

"lib"

],

"scripts": {

"test": "echo \"Error: no test specified\" && exit 1"

},

"sideEffects": [

"**/*.css"

],

"keywords": [

"dhs-ui",

"vue3组件库",

"frontend",

"element-plus"

],

"author": "dengdeng",

"license": "ISC",

"typings": "lib/index.d.ts"

}

   下一章进行发布,及遇到的问题。

感谢大佬文章:搭建一个组件库(vue3)_vue3组件库搭建-CSDN博客



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。