背景
4月发布了一篇文章,快来升级你项目内的axios封装,向重复请求say goodbye,介绍了axios的二次封装用于支持常规请求及自定义请求,并对同一时间内的相同请求做拦截处理(如果您没有阅读过这篇文章,建议您花费3分钟大致了解)。恰逢最近准备写一个跨框架组件库(工作量很大,前端三个小伙伴利用空闲时间在卷,待组件库完善后会分享给大家,敬请期待),需要学习发布npm包,昨天就想着利用空闲时间把之前写的去除重复请求的axios封装发布为npm包,便于代码复用,回馈社区的同时也能学以致用。
【资料图】
阅读本文,你将收获:
从0开始创建并发布npm的全过程。【相关教程推荐:nodejs视频教程、编程教学】
一个持续迭代且简单实用的axios请求去重工具库。
工具库准备
创建一个新项目,包含package.json
{ "name": "drrq", "type": "module", "version": "1.0.0"}
登录后复制
功能实现 /src/index.js
npm i qs axios
主要思路是用请求的url和参数作为key记录请求队列,当出现重复请求时,打断后面的请求,将前面的请求结果返回时共享给后面的请求。
import qs from "qs";import axios from "axios";let pending = []; //用于存储每个ajax请求的取消函数和ajax标识let task = {}; //用于存储每个ajax请求的处理函数,通过请求结果调用,以ajax标识为key//请求开始前推入pendingconst pushPending = (item) => { pending.push(item);};//请求完成后取消该请求,从列表删除const removePending = (key) => { for (let p in pending) { if (pending[p].key === key) { //当前请求在列表中存在时 pending[p].cancelToken(); //执行取消操作 pending.splice(p, 1); //把这条记录从列表中移除 } }};//请求前判断是否已存在该请求const existInPending = (key) => { return pending.some((e) => e.key === key);};// 创建taskconst createTask = (key, resolve) => { let callback = (response) => { resolve(response.data); }; if (!task[key]) task[key] = []; task[key].push(callback);};// 处理taskconst 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) => { // 判断是否存在请求中的当前请求 如果有取消当前请求 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); } ); // 请求执行前加入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";// 示例请求Getexport const exampleRequestGet = (data) => request("get", "/xxxx", data);// 示例请求Postexport 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";// 处理请求数据 (拼接url,data添加token等) 请根据实际情况调整const paramsHandler = (url, data) => { url = baseURL + url; data.token = "xxxx"; return { url, data };};// 处理全局接口返回的全局处理相关逻辑 请根据实际情况调整const resHandler = (res) => { // TODO 未授权跳转登录,状态码异常报错等 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" },
登录后复制
执行npm run test
功能正常,工具库准备完毕。
(eslint和prettier读者可视情况选用)
打包
一般项目的打包使用webpack,而工具库的打包则使用rollup
安装 Rollup
通过下面的命令安装 Rollup:
npm install --save-dev rollup
登录后复制
创建配置文件
在根目录创建一个新文件 rollup.config.js
export default { input: "src/index.js", output: { file: "dist/drrp.js", format: "esm", name: "drrp" }};
登录后复制
input —— 要打包的文件output.file —— 输出的文件 (如果没有这个参数,则直接输出到控制台)output.format —— Rollup 输出的文件类型安装babel
如果要使用 es6 的语法进行开发,还需要使用 babel 将代码编译成 es5。因为rollup的模块机制是 ES6 Modules,但并不会对 es6 其他的语法进行编译。
安装模块
rollup-plugin-babel 将 rollup 和 babel 进行了完美结合。
npm install --save-dev rollup-plugin-babel@latestnpm install --save-dev @babel/core npm install --save-dev @babel/preset-env
登录后复制
根目录创建 .babelrc
{ "presets": [ [ "@babel/preset-env", { "modules": false } ] ]}
登录后复制
兼容 commonjs
rollup 提供了插件 rollup-plugin-commonjs,以便于在 rollup 中引用 commonjs 规范的包。该插件的作用是将 commonjs 模块转成 es6 模块。
rollup-plugin-commonjs 通常与 rollup-plugin-node-resolve 一同使用,后者用来解析依赖的模块路径。
安装模块
npm install --save-dev rollup-plugin-commonjs rollup-plugin-node-resolve
登录后复制
压缩 bundle
添加 UglifyJS 可以通过移除注上释、缩短变量名、重整代码来极大程度的减少 bundle 的体积大小 —— 这样在一定程度降低了代码的可读性,但是在网络通信上变得更有效率。
安装插件
用下面的命令来安装 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"},
登录后复制
即可执行npm run build将/src/index.js打包为/dist/drrq.js
发包前的准备
准备npm账号,通过npm login或npm adduser。这里有一个坑,终端内连接不上npm源,需要在上网工具内复制终端代理命令后到终端执行才能正常连接。
准备一个简单清晰的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": "能自动取消重复请求的axios封装", "type": "module", "keywords": [ "取消重复请求", ], "dependencies": { "axios": "^1.2.0", "qs": "^6.11.0" }, "scripts": { "test": "node example", "build": "rollup -c" }, "devDependencies": { ... }}
登录后复制
name 包名称 一定不能与npm已有的包名重复,想一个简单易记的private 是否为私有version 版本main 入口文件位置repository git仓库地址author 作者license 协议description 描述keywords 关键词,便于检索最后,执行npm publish就搞定啦
更多node相关知识,请访问:nodejs 教程!
以上就是手把手带你从0开始创建并发布npm包的详细内容,更多请关注php中文网其它相关文章!