Vue2 + Vue CLI 打包成 NPM 包(支持组件 + 工具类)
@zs.duan
Vue2 + Vue CLI 打包成 NPM 包(支持组件 + 工具类)
阅读量:364
2025-08-05 16:48:22
# 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 ```
评论:

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