前言:
最近在做公司项目中,有这么一件事情,很是头疼,就是同一套代码,不同项目,要改相同bug,改好多遍,改的都想吐,于是就想做一个组件库,这样更新一下就全都可以了,当然也是第一次主导组件库的搭建,有哪些不对的,还请各位大佬指出来哈。
准备:
1、node(18+)
2、Verdaccio :是一个 Node.js创建的轻量的私有npm proxy registry,可以直接在你本地起一个私有库
npm i verdaccio -g启动:verdaccio
就会出现下面的页面
可以直接创建一个用户:
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里面,一定要安装相应的依赖呀
npm 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 init2、安装 vite 和 @vitejs/plugin-vuepnpm vite @vitejs/plugin-vue -D -w3.新建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"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"></div> <script src="main.ts" type="module"></script> </body></html> 注意: vite 是基于 esmodule 的 所以 type="module"@vitejs/plugin-vue 会默认加载 examples 下的 index.html5、新建app.vue<template> <div> app </div></template><script setup lang="ts"></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.tsimport InputNumber from './inputNumber.vue'InputNumber.install = (app) => { app.component(InputNumber.name, InputNumber)}export default InputNumber 4.components/index.tsimport 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"><template #suffix><span class="iconBtn add" @click="add"><el-icon><ArrowUp /></el-icon></span><span class="iconBtn decrease" @click="decrease"><el-icon><ArrowDown /></el-icon></span></template><template v-if="props.isAppend" #append>{{ props.appendText }}</template></el-input></template><script lang="ts"> export default { name: 'InputNumber'}</script><script setup lang="ts"> 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">.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" /> </div></template><script setup lang="ts">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、打包好的文件,进行初始化
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博客