Vue2 + Vue CLI 打包成 NPM 包(支持组件 + 工具类)

本指南将带你从零开始,使用 Vue CLI 打包一个 支持组件 + 工具类 的 npm 包,并提供完整的打包脚本、配置文件、版本限制提示等。

这里是用 zsduan-ui 做的一个示例 你可以把 zsduan-ui 替换成自己npm包的名称

前期工作

用vue cli 创建项目 请选用vue2 ts 版本

Q:为什么用ts?A:因为你不确定创建的npm包是否会用到ts 所以选择ts

vue create vue-npm-demo
cd vue-npm-demo
npm i
npm run serve

打包npm包组件

1. 创建组件

创建组件文件 src/components/dzs-button.vue
<!-- index.vue -->
<template>
    <div>
        <button class="dzs-button" @click="handleClick">{{text}}</button>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component({
    name : 'dzs-button'
})
export default class dzsButton extends Vue {
    @Prop({
        type: String,
        default: ''
    })
    text!: string


    handleClick(event: Event) {
        this.$emit('click', event)
    }
}
</script>
// index.ts
import dzsButton from "./index.vue";
export declare type dzsDialogInstance = InstanceType<typeof dzsButton>
export default dzsButton;
export const dzsDialogPlugin = {
    install : <T extends Record<string, any>>(Vue : any , options ?: T) =>{
        Vue.component(dzsButton.name, dzsButton)
    },
    version : '20250725'
}
export { dzsButton }
// 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 };

目录结构

my-component-library/
└── src/
     └── components/         
           ├── dzs-button.d.ts
           ├── index.ts
           └── index.vue

2.修改 tsconfig.json 文件

新增下面的配置项
"compilerOptions": {
    // ... 其他配置项
    "declaration": true,
    "declarationDir": "./dist/zsduan-ui/types",
    "emitDeclarationOnly": true,
    "outDir": "./dist/zsduan-ui",
    // ... 其他配置项
}

3.修改 vue.config.js 文件

新增下面的配置项
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 文件

新增下面的配置项
{
    // ... 其他配置项
    "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:为了检查依赖项是否符合
// 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 打包文件

用于对组件的打包

// 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 文件进行复制
// 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

{
    // ... 其他配置项
    "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"
        // ... 其他配置项
    }
    // ... 其他配置项
}
打包完成后的目录结构
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 全局引用
// main.js / main.ts
import zsduanUI from 'zsduan-ui'
Vue.use(zsduanUI)
9.2 全局注册组件
import {dzsButton} from 'zsduan-ui'
Vue.component('dzs-button',dzsButton)
9.3 局部引用
import {dzsButton} from 'zsduan-ui'
export default {
    components: {
        dzsButton
    }
}

到这里 组件已经打包完成

utils 工具类打包

1. 创建工具TS

// utils/sum.ts
type Sum = (a: number, b: number) => number
export const sum: Sum = (a, b) => {
    return a + b
}

2. 创建入口文件

// utils/index.ts
export { default as sum } from './sum'

目录结构

src/
└── utils/
    ├── index.ts
    └── sum.ts

3. 安装打包依赖

npm install --save-dev rollup @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve

4.创建 tsconfig.utils.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
// 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 文件
// 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 文件
// 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 文件中添加以下内容:

{
    // ... 其他配置项
    "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.打包完成后的目录结构

dist/
└── zsduan-ui/
    ├── utils/
    ├── types/
    |   ├── index.d.ts
    |   └── sum.d.ts
    ├── index.d.ts
    ├── index.js
    └── package.json

10.使用方法

import { sum } from 'zsduan-ui/utils';
console.log(sum(1, 2));

到这里就完成了一个 npm 包的创建,你可以根据自己的需求进行扩展。

优化

1. 可以联合起来一起打包 在 跟根目录下 package.json 文件 增加以下脚本

{
    // ... 其他配置项
    "scripts": {
        // ... 其他配置项
        "build:npm" : "npm run build:lib && npm run build:utils"
        // ... 其他配置项
    }
    // ... 其他配置项
}

2.整体目录结构

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

个人主页
个人总结

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