Introduction & 前言 
背景來至 unsplash.com 的 Chris Ried 
 
當使用了 JavaScript  和 Node.js  這麼久,你肯定使用過 npm install,但這些 npm  上的套件到底是怎麼產生出來的,你可曾想過嗎?
當你開發了自己覺得超屌超猛或是別人肯定也會想試試看的程式碼之後,你知道怎麼讓大家也透過 npm install 享受到嗎?
如果你有這些問題,看這篇文章就對了,這篇文章將以 React.js  的元件當作出發點,帶你一窺怎麼透過 Webpack  把套件發布到 npm  上,讓大家也能夠安裝你的套件。
如果不會 React.js  也還是可以參考本篇文章,之後將 React.js  的部分換成你熟悉的框架或是純 JavaScript  也可以!
 
後續筆者也會研究其他的打包工具,例如 rollup  或者 browserify 
 
整篇文章是筆者自己的心得,如果文內有錯誤的地方,還請盡情指出。 
該篇文章參考了其他的教學,加上自己碰到的坑,整理出心得並作為筆記釋出,其中也補足了其他教學文中少的部分,而看文章的你一定會有自己的需求,請先別急,看完文章後請截取自己需要的部分,如果你覺得這篇文章對你有幫助,還請將文章分享給更多人知道,或者結合自己的心得,在發布其他更多的文章幫助更多的開發者。
Summary & 摘要 
在開始前首先要非常非常感謝 稀土掘金 的 黑土豆 一篇 (建议收藏)使用React+Typescript开发组件并发布到npm仓库  文章,沒有這篇文章大概筆者還在跟 ChatGPT 有來沒回的 
 
