# github package && npm repo ## 開發流程 ``` mermaid graph TD; local_package-->npm_login; npm_login-->npm_build; npm_build--->github_repo; github_repo-->git_action; git_action-->npm_publish; npm_publish-->github_package; npm_publish-->npm_repo; ``` ## init npm package 將 npm 初始化 ```typescript= npm init --scope=@my-org npm init --scope=@my-username ``` ### npm_login : **1. publish to npm repo** 驗證npm publish權限,登入npm 帳號。 **指令:npm login** **2. publish to github package** 驗證npm publish權限,登入 github repo ower 帳號。 **指令 : npm login --scope=@OWER --registry=https://npm.pkg.github.com** **注意** : 這時登入的密碼需要透過生成 **Personal access tokens** 當作密碼,以確保發布者的所有權限。 [如何生成Personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) ### npm_publish **npm publish** (發布私有包) **npm publish --access public** (發布公開包) **注意 :** **npm publish** 預設是私有狀態,如果 **package.json** 中有設定 **"private": false** 將會報錯 --- ### npm_update 如果要將npm update的畫板好記得更新 ```typescript npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] npm publish ``` 如果直接 `npm publish`會得到以下的error,原因是如果發布同版本的 pkg ,npm 因為 security policy 關係不能重複上傳相同版本,所以記得更新版本號 ![](https://hackmd.io/_uploads/HJUN3F6Bh.png) ### npm_build 在 **package.json** 執行npm script測試打包後的模組結構指令如下。 ```json= //package.json "scripts": { "dev": "next dev", "start": "next start", "lint": "next lint", "build-next": "next build", "build": "rm -rf dist/ && npm run build:esm && npm run build:cjs && npm run build:css", "build:css": "tailwindcss build styles/globals.css -o public/styles.css --minify && copy-files-from-to", "build:esm": "tsc --project tsconfig.production.json", "build:cjs": "tsc --project tsconfig.production.json --module CommonJS --outDir dist/cjs", "prepublish": "npm run build" }, "copyFiles": [ { "from": "public/styles.css", "to": "dist/main.css" } ], ``` ### 打包規則 * **build:css** :因為專案是用 tailwindcss 開發,所以需要打包 css 檔案,之後透過 copy-files-from-to 套件將打包好的 css 放到 dist 資料夾中。 * **build:esm && build:cjs** : 透過 tsconfig.production.json 打包需要模塊到 dist 資料夾中。 * **copyFiles** :讓 copy-files-from-to 知道哪些哪檔案需要移入 。 #### 打包建議 因為打包套件很像在寫測試他是獨立的專案,所以建議把所有型別放 _TYPES_ 資料夾中去管理,之後引入套件才不會出錯。 ![](https://i.imgur.com/w5hBRhs.png) **備註 :** 因為專案中也需要做 ts 檢測,所以會分成 tsconfig.production.json 作為打包的設定檔。 ### 打包後的結構如下 使用套件時會考慮開發者使用什麼模塊所以會分別打包 **cjs(CommandJS)** 跟 **esm(esModule)** 兩種模塊,以及套件中用到的所有樣式檔案都在 main.css 中 。 ![](https://i.imgur.com/ghOTda7.png) 每一個 component因為 tsconfig.production.json 設定的關係所以會有 * *.d.ts 型別宣告檔 * 基礎模塊*.js引用入口 * *.js.map 錯誤代碼位置讓之後更新套件方便查錯 ![](https://i.imgur.com/xEUchZc.png) ### 如何使用套件 > #### step.1 在 _app.tsx 中引用打包好的樣式文件 ![](https://i.imgur.com/ZPQPZaS.png) > #### step.2 根據套件位置引入 ![](https://i.imgur.com/c4LTJdO.png) --- ### git_action **git action** 是一個 **CI** 工具,讓使用者可以推送到 **github repo** 時自動執行動作,例如推送打包的套件到 **npm repo** 或 **github PKG** ,**github** 稱以上的動作叫 **workflow**,**workflow** 可以在專案中新增以下的資料夾來啟用。 ![](https://i.imgur.com/2z76Ccm.png) ```yml= # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages name: Node.js Package on: release: types: [created] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 - run: npm ci - run: npm test publish-gpr: needs: build runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 registry-url: https://npm.pkg.github.com/ - run: npm ci - run: npm publish env: NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} ``` 更多 **git action** 教學請看以下的連結 : [教學](https://docs.github.com/en/actions) --- ## 本地測試套件使用情形 ``` mermaid graph TD; local_package-->npm_link; local_package-->npm_install; npm_link-->another_project; npm_install-->another_project; ``` ### 在本地測試套件有兩種方式 : **1. npm install ~/[PACKAGE_PATH]** 安裝方式跟平時使用 **npm i** 的方式一樣,差別在於要指定套件路徑去安裝。 **2. npm link** 使用npm link 之前要在套件專案下npm link指令,之後到要測試的專案使用 **npm link [@OWER/PACKAGENAME]** **npm link** 其實就是將專案復用到電腦根目錄的位置,使用npm link 就是讓其他專案與根目錄的.nvm檔案做連結去使用。 > 結構如下 : ![](https://i.imgur.com/09jbEMv.png) --- ## package.json ### npm相關套件設定 ```json= { "name": "@ani1788/danny", "version": "1.0.0", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "private": false, "files": [ "dist" ], "repository": { "type": "git", "url": "https://github.com/ani1788/point-trade-platform-ui" }, "publishConfig": { "@ani1788:registry": "https://npm.pkg.github.com" }, "scripts": { "dev": "next dev", "start": "next start", "lint": "next lint", "build-next": "next build", "build": "rm -rf dist/ && npm run build:esm && npm run build:cjs && npm run build:css", "build:css": "tailwindcss build styles/globals.css -o public/styles.css --minify && copy-files-from-to", "build:esm": "tsc --project tsconfig.production.json", "build:cjs": "tsc --project tsconfig.production.json --module CommonJS --outDir dist/cjs", "prepublish": "npm run build" }, "copyFiles": [ { "from": "public/styles.css", "to": "dist/main.css" } ], "dependencies": { "@svgr/webpack": "^6.4.0", "next": "12.3.1", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { "@types/node": "^18.7.18", "@types/react": "18.0.20", "@types/react-dom": "18.0.6", "autoprefixer": "^10.4.12", "eslint": "8.23.1", "eslint-config-next": "12.3.1", "postcss": "^8.4.17", "tailwindcss": "^3.1.8", "typescript": "4.8.3" }, "peerDependencies": { "react": "^17.0.2", "react-dom": "^17.0.2" } } ``` **name(套件名稱)** : @OWER/YOUR_PROJECT 。 **version** : 套件版本。 **main** :模塊預設進入的路徑。 **files** :npm build 時要包含哪些資料夾。 **repository** :package存放位置。 **prepublish** :這個指令是執行 **npm publish** 時會做的指令,使用情境很像用 **husky** 的 **precommit** 功能,目的在於部署前打包一個 **dist** 資料夾後推送上去。 **publishConfig** : 告訴你 **publish** 的 **repo** 套件擁有者跟套件名稱。 **peerDependencies** : 當使用者下載你發佈的套件後會發現也把開源者使用的 **library** 也一起下載了,這樣會有重複下載其他套件的問題,會造成其他套件版本衝突,這時 **peerDependencies** 就是告訴 **npm** 我下載這個包時只會指定下載哪些使用者需要的 **library**。 --- ## tsconfig.production.json ```json= { "compilerOptions": { "allowSyntheticDefaultImports": true, "declaration": true, "esModuleInterop": true, "jsx": "react", "lib": ["es5", "es2015", "es2016", "dom", "esnext"], "types": ["node"], "module": "es2015", "moduleResolution": "node", "noImplicitAny": true, "noUnusedLocals": true, "outDir": "dist/esm", "sourceMap": true, "strict": true, "target": "es6" }, "include": ["./components/**/*.ts", "./components/**/*.tsx"] } ``` **declaration** : 打包時自動生成宣告檔。 **esModuleInterop** : 作用是支持使用import d from 'cjs'的方式引入commonjs包。 **jsx** : jsx檔案要用什麼方式解析。 **sourceMap** : 自動生成sourceMap。 **outDir** : 打包後的路徑。 **include** : 哪些檔案需要經過編譯。 --- ## .npmrc( 套件權限資訊 ) ```npm= npm.pkg.github.com/:_authToken=YOUR_TOKEN @OWER:registry=https://npm.pkg.github.com ``` **_authToken** : **npm login** 密碼 **@OWER** : github repo OWER ### customer npm init https://blog.greenroots.info/tips-to-customize-npm-init-to-make-it-your-own ### 2023 publish npm https://www.youtube.com/watch?v=eh89VE3Mk5g&ab_channel=MattPocock ## npm 相關補充 ### 查詢沒在使用的套件 ``` > npx depcheck ``` ### 查詢沒在使用的套件,排除missing ``` npx depcheck --skip-missing ``` ### expo 兼容性 查看專案中與expo 47兼容的版本 ``` expo update 47 ``` ### nvm management local node version permanent ```typescript # this will use latest node version > nvm alias default node > nvm use default # use specific node version # nvm set default node.js version 16.14.2 > nvm alias default 16.14.2 > nvm use > node -v # v16.14.2 ``` ### 查詢 local 已安裝的 global 套件 ```typescript > npm ls -g /Users/danny/.nvm/versions/node/v20.0.0/lib ├── @arelstone/traduora-cli@1.1.3 ├── @danny101201/commander@1.0.1 ├── commander@npm:@danny101201/commander@1.0.1 -> ./../../../../../develpoment/learn/node/commander ├── corepack@0.17.2 ├── eas-cli@3.15.0 ├── firebase-tools@12.4.3 ├── json-server@0.17.3 ├── json5@2.2.3 ├── lightningcss-cli@1.21.5 ├── npm@9.7.1 ├── tsx@3.12.7 ├── typeorm@0.3.17 └── typescript@4.9.5 ``` 從資料來看 npm global install 會存放在 `.nvm` 中,並根據 node version 分類套件安裝的node 版本。 ![](https://hackmd.io/_uploads/Syh87p7pn.png) ### npm bin ```typescript // package.json { "name": "@danny101201/commander", "version": "1.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "bin": { "commander_tests2": "index.js" }, "dependencies": { "commander": "^10.0.0" } } ``` bin 功能是將你定義的 cli 名稱去印射到你想執行的 script,例如底下我想在全局透過呼叫 `commander_tests2` 而去執行 index.js 內容。 在 package.json 中指定 bin 會讓你在 npm i -g 或是 npm link 時,在 `.nvm/versions/node` 底下去保存你在 bin 定義的 file,檔名是 key檔案內容是 value ![](https://hackmd.io/_uploads/H1UtVpQph.png) 這邊記得在你想成形的 script 中加 usr 宣告 ```typescript #!/usr/bin/env node // index.js ``` 這樣你就可以透過 cli 方式透過打 `commander_tests2` 去執行 `index.js` 裡面的內容 ![](https://hackmd.io/_uploads/H1F88T762.png) ## 根據 lib 引入方式指定導入什麼 file 建議所有入境都加上副檔名,否則 `nodejs` 會 `import` 錯誤檔案,可能會把 `commjs` 當成 `esModule` ```typescript // pageage.json "exports": { "./package.json": "./package.json", ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } }, "./presets": { "import": { "types": "./dist/presets.d.ts", "default": "./dist/presets.js" }, "require": { "types": "./dist/presets.d.cts", "default": "./dist/presets.cjs" } }, "./package.json": "./package.json" } ``` ## 在 esm 引入 cjs ```typescript // file.mjs import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const pkg = require('pkg-commonjs'); ``` https://medium.com/dcardlab/%E4%B8%80%E7%AF%87%E6%96%87%E6%90%9E%E6%B8%85%E6%A5%9A-node-js-%E6%A8%A1%E7%B5%84%E8%A1%8C%E7%82%BA-%E8%87%AA%E7%94%B1%E9%81%8B%E7%94%A8-commonjs-%E8%88%87-esm-%E6%A8%A1%E7%B5%84-5f1e393276ba ## pnpm update https://www.youtube.com/watch?v=9AyfXGHu56Y&ab_channel=MayankSrivastava ```typescript > pnpm --recursive update @typescript-eslint/parser@latest ``` ## 檢查可以升級的 package https://www.freecodecamp.org/chinese/news/how-to-update-npm-dependencies/ ```typescript > pnpm outdated ``` ## publish public package ```typescript // package.json { "publishConfig": { "access": "public" } } ```