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
        
            请求加解密
        

还没有人评论 快来占位置吧