本篇文章預設學習前的基本條件需求:
知道 React.js(不會使用也沒關係,可以換成自己熟悉的框架或者純 JavaScript 都行) 
電腦已經有 Node.js 環境且有 npm 
知道終端機及 npm 的基本操作(含安裝、初始化 package.json) 
大概知道 Webpack(如果不懂可以參考筆者之前的文章 [Tool Note] — 關於Webpack #1 - 第一次就上手  或是線上其他文章) 
一顆熱忱的心 -> 超級重要,碰到坑千萬不要氣餒 
 
本篇文章將有以下幾個步驟:
前前前言 專案初始化 安裝相關依賴 撰寫元件 Babel及Webpack設定 完善package_json TypeScript編譯 建立Demo 編譯以及Link的使用 發布前的測試 編寫README.md 發布套件 額外問題  
本篇設定將以產出 ESM  為例,如果你想產出 CJS  可以參考筆者開頭提及的文章 (建议收藏)使用React+Typescript开发组件并发布到npm仓库 ,本篇文章也是因為參考的文章沒有 TypeScript  開啟紀錄一路
 
前前前言 在開始之前必須先說一下為什麼會開始做這項研究,一切開始於筆者在公司的專案,因為專案業務需求,在某一次的活動中需要刮刮樂的功能
對於前端工程師來說,如果時程不是太趕的情況下,透過需求有機會可以實作到一些平常碰不到的技術,但因為當時時間緊迫的情況下,筆者找了兩款刮刮樂的套件來使用,對我來說有輪子了,時程又趕的情況下,自行造輪子不太合理
因為筆者的專案框架為 React.js  ,故找到的兩款套件為 react-scratchcard-v2 scratch-card Canvas  和 React.js  製作了這個刮刮卡套件
而筆者完成功能後,開始想,會不會有其他人也剛好有這個需求,並且找不到合適的套件,所以筆者就決定將自己製作的 React.js  元件發布到 npm  上
或許有剛好符合功能的套件,但筆者在幾度搜尋之下只找到這兩個比較符合專案需求的套件,如果你也有找到不錯且符合筆者需求的套件歡迎下面留言分享一下
 
在接下來的教學之前,筆者先將專案的 Github 附上,源碼的部分其實不多,也一併分享了,想要取用也歡迎
專案初始化 在進行發布 NPM  套件之前,我們需要先初始化一個專案,該專案最終只會輸出一個 index  給安裝的人引入使用,所以請從新的資料夾開啟你的專案設定
這邊接下來都會用筆者建立的 react-scratch-ticket  刮刮樂套件作為舉例
1 2 3 mkdir  react-scratch-ticketcd  react-scratch-ticketnpm init -y 
如果你知道 package.json 的詳細設定,也可以使用 npm init,不要輸入 -y,關於設定可以參考 史上最強套件管理 - NPM , npm init 與 npm install (Day11) 
 
不使用 -y  會有上圖這種詢問回答的步驟
之後會產生一個 package.json  的檔案,接下來有安裝套件就會再產生 package-lock.json  ,這邊就不再多做介紹。
安裝相關依賴 安裝 React.js 依賴 因為我們專案是要發布 React.js  的套件,這邊需要安裝關於 React.js  需要的資源,當然如果你使用其他框架例如 Vue.js  或者純 JavaScript  也行
1 npm i react react-dom -D 
接下來一切安裝的東西基本上都會加上 -D,因為我們的套件沒有需要在執行時會依賴到的資源,如果你有的話記得要拿掉
 
安裝 Webpack 依賴 由於我們會用到 Webpack  進行打包,這邊也請一起安裝,如果你使用其他打包工具的話請自行安裝
1 npm i webpack webpack-cli webpack-dev-server webpack-merge -D 
安裝 TypeScript 依賴 因為筆者開發使用 TypeScript(以下開始簡稱 TS)  ,這邊也需要安裝要打包 TS  的相關依賴
安裝 CSS 依賴 也許你開發會使用到 CSS  的部分,這邊也可以一併安裝上,不安裝的話後續再 Webpack  設定請記得拿掉 CSS  編譯相關的設定
1 npm i postcss postcss-loader postcss-preset-env style-loader css-loader sass-loader node-sass mini-css-extract-plugin -D 
安裝 babel 依賴 關於 babel  的介紹可以參考 Day 18 - 為什麼要用 Babel 
1 npm i @babel/cli @babel/core @babel/preset-env @babel/preset-react -D 
安裝 React.js 的 TypeScript依賴 因為需要用到 TS  所以也要安裝關於 React.js  的 TS  依賴
1 npm i @types/react @types/react-dom ts-loader @babel/preset-typescript -D 
一切安裝完畢後現在 package.json  內的 devDependencies  應該長得像下面一樣,如果有少什麼再自行評斷要新增或刪除什麼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 "devDependencies" :  {   "@babel/cli" :  "^7.26.4" ,    "@babel/core" :  "^7.26.9" ,    "@babel/preset-env" :  "^7.26.9" ,    "@babel/preset-react" :  "^7.26.3" ,    "@babel/preset-typescript" :  "^7.26.0" ,    "@types/react" :  "^19.0.10" ,    "@types/react-dom" :  "^19.0.4" ,    "babel-loader" :  "^10.0.0" ,    "css-loader" :  "^7.1.2" ,    "mini-css-extract-plugin" :  "^2.9.2" ,    "node-sass" :  "^9.0.0" ,    "postcss" :  "^8.5.3" ,    "postcss-loader" :  "^8.1.1" ,    "postcss-preset-env" :  "^10.1.5" ,    "react" :  "^19.0.0" ,    "react-dom" :  "^19.0.0" ,    "sass-loader" :  "^16.0.5" ,    "style-loader" :  "^4.0.0" ,    "ts-loader" :  "^9.5.2" ,    "typescript" :  "^5.8.2" ,    "webpack" :  "^5.98.0" ,    "webpack-cli" :  "^6.0.1" ,    "webpack-dev-server" :  "^5.2.0" ,    "webpack-merge" :  "^6.0.1"  } 
撰寫元件 一切就緒之後,我們先開始撰寫元件,一些設定檔我們可以等等再開始加入,這邊先以刮刮樂為例子
關於刮刮樂的源碼,可以參考 react-scratch-ticket  內的 src 
 
首先我們需要在專案內建立 src  資料夾,並且在專案內創建 index.tsx  ,目前的架構大概會如下
1 2 3 4 5 6 ├── node_modules # 安裝套件後生成  ├── src # 你要發布的套件代碼精華請放這      └── index.tsx  # 確保至少有一個套件進入點  ├── README.md ├── package-lock .json └── package.json 
你的專案如果不是 React.js  也可以創建 index.ts  或者沒有用 TS  也可以創建 index.js  ,最終我們要打包套件會需要一個入口點的
因為筆者預期之後會擴增刮刮樂可以引入的組件類型,例如可以一張刮刮樂多個可刮區域,所以筆者在 index.tsx  內的做法是透過他輸出 TS  以及不同元件,方便後續引用
1 2 3 4 5 6 7 export  { default  as  ReactScratchTicket  } from  './component/ReactScratchTicket' ;export  type  { ScratchTicketImperative  } from  './component/ReactScratchTicket' ;
元件的部分這邊後續筆者會再補充自己的寫作想法,目前以介紹發布流程為主
 
Babel及Webpack設定 在放入我們要發布的元件後,我們先把一些設定檔建立好
建置 Babel 設定 先從最簡單的 babel  開始吧,這項東西是 JavaScript  的轉譯器,簡單解釋是他可以將 ECMAScript2015(ES6)  及以上的程式碼轉為向下相容的版本,讓較舊的瀏覽器也能解讀
先在根目錄創建 .babelrc  ,並在裡面寫上下面內容
1 2 3 4 5 6 7 {   "presets" :  [      "@babel/preset-env" ,      "@babel/preset-react" ,      "@babel/preset-typescript"    ]  } 
建置 Webpack 設定 比較重頭戲的部分都在 Webpack  ,後續如果編譯失敗,或者實際安裝套件並且使用有問題,八九成都是這邊出問題
這部分筆者參考 (建议收藏)使用React+Typescript开发组件并发布到npm仓库 config  的資料夾,然後分別建立幾個檔案
建立 webpack.base.js  並寫入以下內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export  default  {  resolve : {     extensions : ['.js' , '.jsx' , '.ts' , '.tsx' ],   },   module : {     rules : [       {         test : /(\.js(x?))|(\.ts(x?))$/ ,         use : [           {              loader : 'babel-loader'            }         ],         exclude : /node_modules/ ,       }     ]   }, }; 
建立 webpack.dev.config.js  並寫入以下內容,對於 demo  的部分等等會建立
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import  path from  'path' ;import  { fileURLToPath } from  'url' ;import  { merge } from  'webpack-merge' ;import  baseConfig from  './webpack.base.js' ;const  __filename = fileURLToPath (import .meta .url );const  __dirname = path.dirname (__filename);const  devConfig = {  mode : 'development' ,   entry : path.join (__dirname, "../demo/src/index.tsx" ),   output : {     path : path.join (__dirname, "../demo/src/" ),     filename : "dev.js" ,   },   module : {     rules : [       {         test : /.s[ac]ss$/ ,         exclude : /.min.css$/ ,         use : [           { loader : 'style-loader'  },           {             loader : 'css-loader' ,             options : {               modules : {                 mode : "global"                }             }           },           {             loader : 'postcss-loader' ,             options : {               postcssOptions : {                 plugins : [                   [                     'postcss-preset-env' ,                   ],                 ],               },             },           },           { loader : 'sass-loader'  }         ]       },       {         test : /.min.css$/ ,         use : [           { loader : 'style-loader'  },           { loader : 'css-loader'  }         ]       }     ]   },   devServer : {     static : path.join (__dirname, '../demo/src/' ),     compress : true ,     host : '127.0.0.1' ,     port : 8001 ,      open : true     }, }; const  exportConfig = merge (devConfig, baseConfig);export  default  exportConfig;
建立 webpack.prod.config.js  並寫入以下內容,這份才是輸入後用的,有可能你在本地使用自己寫的套件內容沒問題,但是編譯後放到其他專案去使用就出問題,大部分都需要在這邊解決
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import  path from  'path' ;import  { fileURLToPath } from  'url' ;import  { merge } from  'webpack-merge' ;import  baseConfig from  './webpack.base.js' ;import  MiniCssExtractPlugin  from  "mini-css-extract-plugin" ;const  __filename = fileURLToPath (import .meta .url );const  __dirname = path.dirname (__filename);const  esmConfig = {  mode : 'production' ,   entry : path.join (__dirname, "../src/index.tsx" ),   output : {     path : path.join (__dirname, "../dist/" ),     filename : "index.esm.js" ,     libraryTarget : 'module' ,     module : true ,   },   experiments : {     outputModule : true ,   },   module : {     rules : [       {         test : /.s[ac]ss$/ ,         exclude : /.min.css$/ ,         use : [           { loader : MiniCssExtractPlugin .loader  },           {             loader : 'css-loader' ,             options : {               modules : {                 mode : "global"                }             }           },           {             loader : 'postcss-loader' ,             options : {               postcssOptions : {                 plugins : [                   [                     'postcss-preset-env' ,                   ],                 ],               },             },           },           { loader : 'sass-loader'  }         ]       },     ]   },   plugins : [       new  MiniCssExtractPlugin ({           filename : "index.min.css"        })   ],   externals : {       react : 'react' ,       'react-dom' : 'react-dom'    },   externalsType : 'module' , }; export  default  merge (esmConfig, baseConfig);
再次提醒,如果你沒有用到 scss  或者相關的套件,請記得要移除,反之如果有用到的部分,請自行寫入設定
 
完善package_json 現在我們必須完整我們的 package.json  ,否則發布會失敗
基於範例套件的 package.json  如下,請補上你缺少的部分,這邊如果你不是使用 ESM  輸出,請再次參考 (建议收藏)使用React+Typescript开发组件并发布到npm仓库 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 {   "name" :  "react-scratch-ticket" ,    "version" :  "1.0.0" ,    "main" :  "./dist/index.esm.js" ,    "types" :  "./dist/types/index.d.ts" ,    "module" :  "./dist/index.esm.js" ,    "type" :  "module" ,     "scripts" :  {      "test" :  "echo \"Error: no test specified\" && exit 1" ,      "dev" :  "webpack-dev-server --config config/webpack.dev.config.js" ,      "build" :  "webpack --config config/webpack.prod.config.js" ,      "build:clean" :  "rimraf dist && webpack --config config/webpack.prod.config.js && npx tsc" ,       "publish" :  "npm publish --registry https://registry.npmjs.org"     } ,    "files" :  [      "/dist" ,      "README*.md" ,      "LICENSE"    ] ,    "keywords" :  [      "react" ,      "scratch" ,      "ticket" ,      "scratch-ticket" ,      "react-scratch-ticket" ,      "card" ,      "scratch-card" ,      "react-scratch-card" ,      "canvas" ,      "scratch-canvas" ,      "react-scratch-canvas"    ] ,    "author" :  "RexHung0302" ,    "license" :  "MIT" ,    "description" :  "This is a scratch ticket component, basic on React" ,    "homepage" :  "https://github.com/RexHung0302/react-scratch-ticket#readme" ,    "repository" :  {      "type" :  "git" ,      "url" :  "git+https://github.com/RexHung0302/react-scratch-ticket.git"    } ,    "bugs" :  {      "url" :  "https://github.com/RexHung0302/react-scratch-ticket/issues"    } ,    "engines" :  {      "npm" :  ">=10.0.0" ,      "node" :  ">=20.0.0"    } ,    "dependencies" :  { } ,    "devDependencies" :  {         }  } 
version: 每次發布新的內容到 npm 去,在 npm run build 之後,請記得改版 
main: 作為你發布套件後,使用者引入你套件的入口是哪個檔案,這個檔案在這個筆記中是透過 webpack 產出的 
types: 因為筆者輸出的套件是使用 ESM,這邊需要加上這個設定,告知專案這個套件的 TS 定義去哪裡找 
files: 發布套件後只有這邊設定到的檔案會發布上去,其餘的檔案都不會被發布到 npm 倉庫中,可參考下圖一 
keywords: 套件的關鍵字,會出現在 npm 搜尋列表小註解,以及使用者搜尋時會 mapping 的關鍵字,可參考下圖二 
description: 套件的說明,會出現在 npm 搜尋列表小註解,可參考下圖二 
 
TypeScript編譯 開始這步驟前,筆者參考此篇文章解決許多 TS  問題 - 產生TypeScript的declare檔案 
為什麼我們需要 TS  編譯呢?如果你安裝套件的環境是使用 TS  ,就一定要做這件事情,確保我們要發布的套件內輸出後的資料夾含有 index.d.ts 
如果沒有就會發生像上圖的事情,找不到套件的定義
 
請先輸入以下指令,我們需要建立 TS  的設定檔案
如果發生 tsc  找不到的問題,可以將 TS  安裝到全域環境
如果有產生出來或者沒有產生出來,都請在根目錄確保有 tsconfig.json  這個檔案,內容請換上下面的部分,或者可以依照個人喜好修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 {   "compilerOptions" :  {      "sourceMap" :  true ,      "target" :  "ESNext" ,       "module" :  "ESNext" ,       "moduleResolution" :  "node" ,      "noEmitOnError" :  true ,      "lib" :  [ "es2017" ,  "DOM" ] ,      "strict" :  true ,      "esModuleInterop" :  false ,      "outDir" :  "dist" ,      "rootDir" :  "./src" ,      "allowJs" :  true ,      "noImplicitAny" :  true ,      "resolveJsonModule" :  true ,      "allowSyntheticDefaultImports" :  true ,      "jsx" :  "react" ,      "skipLibCheck" :  true ,      "forceConsistentCasingInFileNames" :  true ,      "declaration" :  true ,      "declarationMap" :  true ,      "declarationDir" :  "./dist/types" ,      "emitDeclarationOnly" :  false ,    } ,    "include" :  [ "src" ] ,    "exclude" :  [ "node_modules" ,  "dist" ]  } 
其中尤其是 declaration、declarationMap 及 declarationDir  這三項極其重要,如果沒有這三個,你不會在執行編譯後產出 index.d.ts File 
這邊的 declarationDir  輸出路徑,請記得修改的話,也要一起修改 package.json  的 type  設定,兩邊一定要對得上
都設定好之後目前專案大概長這樣
1 2 3 4 5 6 7 8 9 10 11 12 ├── config  # Webpack 的配置      ├── webpack.base .js     ├── webpack.dev.config.js     └── webpack.prod.config.js ├── node_modules # 安裝套件後生成  ├── src # 你要發布的套件代碼精華請放這      └── index.tsx  # 確保至少有一個套件進入點  ├── README.md ├── .babelrc # babel 的配置  ├── tsconfig.json # TS 的配置  ├── package-lock .json └── package.json 
這邊先不要急著執行編譯指令,我們接下來將會建置 Demo  的部分,透過本地展示你這個專案的內容
建立Demo 在專案根目錄請創建 demo  資料夾,以及 src  ,裡面請放上 index.tsx  , 裡面可以引入你要發布的套件代碼精華 src/index.tsx
以筆者的 demo  為例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import  React  from  "react" ;import  { ReactScratchTicket  } from  "../../src/index" ;import  ReactDOM  from  "react-dom/client" ;import  useIndexController from  "./hook/useIndexController" ;import  './style.scss' ;const  App  = (  const  { prizeInfo, scratchTicketRef, completeHandler, initDoneHandler, resetDoneHandler, clickResetBtnHandler, clickClearCardBtnHandler } = useIndexController ();   return  (     <div  className ="container" >        <h2  className ="font-bold" > React Scratch Ticket Demo</h2 >        <p  className ="container__title" > 100X</p >        <div  className ="content" >          <ReactScratchTicket             ref ={scratchTicketRef}            containerClassName ="scratch-ticket-container"            brushSize ={10}            width ={309}            height ={52}            childrenCenter            maskingLayerImg ='https://picsum.photos/309/52'            maskingLayerColor ="yellow"            finishPercent ={70}            onComplete ={completeHandler}            onInitDone ={initDoneHandler}            onResetDone ={resetDoneHandler}          >           {prizeInfo.name}         </ReactScratchTicket >        </div >        <div  className ="buttons" >          <button  onClick ={clickResetBtnHandler}  className ="button" > Reset</button >          <button  onClick ={clickClearCardBtnHandler}  className ="button" > Clear Card</button >        </div >      </div >    ); }; ReactDOM .createRoot (document .getElementById ('root' )!).render (<App  /> 
另外還需要創建 index.html  來預覽畫面,內容為下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!DOCTYPE html > <html  lang ="en" > <head >     <meta  charset ="UTF-8" >           <title > react-scratch-ticket-demo</title >       <style >          html , body , p  {             margin : 0 ;             padding : 0 ;         }      </style > </head > <body >     <div  id ="root" > </div >      <script  src ="dev.js" > </script >  </body > </html > 
根據 (建议收藏)使用React+Typescript开发组件并发布到npm仓库 dev.js  不會實際在 demo/src  底下產生 dev.js  ,打包好的文件是在內容存,所以實際推上去 Github  並不能直接透過 index.html  預覽,這點筆者還在研究當中
 
都設定好之後目前專案大概長這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ├── config  # Webpack 的配置      ├── webpack.base .js     ├── webpack.dev.config.js     └── webpack.prod.config.js ├── demo # 本地開發預覽      └── src         ├── index.tsx         ├── index.html          └── index.scss ├── node_modules # 安裝套件後生成  ├── src # 你要發布的套件代碼精華請放這      └── index.tsx  # 確保至少有一個套件進入點  ├── README.md ├── .babelrc # babel 的配置  ├── tsconfig.json # TS 的配置  ├── package-lock .json └── package.json 
如果有修改 demo  的相關配置,請記得一定要一併修改 /config/webpack.dev.config.js 
 
這時候可以輸入 npm run dev 來查看你的成果了
編譯以及Link的使用 編譯 
當一切就緒,我們可以執行 npm run build:clean,這行指令寫在 package.json  當中,主要是要刪除舊的編譯過的檔案,然後編譯新的 JS  還有 TS  宣告內容
執行後就能看到資料夾多出了 /dist 資料夾,而我們 package.json  也把 main、types  以及 module  都指向 /dist
這時候可以使用 npm link  指令,這個指令是把打包後的組件引入到本機全局的 node_modules  資料夾之中
輸入之後我們就可以到 demo/src 資料夾中,輸入以下指令
1 2 cd  demo/srcnpm link  react-scratch-ticket 
請記得上面的 react-scratch-ticket  要換成你 package.json  的 name 
接著修改 demo/src/index.tsx 的內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import  React  from  "react" ;import  { ReactScratchTicket  } from  "react-scratch-ticket" ; import  ReactDOM  from  "react-dom/client" ;import  useIndexController from  "./hook/useIndexController" ;import  './style.scss' ;const  App  = (  const  { prizeInfo, scratchTicketRef, completeHandler, initDoneHandler, resetDoneHandler, clickResetBtnHandler, clickClearCardBtnHandler } = useIndexController ();   return  (     <div  className ="container" >        <h2  className ="font-bold" > React Scratch Ticket Demo</h2 >        <p  className ="container__title" > 100X</p >        <div  className ="content" >          <ReactScratchTicket             ref ={scratchTicketRef}            containerClassName ="scratch-ticket-container"            brushSize ={10}            width ={309}            height ={52}            childrenCenter            maskingLayerImg ='https://picsum.photos/309/52'            maskingLayerColor ="yellow"            finishPercent ={70}            onComplete ={completeHandler}            onInitDone ={initDoneHandler}            onResetDone ={resetDoneHandler}          >           {prizeInfo.name}         </ReactScratchTicket >        </div >        <div  className ="buttons" >          <button  onClick ={clickResetBtnHandler}  className ="button" > Reset</button >          <button  onClick ={clickClearCardBtnHandler}  className ="button" > Clear Card</button >        </div >      </div >    ); }; ReactDOM .createRoot (document .getElementById ('root' )!).render (<App  /> 
Link 這部分可以選擇性操作,不做的話也可以
如果有出現錯誤或是找不到,可以使用 npm unlink react-scratch-ticket 再重新 link  一次就行
而依據查詢到的資料,只要後續再跑過 npm run build 之後,請重新執行 npm link,而 link  的時候請務必注意你的目錄位置
關於 npm link  的相關文章可參考 如何使用 npm link 進行 node module 測試 
後續如果你有需要在本地安裝你推的套件,根據上述文章的內容,會有需要注意的部分
發布前的測試 在發布前我們會想要測試一下編譯出來的檔案,這時候我們可以透過使用 npm pack 的方式打包出你套件
你會發現根目錄出現一個帶有跟你 package.json  定義同樣 version  的檔案出現
這時候你可以把你打包出來的 {套件名稱}-{版本號}.tgz 複製起來,然後貼到你要使用的專案去
到你要使用的專案輸入 npm i ./{套件名稱}-{版本號}.tgz,之後就可以跟一般使用套件一樣 import { ReactScratchTicket } from 'react-scratch-ticket';
如果至此沒問題,那就可以準備發布了!
編寫README.md 這部分可以參考 (建议收藏)使用React+Typescript开发组件并发布到npm仓库 四、编写Readme文档  ,透過 readme-md-generator package.json  設定去產生出 README.md 
在使用之前,可以先建立 LICENSE  檔案,內容可以參考其他的文章,或是筆者的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 MIT License Copyright (c) {更換成你的名稱} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
初始化git 請初始化你的 git  ,後續這個套件會推到你的倉庫去,而 npm  的介紹網頁上也會透過你 package.json  的設定連結到你的 github  ,如果不知道怎麼初始化,可以參考 [Git] 初始設定 
然後請建立 .gitignore 檔案,內容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /node_modules .vscode /dist .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* *.tgz .idea 
都設定好之後目前專案大概長這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ├── config  # Webpack 的配置      ├── webpack.base .js     ├── webpack.dev.config.js     └── webpack.prod.config.js ├── demo # 本地開發預覽      └── src         ├── index.tsx         ├── index.html          └── index.scss ├── node_modules # 安裝套件後生成  ├── src # 你要發布的套件代碼精華請放這      └── index.tsx  # 確保至少有一個套件進入點  ├── README.md ├── .gitignore ├── LICENSE  ├── react-scratch-ticket-1.1 .4 .tgz  ├── README.md ├── .babelrc # babel 的配置  ├── tsconfig.json # TS 的配置  ├── package-lock .json └── package.json 
發布套件 終於到最後我們要發布套件了,如果你還沒有 npmjs.com 
但如果你已經有帳號密碼了,可以使用
(/images/20250309/npm-adduser.png)
不管創建還是登入,只要你還沒有透過從專案終端機輸入指令後,通過 npmjs.com 
這邊預設使用者還沒註冊過,點擊圖上的 Create Account 
輸入完資料點擊 Create Account 
這邊的密碼會去檢查你輸入的是不是有在網路上洩漏了,如果是請換一組密碼
接著這邊需要再去信箱收驗證碼,往後的每次登入也都需要收驗證碼,以此保證你的帳號安全
創建成功後就可以關掉網頁回到你的專案了
最後在專案終端機應該會出現這些文字
接著我們就可以輸入 npm run publish
上面的內容跑完就會把專案推上去了,之後請到 npmjs.com 
這邊如果有 match  到關鍵字,基本上就會出現了,但如果沒有可以把 filter  過濾改為 Recently published  ,可能會在第一順位看到
如果都沒有搜尋到,之後可以考慮更改 package.json  的名稱,也可以重新再推送一次看看
 
這邊要特別注意,如果後續你不管從 npmjs.com npm unpublish  刪除,都需要再等上 24 小時,才能再推送一次,所以名稱跟配置一定要弄好,一切沒問題再推送
額外問題 
目前無法一次編譯 ESM  以及 CSJ  ,這部分後續會再找方法解決
目前無法透過 prod  編譯時,建立一份 js  提供給 /demo/src 使用,導致 github  沒辦法放上 demo  連結
目前無法在套件內安裝 TailwindCSS @import "tailwindcss"; 或 @use "tailwindcss"; 是什麼
更多額外問題會之後再補上
 
Conclusion & 結論 這次終於研究了想了好久的 npm  套件發布,即使知道中間可能會碰到很多坑,但真的碰到 Webpack  的時候,又想起之前被支配的恐懼…
透過朋友及同事知道除了 Webpack  還有 rollup  及 browserify  ,後續會再另外研究一下,希望能找出更簡易打包的方式,Webpack  真的太多繁瑣的設定,如果有想用的套件就必須要另外設定 config  檔,一個沒弄好,就會使引用的專案出現錯誤。
最後希望這篇筆記能夠幫助到你,如果你有任何問題,非常歡迎在留言討論,但請理性溝通,友善交流。
參考網站