記一次插件發(fā)布的采坑經(jīng)歷
在你需要導(dǎo)出多個組件然后打包給外部使用的時候,需要配置package.json和vite.config.js ,下面來說一些關(guān)鍵的點,防止下次再遇到不會配置
解釋幾個配置的意思
1."keywords": [
"vue",
"video",
"flv",
"webrtc"
],
含義:這是你庫的“標簽”或“關(guān)鍵詞”。
作用:當其他人在 npmjs.com 網(wǎng)站上搜索時,這些關(guān)鍵詞可以幫助他們發(fā)現(xiàn)你的庫。它相當于給你發(fā)布的包做了 SEO 優(yōu)化。你的這些關(guān)鍵詞(vue, video, flv, webrtc)非常精準,能有效吸引到目標用戶。
2. main, module, types (傳統(tǒng)入口字段)
在現(xiàn)代打包中,這些字段可以看作是為不支持 exports 字段的舊工具提供的向后兼容的入口。
"main": "./dist/zy-video-view.cjs.js",
含義:定義了包的 CommonJS (CJS) 入口文件。
誰會用它:主要被 Node.js 環(huán)境使用。當在 Node.js 項目中寫 const myLib = require('zy-video-view'); 時,Node.js 會去加載這個 cjs.js 文件。
module": "./dist/zy-video-view.es.js",
含義:定義了包的 ES Module (ESM) 入口文件。
誰會用它:主要被現(xiàn)代打包工具(如 Vite, Webpack, Rollup)使用。這些工具在構(gòu)建前端項目時,會優(yōu)先尋找 module 字段。使用 ESM 格式的好處是支持搖樹優(yōu)化 (Tree Shaking),可以移除未使用的代碼,減小最終打包體積。
"types": "./dist/zy-video-view.d.ts",
含義:定義了包的 TypeScript 類型聲明文件 的入口。
誰會用它:TypeScript 編譯器 (tsc) 和代碼編輯器(如 VS Code)。正是因為這個字段,當用戶 import { zyFlvVideoView } from 'zy-video-view'; 時,編輯器能提供代碼自動補全、類型檢查和屬性文檔提示。這是提升開發(fā)者體驗(DX)的關(guān)鍵。
3. exports (現(xiàn)代入口字段)
"exports": {
".": {
"types": "./dist/zy-video-view.d.ts",
"import": "./dist/zy-video-view.es.js",
"require": "./dist/zy-video-view.cjs.js"
},
"./dist/style.css": "./dist/style.css",
"./package.json": "./package.json"
},
含義:這是目前最推薦的、最權(quán)威的方式來聲明包的入口。當 exports 字段存在時,它會完全覆蓋 main、module、types 的作用。它像一個精確的“路由表”或“海關(guān)申報單”,嚴格控制了外部可以訪問你包里的哪些文件。
"." (主入口):
代表用戶直接導(dǎo)入包名,如 import ... from 'zy-video-view'。
"import": 當環(huán)境使用 import (ESM) 時,提供 es.js 文件。
"require": 當環(huán)境使用 require (CJS) 時,提供 cjs.js 文件。
"types": 為這個主入口指定類型聲明文件。
"./dist/style.css" (子路徑導(dǎo)出):
這是一個非常重要的配置!它允許用戶單獨導(dǎo)入你的樣式文件,例如 import 'zy-video-view/dist/style.css';。
如果沒有在 exports 中聲明這個路徑,用戶將無法訪問到 style.css,即使它存在于 dist 文件夾中。exports 默認會隱藏所有未聲明的文件。
"./package.json":
同理,允許外部工具訪問你包的 package.json 文件。為什么允許外部訪問package.json 呢?
因為:
1. 處理對等依賴 (peerDependencies) - 這是最重要的原因
你的庫很可能依賴于 Vue。你應(yīng)該在 package.json 中這樣聲明:
Generated json
這告訴用戶:“我的庫需要 Vue 3 才能運行,但你得自己安裝它,我不會幫你打包進來。”
當用戶安裝你的庫時,npm 或 pnpm 這樣的包管理器需要讀取你庫的 package.json 來檢查這個 peerDependencies 字段。
如果它能讀取到,它就會檢查用戶的項目里是否安裝了 Vue 3。如果沒有,它會給出一個警告,提醒用戶安裝。
如果你不導(dǎo)出 package.json,包管理器就無法讀取這個信息,也就無法給出這個重要的警告,可能會導(dǎo)致用戶的項目在運行時出錯。
2. 構(gòu)建工具的模塊解析 (Module Resolution)
像 Vite 或 Webpack 這樣的現(xiàn)代構(gòu)建工具在解析模塊時,有時會需要參考 package.json。例如,它們可能會檢查 "type": "module" 字段來確定如何處理 .js 文件。雖然 exports 字段中的 "import" 和 "require" 條件已經(jīng)提供了足夠的信息,但允許訪問 package.json 可以為更復(fù)雜的解析場景提供兼容性保障。
3. 調(diào)試和版本檢查
想象一下,一個用戶報告了你的庫的一個 bug。你可能會讓他運行一個診斷腳本,這個腳本需要知道他當前使用的 zy-video-view 的確切版本號。這個腳本可能會這樣做:
Generated javascript
// 在用戶的項目中運行
const myLibVersion = require('zy-video-view/package.json').version;
console.log('zy-video-view version:', myLibVersion);
Use code with caution.
JavaScript
如果你的 exports 允許訪問 package.json,這段代碼會成功運行并打印出版本號。
如果不允許,這段代碼會直接拋出 ERR_PACKAGE_PATH_NOT_EXPORTED 錯誤,調(diào)試將變得更加困難。
4. Monorepo (單一代碼庫) 工具
在 Monorepo 架構(gòu)中,像 pnpm workspaces 或 Turborepo 這樣的工具需要遍歷工作區(qū)內(nèi)所有包的 package.json 文件來構(gòu)建整個項目的依賴關(guān)系圖。如果某個包的 package.json 不可訪問,這些工具就可能無法正常工作。
4. files
Generated json
"files": [
"dist/*"
],
Use code with caution.
Json
含義:這是一個“白名單”,指定了當你執(zhí)行 npm publish 時,哪些文件或文件夾應(yīng)該被上傳到 NPM 倉庫。
作用:保持你發(fā)布的包干凈、輕量。它確保只有編譯后的、可直接使用的 dist 文件夾內(nèi)容會被上傳,而你的源代碼 (src)、配置文件 (vite.config.ts, .gitignore 等) 不會被包含進去。用戶下載你的包時,只會得到必要的文件。
這就是為什么發(fā)布之前要先build構(gòu)建一下的原因,沒有dist的時候,調(diào)用方下載這個包是沒法用的
接下來看看vite的配置部分,我的項目是一個視頻播放器組件,以此為例
rollupOptions: {
external: ["vue", "mpegts.js"], //不要把vue和mpegts.js這些依賴也打包進來
output: {
globals: {
//UMD下的全局變量名稱
vue: "Vue",
"mpegts.js": "mpegts",
},
exports: "named", // 使用命名導(dǎo)出避免警告
// 確保 CSS 文件被正確輸出
assetFileNames: (assetInfo) => {
// 處理 CSS 文件
if (assetInfo.type === "asset" && assetInfo.names) {
const name = assetInfo.names[0];
if (name && name.endsWith(".css")) {
return "style.css";
}
}
return "[name][extname]";
},
},
},
講一下assetFileNames:
1.背景問題: 當你構(gòu)建一個包含 CSS 的 Vue 組件庫時,Vite/Rollup 默認可能會為了避免緩存問題,給輸出的 CSS 文件名加上一個隨機哈希值,例如 style.a1b2c3d4.css。
對于庫的危害: 如果文件名是隨機的,你的庫的使用者就無法像下面這樣穩(wěn)定地導(dǎo)入樣式:
// 用戶希望這樣做,但如果文件名是隨機的,這就行不通了
import 'zy-video-view/dist/style.css';
Use code with caution.
解決方案: assetFileNames 配置允許你完全控制資源文件(assets)的輸出文件名。
你的這段代碼邏輯檢查每一個即將輸出的資源文件 (assetInfo)。
如果發(fā)現(xiàn)這個文件是一個 CSS 文件 (assetInfo.name.endsWith('.css')),它會強制將其文件名重命名為 style.css。
這樣,無論構(gòu)建多少次,輸出的 CSS 文件名永遠都是 style.css。
2.cssCodeSplit: false
背景問題: 如果你的庫里有多個組件(例如 zy-flv-video-view 和 zy-webrtc-video-view),并且它們各自有自己的 <style> 塊,Vite 默認可能會把它們的 CSS 分割成多個文件(Code Splitting)。
對于庫的危害: 一個組件庫通常不希望用戶去導(dǎo)入多個零散的 CSS 文件。用戶只想要一個全功能的樣式入口。
解決方案: 將 cssCodeSplit 設(shè)置為 false,它會告訴 Vite:“不要對 CSS 進行代碼分割。請把所有組件的所有樣式,全部合并到同一個 CSS 文件里去。”
? 核心收益: 確保了所有樣式都被打包進一個單一的文件中。這個配置與上面的 assetFileNames 完美配合:
cssCodeSplit: false 負責將所有 CSS 合并成一個文件。
assetFileNames 負責將這個合并后的文件命名為 style.css。
最后看一下如何暴露給外部才可以實現(xiàn)既能用對象方式使用還能使用解構(gòu)方式使用
import ZyFlvVideo from "./components/flv_video/index.vue";
import ZyWebRtcVideo from "./components/webrtc_video/index.vue";
import { withInstall } from "./utils/with-install";
// 確保 VideoView 的類型是 DefineComponent
const zyFlvVideoView = withInstall(ZyFlvVideo);
const zyWebRtcVideoView = withInstall(ZyWebRtcVideo);
// 創(chuàng)建一個包含所有組件的對象作為默認導(dǎo)出
const ZyVideoView = {
zyFlvVideoView,
zyWebRtcVideoView,
install(app: any) {
app.component("zy-flv-video-view", zyFlvVideoView);
app.component("zy-webrtc-video-view", zyWebRtcVideoView);
},
};
// 默認導(dǎo)出
export default ZyVideoView;
// 命名導(dǎo)出
export { zyFlvVideoView, zyWebRtcVideoView };
// 類型導(dǎo)出
export type { IFlvVideoProps } from "./components/flv_video/type";
export type { IWebRtcPlayerProps } from "./components/webrtc_video/type";
ZyVideoView對象可以寫成
const ZyVideoView = {
install(app: any) {
app.component("zy-flv-video-view", zyFlvVideoView);
app.component("zy-webrtc-video-view", zyWebRtcVideoView);
},
};嗎?
是的,可以去掉。從技術(shù)上講 app.use() 功能不會受到任何影響。
去掉后,代碼變成這樣:
Generated typescript
// ... zyFlvVideoView 和 zyWebRtcVideoView 的定義 ...
// 簡化后的默認導(dǎo)出對象
const ZyVideoView = {
install(app: any) {
// 這里的 zyFlvVideoView 和 zyWebRtcVideoView 仍然可以被訪問到
// 因為它們是在同一個文件作用域內(nèi)定義的常量
app.component("zy-flv-video-view", zyFlvVideoView);
app.component("zy-webrtc-video-view", zyWebRtcVideoView);
},
};
export default ZyVideoView;
Use code with caution.
TypeScript
當用戶執(zhí)行 app.use(ZyVideoView) 時,Vue 會調(diào)用這個對象的 install 方法。由于 zyFlvVideoView 和 zyWebRtcVideoView 這兩個常量在當前文件的作用域中是存在的,所以 install 方法可以毫無問題地訪問它們并完成組件的全局注冊。
那么,為什么通常要保留它們呢?
盡管從 install 的角度看它們是多余的,但保留它們提供了一個重要的額外功能:將默認導(dǎo)出的對象變成一個“命名空間”或“工具箱”。
這為庫的使用者提供了另一種靈活的使用方式。
讓我們對比一下保留和去掉的區(qū)別:
情況一:保留屬性(你的原始代碼)
Generated typescript
// 原始代碼
const ZyVideoView = {
zyFlvVideoView,
zyWebRtcVideoView,
install(app: any) { /* ... */ },
};
export default ZyVideoView;
Use code with caution.
TypeScript
使用者可以這樣做:
Generated typescript
import ZyVideo from 'your-library-name'; // 默認導(dǎo)入
// 用法1:全局安裝 (最常見)
app.use(ZyVideo);
// 用法2:手動、有選擇性地注冊或使用
// 用戶可以通過默認導(dǎo)出的對象,直接訪問到具體的組件定義
const flvPlayerComponent = ZyVideo.zyFlvVideoView;
const webrtcPlayerComponent = ZyVideo.zyWebRtcVideoView;
// 可以在某個組件里局部注冊
export default {
components: {
'my-special-flv-player': flvPlayerComponent
}
}
// 甚至可以在 JSX 或渲染函數(shù)中使用
// import { h } from 'vue'
// h(ZyVideo.zyFlvVideoView, { props... })
Use code with caution.
TypeScript
優(yōu)點: 默認導(dǎo)出的 ZyVideo 對象本身就是一個完整的“工具箱”,包含了庫的所有部分。用戶導(dǎo)入一次,就可以訪問到所有東西,非常方便和直觀。
情況二:去掉屬性(你提議的修改)
Generated typescript
// 修改后的代碼
const ZyVideoView = {
install(app: any) { /* ... */ },
};
export default ZyVideoView;
Use code with caution.
TypeScript
使用者的行為會發(fā)生什么變化:
Generated typescript
import ZyVideo from 'your-library-name'; // 默認導(dǎo)入
// 用法1:全局安裝 (仍然有效)
app.use(ZyVideo);
// 用法2:嘗試訪問組件定義
const flvPlayerComponent = ZyVideo.zyFlvVideoView; // !!!錯誤!!!
// Uncaught TypeError: Cannot read properties of undefined (reading 'zyFlvVideoView')
Use code with caution.
TypeScript
缺點: ZyVideo 對象現(xiàn)在變成了一個“黑盒”,它除了能被 app.use() 之外,幾乎沒有其他用處。如果用戶想要獲取單個組件的定義(比如為了局部注冊或在渲染函數(shù)中使用),他們將必須同時使用命名導(dǎo)入:
Generated typescript
import ZyVideo, { zyFlvVideoView } from 'your-library-name';
// 現(xiàn)在必須從命名導(dǎo)入中獲取組件
const flvPlayerComponent = zyFlvVideoView;
Use code with caution.
TypeScript
這破壞了默認導(dǎo)出作為“完整集合”的便利性,可能會讓用戶感到困惑。
結(jié)論
可以去掉嗎? 可以,核心的 app.use() 功能不受影響。
應(yīng)該去掉嗎? 強烈不推薦。
保留 zyFlvVideoView 和 zyWebRtcVideoView 作為 ZyVideoView 對象的屬性,是一種遵循社區(qū)最佳實踐的設(shè)計模式。它讓你的庫 API 更加健壯和靈活,為使用者提供了“開箱即用”的便利,同時保留了高級用法(如手動訪問和注冊)的可能性。
這就像你買一個工具箱,你既希望店家能幫你把所有工具都安裝好(app.use),也希望自己能打開箱子,單獨拿出某一把螺絲刀來用(ZyVideo.zyFlvVideoView)。你的原始代碼完美地實現(xiàn)了這兩點。

浙公網(wǎng)安備 33010602011771號