建立公司內部 Private NPM — Verdaccio — 實作 React Styled Components Library 安裝、打包、上傳與下載
Private NPM 提供建立一套公司內部專屬 React Components layout library 的可能。Verdaccio 是一套輕量的 Private NPM proxy registry,當其他 Developer 要 import 各種客製化元件,只要在安裝後更新到最新的版本,即可無痛翻新整個 Layout。
安裝流程
- 安裝 Vardaccio
$ npm install --global verdaccio
- 設訂 Registry 到 localhost:4873
$ npm set registry http://localhost:4873/
- 可以在根目錄中的
.npmrc
裡面看到registry=://localhost:4873/
- 此時 run verdaccio 後打開 http://localhost:4873/ 就可以看到在 local 能 login 的畫面
$ verdaccio
- 增加使用者 > 輸入帳號、密碼和 email
$ npm adduser --registry http://localhost:4873
- 回到 http://localhost:4873/ 登錄剛輸入的帳密即可
打包流程
打開要包裝成 Library 的 React Layout 專案後
- 增加一個
.npmignore
檔案把不需要打包進 Library 的東西「排除」
node_modules/
public
Reference
src
- 在
.gitignore
檔案中加上 Library 才不會一起 commit 進 Git
lib
.tmp
- 增加一個
.babelrc.js
檔案設定打包工具
module.exports = {
env: {
es: {
presets: [
['@babel/preset-env', { modules: false }],
'@babel/preset-react'
],
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }],
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-do-expressions',
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-function-bind',
'@babel/plugin-proposal-logical-assignment-operators',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }],
'@babel/plugin-proposal-throw-expressions',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-runtime',
[
'transform-rename-import',
{
original: '(.*)/([^/]+)\\.svg$',
replacement: (text, $1, $2) => {
return (
$1 + '/' + $2.replace(/(^|_)./g, s => s.slice(-1).toUpperCase())
)
}
}
]
]
}
}
}
- 增加 svgrTemplate.js 檔案 (package.json file 會解釋)
const types = require('@babel/types')function template(babelAPI, opts, values) {
const componentName = types.identifier('ReactComponent')
const exports = types.exportNamedDeclaration(null, [
types.exportSpecifier(componentName, componentName)
])
return babelAPI.template.ast`
${values.imports}
const ${componentName} = (${values.props}) => ${values.jsx}
${exports}
`
}module.exports = template
- 在 package.json 將此版本設訂為第一版
"version": "0.1.0",
- 加上 lib 作為打包後輸出路徑
"main": "lib",
"module": "lib",
- 移除 package.json 上的
"private": true,
讓專案變成可共用的 - 只留下不會與使用 Library 的 Project 衝突的 dependencies (避免影響安裝 project 的 dependency 衝突,假設兩邊都基於 React,但只要以使用 Library Project 的為主即可)
"dependencies": {
"react-swipe": "5.1.1",
"styled-components": "^4.2.0",
"swipe-js-iso": "2.0.0"
}
- 其餘打包工具和不需要一起打包進 Library 裡放在 devDependencies
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "7.2.3",
"@babel/plugin-proposal-decorators": "7.2.3",
"@babel/plugin-proposal-do-expressions": "7.0.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.2.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/plugin-proposal-logical-assignment-operators": "7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.0.0",
"@babel/plugin-proposal-optional-chaining": "7.0.0",
"@babel/plugin-proposal-pipeline-operator": "7.3.2",
"@babel/plugin-proposal-throw-expressions": "7.0.0",
"@babel/plugin-syntax-dynamic-import": "7.0.0",
"@babel/plugin-transform-runtime": "7.2.0",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "7.4.2",
"@babel/types": "^7.4.4",
"@svgr/cli": "^4.2.0",
"babel-plugin-transform-rename-import": "^2.3.0",
"cross-env": "^5.2.0",
"node-sass": "^4.11.0",
"prettier": "^1.17.0",
"prop-types": "^15.7.2",
"query-string": "^6.4.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.0",
"rimraf": "^2.6.3"
},
其中,@svgr/cli 用來處理 svg 打包,而 svg 打包後會從 svg 轉為 React Components ,使用套件 babel-plugin-transform-rename-import
將匯入的 SVG 自動轉符合 import 的格式,svgr 自動把檔案轉為駝峰命名。
cross-env
處理跨平台設置 NODE_ENV
指令
rimraf
處理跨平台刪除指令
- 在 scripts 設定
build:lib
指令時所需要打包的東西
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"format": "prettier --single-quote --no-semi --write \"src/**/*.{js,jsx,scss}\"",
"test": "react-scripts test",
"eject": "react-scripts eject",
"pre-clean": "rimraf lib",
"post-clean": "rimraf .tmp",
"build-js": "cross-env NODE_ENV=es babel src .tmp --out-dir lib",
"build-svg": "svgr src --template svgrTemplate.js --out-dir .tmp",
"cp:resources": "cp -R src/img lib",
"build:lib": "npm run pre-clean && npm run build-svg && npm run build-js && npm run post-clean && npm run cp:resources"
},
- 其中,
pre-clean
先清除上次打包出來的 lib 資料夾,然後開始轉 SVG,使用 svgr 打包後所有的 .svg 檔案會變 .js 檔案。 - 由於我們使用
import { ReactComponent as MyComponent } form './mySvg.svg'
的語法引用 SVG,因此要使用svgrTemplate.js
去改掉所有 .svg 轉出來的 .js 檔案來符合 import 的格式。 - SVG 轉成 js 會產生 .tmp 資料夾,使用 Babel 把 src 與 .tmp 一起轉譯輸出到 lib 資料夾,並且修改引用 SVG 的路徑 (包含修改為駝峰命名),從
import { ReactComponent as MyComponent } from '../my_space.svg'
變成import { ReactComponent as MyComponent } from '../MySpace'
。 - 輸出完成後刪掉 .tmp , 接著才把 img 資料夾圖片資源複製到 lib 資料夾當中,讓 image 找得到。
以下為完整的 package.json
{
"name": "myLibrary",
"version": "0.1.0",
"main": "lib",
"module": "lib",
"dependencies": {
"react-swipe": "5.1.1",
"styled-components": "^4.2.0",
"swipe-js-iso": "2.0.0"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "7.2.3",
"@babel/plugin-proposal-decorators": "7.2.3",
"@babel/plugin-proposal-do-expressions": "7.0.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.2.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/plugin-proposal-logical-assignment-operators": "7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.0.0",
"@babel/plugin-proposal-optional-chaining": "7.0.0",
"@babel/plugin-proposal-pipeline-operator": "7.3.2",
"@babel/plugin-proposal-throw-expressions": "7.0.0",
"@babel/plugin-syntax-dynamic-import": "7.0.0",
"@babel/plugin-transform-runtime": "7.2.0",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "7.4.2",
"@babel/types": "^7.4.4",
"@svgr/cli": "^4.2.0",
"babel-plugin-transform-rename-import": "^2.3.0",
"cross-env": "^5.2.0",
"mathjs": "^5.9.0",
"node-sass": "^4.11.0",
"prettier": "^1.17.0",
"prop-types": "^15.7.2",
"query-string": "^6.4.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.0",
"rimraf": "^2.6.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"format": "prettier --single-quote --no-semi --write \"src/**/*.{js,jsx,scss}\"",
"test": "react-scripts test",
"eject": "react-scripts eject",
"pre-clean": "rimraf lib",
"post-clean": "rimraf .tmp",
"build-js": "cross-env NODE_ENV=es babel src .tmp --out-dir lib",
"build-svg": "svgr src --template svgrTemplate.js --out-dir .tmp",
"cp:resources": "cp -R src/img lib",
"build:lib": "npm run pre-clean && npm run build-svg && npm run build-js && npm run post-clean && npm run cp:resources"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
- 執行 yarn 重新安裝一次 package.json
$ yarn
- 更改 img 路徑從 (因為打包沒辦法 import 資料夾的 index,所以要改為直接 import index 檔案)
../img
-> ../img/index
../../img
-> ../../img/index
- 開始 build lib 資料夾
$ yarn run build:lib
完成後後準備上傳第一版的 Library
上傳與下載流程
- 查看目前在哪個 registry
$ npm get registry
- 如果還在 http://localhost:4873/ 將其設定要上傳的 registry 位置
$ npm set registry on http://111.11.11.111
- 將準備好的 Library上傳到 private NPM
$ npm publish --registry http://111.11.11.111
- 如果上傳錯誤,要移除上傳錯誤的 Library
$ npm unpublish --force myLibrary
- 如果出現
npm ERR! request entity too large
檔案太大的訊息
可以下
$ verdaccio
就會出現 config file — C:\Users\angel.ho.config\verdaccio\config.yaml
打開檔案加入 max_body_size
在檔案中
max_body_size: 1000mb
然後停掉 verdaccio 再重新 run 即可 publish 更大尺寸的 lib
- 上傳完成後即可在 http://111.11.11.111 看到 lib 這是第幾版的 lib
- 新專案準備好 React 環境之後 接著在新的空資料夾安裝此 Library
$ yarn add myLibrary
- 此時即可完成 Components import
import React from 'react';
import { Page } from 'myLibrary/lib/components/page'
function App() {
return (
<div className="App">
<ResetStyle/>
<GlobalStyle/>
<Page/>
</div>
);
}
export default App;
- 久未登入記得重run
npm login
才能更新 Library - 如果 Project 被 registry 咬住,進入
code ~/.npmrc
去確認目前所在 registry
Say hello! 我是 Angel,這裏的內容如果有幫到你,希望能獲得一些拍手作為鼓勵
工作上的合作歡迎隨時透過 Mail 聯繫我 contact@aneglho.design–
Thanks for watching!