站长资讯网
最全最丰富的资讯网站

手把手带你从0开始创建并发布npm包

都2202年了,不会有人还不会发布npm包吧?下面本篇文章给大家分享一下从0开始创建并发布npm的全过程,希望对大家有所帮助!

手把手带你从0开始创建并发布npm包

前端(vue)入门到精通课程,老师在线辅导:联系老师
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API调试工具:点击使用

背景

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  //请求开始前推入pending const 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); };  // 创建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) => {                         // 判断是否存在请求中的当前请求 如果有取消当前请求                         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'; // 示例请求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';  // 处理请求数据  (拼接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

手把手带你从0开始创建并发布npm包

功能正常,工具库准备完毕。

(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@latest npm 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源,需要在上网工具内复制终端代理命令后到终端执行才能正常连接。

手把手带你从0开始创建并发布npm包

准备一个简单清晰的readme.md

手把手带你从0开始创建并发布npm包

修改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 包都需要一个版本,以便开发人员在安全地更新包版本的同时不会破坏其余的代码。npm 使用的版本系统被叫做 SemVer,是 Semantic Versioning 的缩写。

不要过分担心理解不了相较复杂的版本名称,下面是他们对基本版本命名的总结: 给定版本号 MAJOR.MINOR.PATCH,增量规则如下:

  • MAJOR 版本号的变更说明新版本产生了不兼容低版本的 API 等,

  • MINOR 版本号的变更说明你在以向后兼容的方式添加功能,接下来

  • PATCH 版本号的变更说明你在新版本中做了向后兼容的 bug 修复。

表示预发布和构建元数据的附加标签可作为 MAJOR.MINOR.PATCH 格式的扩展。

最后,执行npm publish就搞定啦

手把手带你从0开始创建并发布npm包

手把手带你从0开始创建并发布npm包

本文的完整代码已开源至gitee.com/yuanying-11… ,感兴趣的读者欢迎fork和star!

转载地址:https://juejin.cn/post/7172240485778456606

赞(0)
分享到: 更多 (0)
网站地图   沪ICP备18035694号-2    沪公网安备31011702889846号