都2202年了,不會有人還不會發(fā)布npm包吧?下面本篇文章給大家分享一下從0開始創(chuàng)建并發(fā)布npm的全過程,希望對大家有所幫助!
前端(vue)入門到精通課程,老師在線輔導(dǎo):聯(lián)系老師
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點擊使用
背景
4月發(fā)布了一篇文章,快來升級你項目內(nèi)的axios封裝,向重復(fù)請求say goodbye,介紹了axios的二次封裝用于支持常規(guī)請求及自定義請求,并對同一時間內(nèi)的相同請求做攔截處理(如果您沒有閱讀過這篇文章,建議您花費3分鐘大致了解)。恰逢最近準(zhǔn)備寫一個跨框架組件庫(工作量很大,前端三個小伙伴利用空閑時間在卷,待組件庫完善后會分享給大家,敬請期待),需要學(xué)習(xí)發(fā)布npm包,昨天就想著利用空閑時間把之前寫的去除重復(fù)請求的axios封裝發(fā)布為npm包,便于代碼復(fù)用,回饋社區(qū)的同時也能學(xué)以致用。
閱讀本文,你將收獲:
-
從0開始創(chuàng)建并發(fā)布npm的全過程?!鞠嚓P(guān)教程推薦:nodejs視頻教程、編程教學(xué)】
-
一個持續(xù)迭代且簡單實用的axios請求去重工具庫。
工具庫準(zhǔn)備
創(chuàng)建一個新項目,包含package.json
{ "name": "drrq", "type": "module", "version": "1.0.0" }
功能實現(xiàn) /src/index.js
npm i qs axios
主要思路是用請求的url和參數(shù)作為key記錄請求隊列,當(dāng)出現(xiàn)重復(fù)請求時,打斷后面的請求,將前面的請求結(jié)果返回時共享給后面的請求。
import qs from "qs"; import axios from "axios"; let pending = []; //用于存儲每個ajax請求的取消函數(shù)和ajax標(biāo)識 let task = {}; //用于存儲每個ajax請求的處理函數(shù),通過請求結(jié)果調(diào)用,以ajax標(biāo)識為key //請求開始前推入pending const pushPending = (item) => { pending.push(item); }; //請求完成后取消該請求,從列表刪除 const removePending = (key) => { for (let p in pending) { if (pending[p].key === key) { //當(dāng)前請求在列表中存在時 pending[p].cancelToken(); //執(zhí)行取消操作 pending.splice(p, 1); //把這條記錄從列表中移除 } } }; //請求前判斷是否已存在該請求 const existInPending = (key) => { return pending.some((e) => e.key === key); }; // 創(chuàng)建task const createTask = (key, resolve) => { let callback = (response) => { resolve(response.data); }; if (!task[key]) task[key] = []; task[key].push(callback); }; // 處理task const handleTask = (key, response) => { for (let i = 0; task[key] && i < task[key].length; i++) { task[key][i](response); } task[key] = undefined; }; const getHeaders = { 'Content-Type': 'application/json' }; const postHeaders = { 'Content-Type': 'application/x-www-form-urlencoded' }; const fileHeaders = { 'Content-Type': 'multipart/form-data' }; const request = (method, url, params, headers, preventRepeat = true, uploadFile = false) => { let key = url + '?' + qs.stringify(params); return new Promise((resolve, reject) => { const instance = axios.create({ baseURL: url, headers, timeout: 30 * 1000, }); instance.interceptors.request.use( (config) => { if (preventRepeat) { config.cancelToken = new axios.CancelToken((cancelToken) => { // 判斷是否存在請求中的當(dāng)前請求 如果有取消當(dāng)前請求 if (existInPending(key)) { cancelToken(); } else { pushPending({ key, cancelToken }); } }); } return config; }, (err) => { return Promise.reject(err); } ); instance.interceptors.response.use( (response) => { if (preventRepeat) { removePending(key); } return response; }, (error) => { return Promise.reject(error); } ); // 請求執(zhí)行前加入task createTask(key, resolve); instance(Object.assign({}, { method }, method === 'post' || method === 'put' ? { data: !uploadFile ? qs.stringify(params) : params } : { params })) .then((response) => { // 處理task handleTask(key, response); }) .catch(() => {}); }); }; export const get = (url, data = {}, preventRepeat = true) => { return request('get', url, data, getHeaders, preventRepeat, false); }; export const post = (url, data = {}, preventRepeat = true) => { return request('post', url, data, postHeaders, preventRepeat, false); }; export const file = (url, data = {}, preventRepeat = true) => { return request('post', url, data, fileHeaders, preventRepeat, true); }; export default { request, get, post, file };
新增示例代碼文件夾/example
示例入口index.js
import { exampleRequestGet } from './api.js'; const example = async () => { let res = await exampleRequestGet(); console.log('請求成功 '); }; example();
api列表api.js
import { request } from './request.js'; // 示例請求Get export const exampleRequestGet = (data) => request('get', '/xxxx', data); // 示例請求Post export const exampleRequestPost = (data) => request('post', '/xxxx', data); // 示例請求Post 不去重 export const exampleRequestPost2 = (data) => request('post', '/xxxx', data, false); // 示例請求Post 不去重 export const exampleRequestFile = (data) => request('file', '/xxxx', data, false);
全局請求封裝request.js
import drrq from '../src/index.js'; const baseURL = 'https://xxx'; // 處理請求數(shù)據(jù) (拼接url,data添加token等) 請根據(jù)實際情況調(diào)整 const paramsHandler = (url, data) => { url = baseURL + url; data.token = 'xxxx'; return { url, data }; }; // 處理全局接口返回的全局處理相關(guān)邏輯 請根據(jù)實際情況調(diào)整 const resHandler = (res) => { // TODO 未授權(quán)跳轉(zhuǎn)登錄,狀態(tài)碼異常報錯等 return res; }; export const request = async (method, _url, _data = {}, preventRepeat = true) => { let { url, data } = paramsHandler(_url, _data); let res = null; if (method == 'get' || method == 'GET' || method == 'Get') { res = await drrq.get(url, data, preventRepeat); } if (method == 'post' || method == 'POST' || method == 'Post') { res = await drrq.post(url, data, preventRepeat); } if (method == 'file' || method == 'FILE' || method == 'file') { res = await drrq.file(url, data, preventRepeat); } return resHandler(res); };
測試功能
代碼寫完后,我們需要驗證功能是否正常,package.json加上
"scripts": { "test": "node example" },
執(zhí)行npm run test
功能正常,工具庫準(zhǔn)備完畢。
(eslint和prettier讀者可視情況選用)
打包
一般項目的打包使用webpack,而工具庫的打包則使用rollup
安裝 Rollup
通過下面的命令安裝 Rollup:
npm install --save-dev rollup
創(chuàng)建配置文件
在根目錄創(chuàng)建一個新文件 rollup.config.js
export default { input: "src/index.js", output: { file: "dist/drrp.js", format: "esm", name: 'drrp' } };
- input —— 要打包的文件
- output.file —— 輸出的文件 (如果沒有這個參數(shù),則直接輸出到控制臺)
- output.format —— Rollup 輸出的文件類型
安裝babel
如果要使用 es6 的語法進(jìn)行開發(fā),還需要使用 babel 將代碼編譯成 es5。因為rollup的模塊機制是 ES6 Modules,但并不會對 es6 其他的語法進(jìn)行編譯。
安裝模塊
rollup-plugin-babel 將 rollup 和 babel 進(jìn)行了完美結(jié)合。
npm install --save-dev rollup-plugin-babel@latest npm install --save-dev @babel/core npm install --save-dev @babel/preset-env
根目錄創(chuàng)建 .babelrc
{ "presets": [ [ "@babel/preset-env", { "modules": false } ] ] }
兼容 commonjs
rollup 提供了插件 rollup-plugin-commonjs,以便于在 rollup 中引用 commonjs 規(guī)范的包。該插件的作用是將 commonjs 模塊轉(zhuǎn)成 es6 模塊。
rollup-plugin-commonjs 通常與 rollup-plugin-node-resolve 一同使用,后者用來解析依賴的模塊路徑。
安裝模塊
npm install --save-dev rollup-plugin-commonjs rollup-plugin-node-resolve
壓縮 bundle
添加 UglifyJS 可以通過移除注上釋、縮短變量名、重整代碼來極大程度的減少 bundle 的體積大小 —— 這樣在一定程度降低了代碼的可讀性,但是在網(wǎng)絡(luò)通信上變得更有效率。
安裝插件
用下面的命令來安裝 rollup-plugin-uglify:
npm install --save-dev rollup-plugin-uglify
完整配置
rollup.config.js 最終配置如下
import resolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; import babel from 'rollup-plugin-babel'; import { uglify } from 'rollup-plugin-uglify'; import json from '@rollup/plugin-json' const paths = { input: { root: 'src/index.js', }, output: { root: 'dist/', }, }; const fileName = `drrq.js`; export default { input: `${paths.input.root}`, output: { file: `${paths.output.root}${fileName}`, format: 'esm', name: 'drrq', }, plugins: [ json(), resolve(), commonjs(), babel({ exclude: 'node_modules/**', runtimeHelpers: true, }), uglify(), ], };
在package.json中加上
"scripts": { "build": "rollup -c" },
即可執(zhí)行npm run build將/src/index.js打包為/dist/drrq.js
發(fā)包前的準(zhǔn)備
準(zhǔn)備npm賬號,通過npm login或npm adduser。這里有一個坑,終端內(nèi)連接不上npm源,需要在上網(wǎng)工具內(nèi)復(fù)制終端代理命令后到終端執(zhí)行才能正常連接。
準(zhǔn)備一個簡單清晰的readme.md
修改package.json
完整的package.json如下
{ "name": "drrq", "private": false, "version": "1.3.5", "main": "/dist/drrq.js", "repository": "https://gitee.com/yuanying-11/drrq.git", "author": "it_yuanying", "license": "MIT", "description": "能自動取消重復(fù)請求的axios封裝", "type": "module", "keywords": [ "取消重復(fù)請求", ], "dependencies": { "axios": "^1.2.0", "qs": "^6.11.0" }, "scripts": { "test": "node example", "build": "rollup -c" }, "devDependencies": { ... } }
- name 包名稱 一定不能與npm已有的包名重復(fù),想一個簡單易記的
- private 是否為私有
- version 版本
- main 入口文件位置
- repository git倉庫地址
- author 作者
- license 協(xié)議
- description 描述
- keywords 關(guān)鍵詞,便于檢索
每個 npm 包都需要一個版本,以便開發(fā)人員在安全地更新包版本的同時不會破壞其余的代碼。npm 使用的版本系統(tǒng)被叫做 SemVer,是 Semantic Versioning 的縮寫。
不要過分擔(dān)心理解不了相較復(fù)雜的版本名稱,下面是他們對基本版本命名的總結(jié): 給定版本號 MAJOR.MINOR.PATCH,增量規(guī)則如下:
MAJOR 版本號的變更說明新版本產(chǎn)生了不兼容低版本的 API 等,
MINOR 版本號的變更說明你在以向后兼容的方式添加功能,接下來
PATCH 版本號的變更說明你在新版本中做了向后兼容的 bug 修復(fù)。
表示預(yù)發(fā)布和構(gòu)建元數(shù)據(jù)的附加標(biāo)簽可作為 MAJOR.MINOR.PATCH 格式的擴展。
最后,執(zhí)行npm publish就搞定啦
本文的完整代碼已開源至gitee.com/yuanying-11… ,感興趣的讀者歡迎fork和star!
轉(zhuǎn)載地址:https://juejin.cn/post/7172240485778456606