Vue2 + Vue CLI 打包成 NPM 包(支持组件 + 工具类)
# Vue2 + Vue CLI 打包成 NPM 包(支持组件 + 工具类)
### 本指南将带你从零开始,使用 Vue CLI 打包一个 支持组件 + 工具类 的 npm 包,并提供完整的打包脚本、配置文件、版本限制提示等。
### 这里是用 zsduan-ui 做的一个示例 你可以把 zsduan-ui 替换成自己npm包的名称
## 前期工作
### 用vue cli 创建项目 请选用vue2 ts 版本
### Q:为什么用ts?A:因为你不确定创建的npm包是否会用到ts 所以选择ts
```bash
vue create vue-npm-demo
cd vue-npm-demo
npm i
npm run serve
```
## 打包npm包组件
### 1. 创建组件
#### 创建组件文件 src/components/dzs-button.vue
```html
```
```typescript
// index.ts
import dzsButton from "./index.vue";
export declare type dzsDialogInstance = InstanceType
export default dzsButton;
export const dzsDialogPlugin = {
install : >(Vue : any , options ?: T) =>{
Vue.component(dzsButton.name, dzsButton)
},
version : "20250725"
}
export { dzsButton }
```
```typescript
// dzs-button.d.ts
export declare class DzsButton {
/** 按钮文本*/
text?: ButtonType;
}
/**
* 点击事件
* @param event 原生事件对象
*/
interface DzsButtonEmits {
(event: "click", e: Event): void;
}
// 导出类型供外部使用
export type { ButtonType, ButtonSize, NativeType, DzsButtonProps, DzsButtonEmits };
```
### 目录结构
```text
my-component-library/
└── src/
└── components/
├── dzs-button.d.ts
├── index.ts
└── index.vue
```
### 2.修改 tsconfig.json 文件
#### 新增下面的配置项
```json
"compilerOptions": {
// ... 其他配置项
"declaration": true,
"declarationDir": "./dist/zsduan-ui/types",
"emitDeclarationOnly": true,
"outDir": "./dist/zsduan-ui",
// ... 其他配置项
}
```
### 3.修改 vue.config.js 文件
#### 新增下面的配置项
```javascript
const { defineConfig } = require("@vue/cli-service")
const path = require("path")
module.exports = defineConfig({
// ... 其他配置项
outputDir: path.resolve(__dirname, "dist/zsduan-ui/lib"),
productionSourceMap: false,
})
```
### 4.修改 package.json 文件
#### 新增下面的配置项
```json
{
// ... 其他配置项
"types": "types/index.d.ts",
"main": "lib/zsduan-ui.umd.js",
"typings": "types/index.d.ts",
"peerDependencies": {
"element-ui": "^2.0.0",
"vue": "^2.6.0"
}
// ... 其他配置项
}
```
### 5.创建 index.ts 的入口文件
#### Q:为什么需要导出 两个dzs-button ? A:因为在项目中,我们可能会使用多个组件,所以需要导出多个组件
#### Q:为什么要有 checkDependencies 这个函数 ? A:为了检查依赖项是否符合
```typescript
// src/index.ts
import dzsButton from "./components/dzs-button"
export { dzsButton } from "./components/dzs-button"
function checkDependencies() {
try {
const elementUI = require("element-ui")
const version = elementUI.version
if (!version.startsWith("2.")) {
console.warn(
"[zsduan-ui] 检测到你安装了 element-ui@%s,我们推荐使用 element-ui@2.x 版本以确保兼容性。",
version
)
}
} catch (e) {
console.error(
"[zsduan-ui] 缺少依赖:未找到 element-ui。请运行:npm install element-ui@^2"
)
}
try {
const Vue = require("vue");
const version = Vue.version
if (!version.startsWith("2.")) {
console.warn(
"[zsduan-ui] 检测到你安装了vue@%s,我们推荐使用 vue@2.x 版本以确保兼容性。",
version
)
}
}catch (e){
console.error(
"[zsduan-ui] 缺少依赖:未找到 vue。请运行:npm install vue@^2"
)
}
}
if (process.env.NODE_ENV !== "production") {
checkDependencies()
}
// 默认导出插件
export default {
install(Vue: any) {
Vue.component("DzsButton", dzsButton)
}
}
```
### 6. 创建 scripts/postbuild.js 打包文件
#### 用于对组件的打包
```javascript
// scripts/postbuild.js
const fs = require("fs")
const path = require("path")
const distTypesDir = path.resolve(__dirname, "../dist/zsduan-ui/types")
const srcComponentsDir = path.resolve(__dirname, "../src/components")
if (!fs.existsSync(distTypesDir)) {
fs.mkdirSync(distTypesDir, { recursive: true })
}
function copyDtsFiles() {
const dirs = fs.readdirSync(srcComponentsDir)
dirs.forEach(dir => {
const dtsFile = path.resolve(srcComponentsDir, dir, `${dir}.d.ts`)
if (fs.existsSync(dtsFile)) {
const destFile = path.resolve(distTypesDir, `${dir}.d.ts`)
// 读取原始 .d.ts 文件内容
const originalContent = fs.readFileSync(dtsFile, "utf-8")
// 生成要追加的内容
const componentName = dir
.split("-")
.map((s, i) => (i === 0 ? s : s[0].toUpperCase() + s.slice(1)))
.join("")
const newContent = `
import { defineComponent } from "vue"
export const ${componentName}: typeof defineComponent
`
// 拼接原始内容 + 新内容
const finalContent = originalContent + newContent
// 写入目标文件(覆盖一次)
fs.writeFileSync(destFile, finalContent, "utf-8")
}
})
}
// 生成 index.d.ts
function generateIndexDts() {
const dirs = fs.readdirSync(srcComponentsDir)
let content = ""
dirs.forEach(dir => {
const dtsFile = path.resolve(srcComponentsDir, dir, `${dir}.d.ts`)
if (fs.existsSync(dtsFile)) {
const componentName = dir
.split("-")
.map((s, i) => (i === 0 ? s : s[0].toUpperCase() + s.slice(1)))
.join("")
content += `export { ${componentName} } from "./${dir}.d.ts"\n`
}
})
fs.writeFileSync(path.resolve(distTypesDir, "index.d.ts"), content)
}
copyDtsFiles()
generateIndexDts()
```
### 7.创建 scripts/copy-package-json.js 文件
#### 用于对 package.json 文件进行复制
```javascript
// scripts/copy-package-json.js
const fs = require("fs")
const path = require("path")
const rootPkg = require("../package.json")
const distDir = path.resolve(__dirname, "../dist/zsduan-ui")
// 解构时避免使用 `module` 这样的保留字
const {
name,
version,
main,
module: esModule,
types,
peerDependencies,
dependencies,
devDependencies,
description,
author,
license
} = rootPkg
const distPkg = {
name,
version,
main,
module: esModule,
types,
peerDependencies,
dependencies: {},
description,
author,
license
}
// 如果有 peerDependencies,保留;没有就删除这个字段
if (!distPkg.peerDependencies) {
delete distPkg.peerDependencies
}
// 写入 dist/zsduan-ui/package.json
fs.writeFileSync(
path.resolve(distDir, "package.json"),
JSON.stringify(distPkg, null, 2),
"utf-8"
)
console.log("✅ package.json 已复制到 dist/zsduan-ui/")
// ✅ 删除 dist/zsduan-ui/types/src 目录
const srcDirInTypes = path.resolve(distDir, "types/src")
if (fs.existsSync(srcDirInTypes)) {
fs.rmSync(srcDirInTypes, { recursive: true, force: true })
console.log("✅ 已删除 dist/zsduan-ui/types/src 目录")
} else {
console.log("ℹ️ dist/zsduan-ui/types/src 不存在,跳过删除")
}
```
### 8.在package.json 中添加 scripts
```json
{
// ... 其他配置项
"scripts": {
// ... 其他配置项
"build:lib": "vue-cli-service build --target lib --name zsduan-ui src/index.ts && tsc && node scripts/postbuild.js && node scripts/copy-package-json.js"
// ... 其他配置项
}
// ... 其他配置项
}
```
#### 打包完成后的目录结构
```text
dist/
dist/
└── zsduan-ui/
├── lib/
| ├── demo.html
| ├── zsduan-ui.common.js
| ├── zsduan-ui.css
| ├── zsduan-ui.udm.js
| └── zsduan-ui.umd.min.js
├── types/
| ├── dzs-button.d.ts
| └── index.d.ts
└── package.json
```
### 9. 如何使用
### 这里没有发布到npm,请自行复制代码到 node_modules/ 中 ,当然你也可以发布到 npm
#### 9.1 全局引用
```javascript
// main.js / main.ts
import zsduanUI from "zsduan-ui"
Vue.use(zsduanUI)
```
#### 9.2 全局注册组件
```javascript
import {dzsButton} from "zsduan-ui"
Vue.component("dzs-button",dzsButton)
```
#### 9.3 局部引用
```javascript
import {dzsButton} from "zsduan-ui"
export default {
components: {
dzsButton
}
}
```
### 到这里 组件已经打包完成
## utils 工具类打包
### 1. 创建工具TS
```typescript
// utils/sum.ts
type Sum = (a: number, b: number) => number
export const sum: Sum = (a, b) => {
return a + b
}
```
### 2. 创建入口文件
```typescript
// utils/index.ts
export { default as sum } from "./sum"
```
### 目录结构
```text
src/
└── utils/
├── index.ts
└── sum.ts
```
### 3. 安装打包依赖
```bash
npm install --save-dev rollup @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve
```
### 4.创建 tsconfig.utils.json 放在根目录下
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declarationDir": "./dist/zsduan-ui/utils/types",
"outDir": "./dist/zsduan-ui/utils/types",
"declaration": true,
"emitDeclarationOnly": true,
"rootDir": "./src/utils",
"module": "ESNext",
"target": "ES5",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/utils/**/*"]
}
```
### 5.建 scripts/utilsbuild.js 文件
#### 用于 生成 utils 的 types
```javascript
// scripts/utilsbuild.js
const fs = require("fs-extra")
const path = require("path")
// 解析路径
const utilsSrc = path.resolve(__dirname, "../src/utils") // 源码 utils 目录
const utilsDist = path.resolve(__dirname, "../dist/zsduan-ui/utils")
const typesDir = path.join(utilsDist, "types")
async function postBuild() {
try {
// 确保 types 目录存在
await fs.ensureDir(typesDir)
// 读取源 utils 目录下的所有 .ts(x) 文件(排除 index.ts、test 文件等)
const files = await fs.readdir(utilsSrc)
const utilFiles = files
.filter(file => {
const ext = path.extname(file)
const name = path.basename(file, ext)
// 只处理 .ts 或 .tsx 文件,且不是 index、type、d.ts 等
return (ext === ".ts" || ext === ".tsx") && !["index", "types"].includes(name)
})
.map(file => path.basename(file, path.extname(file))) // 提取文件名(不含扩展名)
// 生成 index.d.ts 内容:动态导入所有 default export
const indexDtsContent = utilFiles
.map(name => `export { default as ${name} } from "./types/${name}"`)
.join("\n") + "\n"
// 写入 index.d.ts
await fs.writeFile(path.join(utilsDist, "index.d.ts"), indexDtsContent)
console.log("✅ index.d.ts 已生成,自动导出以下模块:", utilFiles)
} catch (err) {
console.error("❌ 类型文件处理失败:", err)
process.exit(1) // 构建失败退出
}
}
postBuild()
```
### 6.建 scripts/rollup-config-utils.js 文件
#### 把 TS 文件编译成 JS 文件
```javascript
// scripts/rollup-config-utils.js
import typescript from "@rollup/plugin-typescript"
import commonjs from "@rollup/plugin-commonjs"
import resolve from "@rollup/plugin-node-resolve"
const path = require("path")
export default {
input: "src/utils/index.ts",
output: {
dir: "dist/zsduan-ui/utils", // ✅ 主输出目录
format: "esm",
sourcemap: false
},
plugins: [
resolve(),
commonjs(),
typescript({
declaration: false, // ✅ 不生成声明文件 因为我们用 tsc 进行了声明文件生成
declarationDir: path.resolve(__dirname, "../dist/zsduan-ui/utils/types"),
outDir: path.resolve(__dirname, "../dist/zsduan-ui/utils/types"),
emitDeclarationOnly: true // ✅ 只生成类型文件
})
]
}
```
### 7.建 scripts/create-utils-pkg.js 文件
#### 生成 package.json 文件
```javascript
// scripts/create-utils-pkg.js
const fs = require("fs-extra")
const path = require("path")
// 定义 utils 输出目录
const utilsDist = path.resolve(__dirname, "../dist/zsduan-ui/utils")
// 确保目录存在
async function createPackageJson() {
try {
await fs.ensureDir(utilsDist)
const pkg = {
name: "zsduan-ui/utils",
version: "1.0.0",
main: "index.js",
module: "index.js",
types: "index.d.ts",
sideEffects: false,
description: "Utility functions for zsduan-ui components library",
keywords: ["zsduan-ui", "utils", "fuzzySearch"],
author: "zs.duan",
license: "MIT"
}
const pkgPath = path.join(utilsDist, "package.json")
await fs.writeJson(pkgPath, pkg, { spaces: 2 })
console.log(`✅ 已生成 package.json 到 ${pkgPath}`)
} catch (err) {
console.error("❌ 生成 package.json 失败:", err)
}
}
createPackageJson()
```
### 8. 在根目录下的 package.json 文件中添加以下内容:
```json
{
// ... 其他配置项
"scripts": {
// ... 其他配置项
"build:utils": "tsc -p tsconfig.utils.json && rollup -c scripts/rollup-config-utils.js --bundleConfigAsCjs && node scripts/utilsbuild.js && node scripts/create-utils-pkg.js"
// ... 其他配置项
}
// ... 其他配置项
}
```
### 9.打包完成后的目录结构
```text
dist/
└── zsduan-ui/
├── utils/
├── types/
| ├── index.d.ts
| └── sum.d.ts
├── index.d.ts
├── index.js
└── package.json
```
### 10.使用方法
```javascript
import { sum } from "zsduan-ui/utils";
console.log(sum(1, 2));
```
### 到这里就完成了一个 npm 包的创建,你可以根据自己的需求进行扩展。
## 优化
### 1. 可以联合起来一起打包 在 跟根目录下 package.json 文件 增加以下脚本
```json
{
// ... 其他配置项
"scripts": {
// ... 其他配置项
"build:npm" : "npm run build:lib && npm run build:utils"
// ... 其他配置项
}
// ... 其他配置项
}
```
### 2.整体目录结构
```text
dist/
└── zsduan-ui/
├── lib/
| ├── demo.html
| ├── zsduan-ui.common.js
| ├── zsduan-ui.css
| ├── zsduan-ui.udm.js
| └── zsduan-ui.umd.min.js
├── types/
| ├── dzs-button.d.ts
| └── index.d.ts
├── utils/
| ├── types/
| | ├── index.d.ts
| | └── sum.d.ts
| ├── index.d.ts
| ├── index.js
| └── package.json
└── package.json
```
一个小前端
我是一个小前端

zs.duan@qq.com



重庆市沙坪坝


我的标签
小程序
harmonyOS
HTML
微信小程序
javaSrcipt
typeSrcipt
vue
uniapp
nodejs
react
防篡改
nginx
mysql
请求加解密
还没有人评论 快来占位置吧