背景:項目比較多,包含一些公共的代碼,如 utils 輔助工具,爲避免複製粘貼、版本同步,要將其抽離出來單獨做爲一個模塊來維護 爲了方便咱們開發,代碼老是分分合合,比人純粹些,分久必合,合久必分。javascript
在這裏呢,我將講解,一步一步的從封裝一個工具(日期處理),到發佈到 npm 倉庫(公共包免費,私有包收費),帶你瞭解整個過程,少踩坑。css
平臺:Mac oshtml
node:v10.15.3java
git:v2.22.1node
代碼管理:GitHubgit
編輯器:VS Codees6
EditorConfig for VS Code:跨編輯器統一項目文件/文本格式github
ESLint:eslint 規範插件npm
Prettier - Code formatter:代碼美化json
@babel/cli
@babel/core
@babel/preset-env
@babel/register
mocha
eslint
eslint-config-prettier
eslint-plugin-prettier
prettier
husky
lint-staged
@babelxxx
將 es6+語法編譯成 es5,eslintxxx
、prettier
代碼書寫規範及美化,husky
、lint-staged
提交鉤子,在提交代碼到倉庫以前作點事情
.
├── .github --- github 相關代碼
├── es --- es6+ 源碼
├── lib --- es5 源碼(由babel編譯而來)
├── test --- 測試代碼
├── .babelrc --- babel 配置
├── .editorconfig --- editor 配置
├── .eslintignore --- 忽略eslint檢查配置
├── .eslintrc --- eslint 配置
├── .gitignore --- 忽略git提交配置
├── .prettierignore --- 忽略代碼美化配置
├── .prettierrc --- 代碼美化配置
├── LICENSE --- 許可證
├── README.md --- 項目介紹(推薦用[readme-md-generator](https://github.com/kefranabg/readme-md-generator)生成)
├── package-lock.json --- pkg lock文件
└── package.json --- pkg 文件
複製代碼
如今開始一步步建立出上面的目錄結構(通用模版)
在 github 上建立一個organization(筆者起了個名 jsany 😎)
在上面建立的組織裏建立(new)一個 public 倉庫(date)
在npm上也建立一個organization(筆者起了個名 jsany 😎)這樣能夠避免 publish 時包名重複的問題
在本地登錄 npm npm login
在本地工做目錄建立一個文件夾 date 並進入 mkdir date && cd date
初始化 git git init
初始化 npm 工程 npm init --scope=jsany
{
"name": "@jsany/date",
"version": "1.0.0",
"description": "javascript date small",
"main": "lib/index.js",
"scripts": {
"test": "mocha --require @babel/register"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jsany/date.git"
},
"keywords": [
"data",
"js",
"format",
"transform"
],
"author": "jiangzhiguo2010", // 注意這裏要和npm帳號的用戶名一致
"license": "MIT", // 開源許可證
"bugs": {
"url": "https://github.com/jsany/date/issues"
},
"homepage": "https://github.com/jsany/date#readme"
}
複製代碼
用 vscode 打開項目(方便操做),在終端執行 code .
(這個須要另外配置,想學的能夠私聊我)
新建目錄 .github
,用來存放 github 相關的東西,這裏我用來放一個提交規則校驗的腳本,詳情請看
新建目錄 es
,用來存放 es6+語法的源碼
新建目錄 lib
,用來存放 es5 語法並支持 commonjs 的源碼,不須要編寫,由 babel 編譯生成
新建目錄 test
,用來存放測試代碼
安裝依賴 npm i --save-dev @babel/cli @babel/core @babel/preset-env @babel/register mocha eslint eslint-config-prettier eslint-plugin-prettier husky lint-staged prettier
新建文件 .babelrc
,babel 配置
{
"presets": [
[
"@babel/preset-env",
{
"modules": "auto", // 若想經過script標籤引入,這裏可使用 umd
"loose": true,
"targets": {
"esmodules": true,
"node": true
}
}
]
]
}
複製代碼
新建文件 .editorconfig
(安裝 EditorConfig for VS Code 插件後,也可經過 ⌘+⇧+p 而後輸入 Generate .editorconfig 生成),editorconfig 配置,跨編輯器統一項目文件/文本格式
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
複製代碼
新建文件 .eslintignore
,eslint 配置,忽略檢查
node_modules/
複製代碼
新建文件 .eslintrc
,eslint 配置
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"plugins": ["prettier"],
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-console": "off",
"prettier/prettier": "error"
}
}
複製代碼
新建文件 .gitignore
,忽略 git 提交配置
# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/yarn.lock
/package-lock.json
.DS_Store
.idea/
.vscode
複製代碼
新建文件 .prettierignore
,忽略代碼美化配置
**/*.svg
**/*.ejs
**/*.html
複製代碼
新建文件 .prettierrc
,代碼美化配置
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100
}
複製代碼
通過上面的步驟,package.json
大致上是這樣子:
{
"name": "@jsany/date",
"version": "1.0.5", // 包版本,每發佈一次,需更新
"description": "javascript date small es5 es6+",
"main": "lib/index.js", // commonjs 入口文件,使用 require 語法引入
"module": "es/index.js", // esmodules 入口文件,使用 import/require 語法引入,支持tree shaking 優化
"files": ["lib", "es"], // 這個files用來指定須要發佈的文件(將無用的文件剔除掉,減小體積,下載快,也能夠在`.npmignore`文件中指定須要剔除的文件)
"scripts": {
"test": "mocha --require @babel/register", // 測試命令
"compile": "babel es --out-dir lib" // babel編譯命令
},
"repository": {
"type": "git",
"url": "https://github.com/jsany/date.git"
},
"keywords": ["data", "js", "format", "transform"],
"author": "jiangzhiguo2010",
"license": "MIT",
"bugs": {
"url": "https://github.com/jsany/date/issues"
},
"homepage": "https://github.com/jsany/date",
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/register": "^7.5.5",
"mocha": "^6.2.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.3.0",
"eslint-plugin-prettier": "^3.1.0",
"husky": "^2.4.1",
"lint-staged": "^8.2.0",
"prettier": "^1.18.2"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "node .github/verifyCommitMsg"
}
},
"lint-staged": {
"*.{js,css,json,md}": ["prettier --write", "git add"]
},
"directories": {
"test": "test"
},
"dependencies": {}
}
複製代碼
date 提供格式化,時區轉換,獲取時間戳等功能
思路:(日期,格式)=>符合預期格式的日期,格式能夠經過正則匹配來返回固定的格式,按照這個來實現一下
新建 dateFormat.js
/** * @description 格式化日期 * @param {(object|string)} date - 日期對象/字符串 * @param {string} mask - 日期格式,默認:mask='yyyy-MM-dd HH:mm:ss' * @returns {string} 返回格式化後的日期 */
const dateFormat = (date, mask = 'yyyy-MM-dd HH:mm:ss') => {
const d = typeof date !== 'object' ? new Date(date) : date;
if (!d.getTime()) {
throw new MyError({ code: '000', msg: ErrorCode['000'] });
}
const zeroize = (value, length = 2) => {
value = String(value);
let zeros = '';
for (let i = 0, len = length - value.length; i < len; i++) {
zeros += '0';
}
return zeros + value;
};
return mask.replace(
/"[^"]*"|'[^']*'|\b(?:d{1,4}|m{1,4}|yy(?:yy)?|([hHMstT])\1?|[lLZ])\b/gi,
function($0) {
switch ($0) {
case 'd':
return d.getDate();
case 'dd':
return zeroize(d.getDate());
case 'ddd':
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'][d.getDay()];
case 'dddd':
return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][
d.getDay()
];
case 'M':
return d.getMonth() + 1;
case 'MM':
return zeroize(d.getMonth() + 1);
case 'MMM':
return [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
][d.getMonth()];
case 'MMMM':
return [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
][d.getMonth()];
case 'yy':
return String(d.getFullYear()).substr(2);
case 'yyyy':
return d.getFullYear();
case 'h':
return d.getHours() % 12 || 12;
case 'hh':
return zeroize(d.getHours() % 12 || 12);
case 'H':
return d.getHours();
case 'HH':
return zeroize(d.getHours());
case 'm':
return d.getMinutes();
case 'mm':
return zeroize(d.getMinutes());
case 's':
return d.getSeconds();
case 'ss':
return zeroize(d.getSeconds());
case 'l':
return zeroize(d.getMilliseconds(), 3);
case 'L':
var m = d.getMilliseconds();
if (m > 99) m = Math.round(m / 10);
return zeroize(m);
case 'tt':
return d.getHours() < 12 ? 'am' : 'pm';
case 'TT':
return d.getHours() < 12 ? 'AM' : 'PM';
case 'Z':
return d.toUTCString().match(/[A-Z]+$/);
// Return quoted strings with the surrounding quotes removed
default:
return $0.substr(1, $0.length - 2);
}
}
);
};
export default dateFormat;
複製代碼
思路:(utc 日期)=> utc 時間戳,經過 getTime
獲得當前時區的時間戳,getTimezoneOffset
獲得當前時區偏移量,兩者差值即 utc 時間戳
新建 utcTimestamp.js
/** * @description 獲取utc時間戳 * @param {string} date - utc日期對象/字符串,默認:當前時間 * @returns {number} 返回utc時間戳 */
const UTCTimestamp = (date = new Date()) => {
return new Date(date).getTime() - new Date().getTimezoneOffset() * 60 * 1000;
};
export default UTCTimestamp;
複製代碼
思路:(utc 日期,時區偏移量,格式)=>任意時區時間,經過 getTime
獲得時間戳,減去輸入的偏移量,即任意時區時間,按照這個來實現一下
新建 utc2target.js
/** * @description utc時間轉目標時區的時間,默認爲utc時間轉本地時間 * @param {object|string} date - utc時間,日期對象/字符串 * @param {number} timezone - 目標時區,默認:本地時區timezone=-480(中國時區+0800) * @param {*} mask - 日期格式,默認:mask='yyyy-MM-dd HH:mm:ss' * @returns {string} 返回目標時區的時間 */
const UTC2Target = (
date,
timezone = new Date().getTimezoneOffset(),
mask = 'yyyy-MM-dd HH:mm:ss'
) => {
const utcTimestamp = new Date(date).getTime();
if (!utcTimestamp) {
throw new MyError({ code: '000', msg: ErrorCode['000'] });
}
date = dateFormat(new Date(utcTimestamp - timezone * 60 * 1000), mask);
return date;
};
export default UTC2Target;
複製代碼
思路:(任意時區日期,時區偏移量,格式)=>utc 時間,經過 getTime
獲得當前時區時間戳,加上輸入的偏移量,即 utc 時間,按照這個來實現一下
新建 target2utc.js
/** * @description 目標時區的時間轉utc時間,默認爲本地時間轉utc時間 * @param {object|string} date - 目標時區時間,日期對象/字符串 * @param {number} timezone - 目標時區,默認:本地時區timezone=-480(中國時區+0800) * @param {*} mask - 日期格式,默認:mask='yyyy-MM-dd HH:mm:ss' * @returns {string} 返回目標時區的utc時間 */
const Target2UTC = (
date,
timezone = new Date().getTimezoneOffset(),
mask = 'yyyy-MM-dd HH:mm:ss'
) => {
let targetTimestamp = new Date(date).getTime();
if (!targetTimestamp) {
throw new MyError({ code: '000', msg: ErrorCode['000'] });
}
date = dateFormat(new Date(targetTimestamp + timezone * 60 * 1000), mask);
return date;
};
export default Target2UTC;
複製代碼
補充一下上面用到的工具函數/模塊:
./helper/errCode.js
export default {
'000': 'Invalid Date',
};
複製代碼
./helper/index.js
/** * @description 獲取數據的具體類型 * @param {any} o - 要判斷的數據 * @returns {string} - 返回該數據的具體類型 */
export const getDataType = o => {
// 映射數據類型
const map2DataType = {
'[object String]': 'String',
'[object Number]': 'Number',
'[object Undefined]': 'Undefined',
'[object Boolean]': 'Boolean',
'[object Array]': 'Array',
'[object Function]': 'Function',
'[object Object]': 'Object',
'[object Symbol]': 'Symbol',
'[object Set]': 'Set',
'[object Map]': 'Map',
'[object WeakSet]': 'WeakSet',
'[object WeakMap]': 'WeakMap',
'[object Null]': 'Null',
'[object Promise]': 'Promise',
'[object NodeList]': 'NodeList',
'[object Date]': 'Date',
'[object FormData]': 'FormData',
};
o = Object.prototype.toString.call(o);
if (map2DataType[o]) {
return map2DataType[o];
} else {
return o.replace(/^\[object\s(.*)\]$/, '$1');
}
};
/** * @description 擴展Error */
export class MyError extends Error {
constructor(props) {
super(props);
this.code = props.code || 0;
this.msg = props.msg || 'default msg';
this.name = 'MyError';
this.message = JSON.stringify(props);
}
}
複製代碼
新建文件 index.js
將方法集中導出
export { default as dateFormat } from './dateFormat';
export { default as UTCTimestamp } from './utcTimestamp';
export { default as UTC2Target } from './utc2target';
export { default as Target2UTC } from './target2utc';
複製代碼
mocha 不支持 esmodules,所以要用 babel 進行編譯,在 cli 增長參數 --require @babel/register
便可
在 test 文件夾下新建文件 index.es.test.js
import { UTCTimestamp, UTC2Target, Target2UTC } from '../es/index';
const assert = require('assert');
const bj = '2019-01-01 08:00:00';
const ist = '2019-01-01 05:30:00';
const utc = '2019-01-01 00:00:00';
const utc_unix = 1546300800000;
describe('#@jsany/date(es)', () => {
describe('#UTCTimestamp', () => {
it('UTCTimestamp() should return true', () => {
return assert.strictEqual(UTCTimestamp(utc, -480), utc_unix);
});
});
describe('#UTC2Target', () => {
it('UTC2Target() should return true', () => {
return assert.strictEqual(UTC2Target(utc, -480), bj);
});
it('UTC2Target() should return true', () => {
return assert.strictEqual(UTC2Target(utc, -330), ist);
});
});
describe('#Target2UTC', () => {
it('Target2UTC() should return true', () => {
return assert.strictEqual(Target2UTC(bj, -480), utc);
});
it('Target2UTC() should return true', () => {
return assert.strictEqual(Target2UTC(ist, -330), utc);
});
});
});
複製代碼
首先運行編譯命令 npm run compile
而後在 test 文件夾下新建文件 index.lib.test.js
const { UTCTimestamp, UTC2Target, Target2UTC } = require('../lib/index');
const assert = require('assert');
const bj = '2019-01-01 08:00:00';
const ist = '2019-01-01 05:30:00';
const utc = '2019-01-01 00:00:00';
const utc_unix = 1546300800000;
describe('#@jsany/date(lib)', () => {
describe('#UTCTimestamp', () => {
it('UTCTimestamp() should return true', () => {
return assert.strictEqual(UTCTimestamp(utc, -480), utc_unix);
});
});
describe('#UTC2Target', () => {
it('UTC2Target() should return true', () => {
return assert.strictEqual(UTC2Target(utc, -480), bj);
});
it('UTC2Target() should return true', () => {
return assert.strictEqual(UTC2Target(utc, -330), ist);
});
});
describe('#Target2UTC', () => {
it('Target2UTC() should return true', () => {
return assert.strictEqual(Target2UTC(bj, -480), utc);
});
it('Target2UTC() should return true', () => {
return assert.strictEqual(Target2UTC(ist, -330), utc);
});
});
});
複製代碼
運行測試:npm run test
首先,新建一個文件夾,做爲測試npm包的新工程,能夠與npm包工程(date
)同級目錄
cd .. && mkdir dateTest && cd dateTest
複製代碼
而後創建與date
的npm軟鏈
npm link ../date
複製代碼
此時,在dateTest文件夾下就有了date的npm依賴,能夠查看下node_modules
如今就能夠新建js文件進行導入測試了
運行 npx readme-md-generator
,建立 README.md
模版文件,而後補全
tips:這種
git remote add origin git@github.com:jsany/date.git
git push -u origin master
首先確認本身已登錄
檢查 package.json
文件,記住每次更改發佈,都應該是一個新的版本,沒問題後開始發佈(公開包 --access=public), 因爲我們是在一個組織(organization)下發包,因此不用擔憂 包名會重複的問題了,無需用 npm view
作檢查了
npm publish --access=public
【參考】:
===🧐🧐 文中不足,歡迎指正 🤪🤪===