Heim > Web-Frontend > js-Tutorial > Hauptteil

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

青灯夜游
Freigeben: 2022-12-08 20:26:57
nach vorne
2786 Leute haben es durchsucht

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

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

背景

4月发布了一篇文章,快来升级你项目内的axios封装,向重复请求say goodbye,介绍了axios的二次封装用于支持常规请求及自定义请求,并对同一时间内的相同请求做拦截处理(如果您没有阅读过这篇文章,建议您花费3分钟大致了解)。恰逢最近准备写一个跨框架组件库(工作量很大,前端三个小伙伴利用空闲时间在卷,待组件库完善后会分享给大家,敬请期待),需要学习发布npm包,昨天就想着利用空闲时间把之前写的去除重复请求的axios封装发布为npm包,便于代码复用,回馈社区的同时也能学以致用。

阅读本文,你将收获:

  • 从0开始创建并发布npm的全过程。【相关教程推荐:nodejs视频教程编程教学

  • 一个持续迭代且简单实用的axios请求去重工具库。

工具库准备

创建一个新项目,包含package.json

{
    "name": "drrq",
    "type": "module",
    "version": "1.0.0"
}
Nach dem Login kopieren

功能实现 /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 = { &#39;Content-Type&#39;: &#39;application/json&#39; };
const postHeaders = { &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39; };
const fileHeaders = { &#39;Content-Type&#39;: &#39;multipart/form-data&#39; };

const request = (method, url, params, headers, preventRepeat = true, uploadFile = false) => {
    let key = url + &#39;?&#39; + 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 === &#39;post&#39; || method === &#39;put&#39; ? { data: !uploadFile ? qs.stringify(params) : params } : { params }))
            .then((response) => {
                // 处理task
                handleTask(key, response);
            })
            .catch(() => {});
    });
};

export const get = (url, data = {}, preventRepeat = true) => {
    return request(&#39;get&#39;, url, data, getHeaders, preventRepeat, false);
};
 export const post = (url, data = {}, preventRepeat = true) => {
     return request(&#39;post&#39;, url, data, postHeaders, preventRepeat, false);
 };
 export const file = (url, data = {}, preventRepeat = true) => {
     return request(&#39;post&#39;, url, data, fileHeaders, preventRepeat, true);
 };
export default { request, get, post, file };
Nach dem Login kopieren

新增示例代码文件夹/example

示例入口index.js

import { exampleRequestGet } from &#39;./api.js&#39;;
const example = async () => {
    let res = await exampleRequestGet();
    console.log(&#39;请求成功 &#39;);
};
example();
Nach dem Login kopieren

api列表api.js

import { request } from &#39;./request.js&#39;;
// 示例请求Get
export const exampleRequestGet = (data) => request(&#39;get&#39;, &#39;/xxxx&#39;, data);

// 示例请求Post
export const exampleRequestPost = (data) => request(&#39;post&#39;, &#39;/xxxx&#39;, data);

// 示例请求Post 不去重
export const exampleRequestPost2 = (data) => request(&#39;post&#39;, &#39;/xxxx&#39;, data, false);

// 示例请求Post 不去重
export const exampleRequestFile = (data) => request(&#39;file&#39;, &#39;/xxxx&#39;, data, false);
Nach dem Login kopieren

全局请求封装request.js

import drrq from &#39;../src/index.js&#39;;
const baseURL = &#39;https://xxx&#39;;

// 处理请求数据  (拼接url,data添加token等) 请根据实际情况调整
const paramsHandler = (url, data) => {
    url = baseURL + url;
    data.token = &#39;xxxx&#39;;
    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 == &#39;get&#39; || method == &#39;GET&#39; || method == &#39;Get&#39;) {
        res = await drrq.get(url, data, preventRepeat);
    }
    if (method == &#39;post&#39; || method == &#39;POST&#39; || method == &#39;Post&#39;) {
        res = await drrq.post(url, data, preventRepeat);
    }
    if (method == &#39;file&#39; || method == &#39;FILE&#39; || method == &#39;file&#39;) {
        res = await drrq.file(url, data, preventRepeat);
    }
    return resHandler(res);
};
Nach dem Login kopieren

测试功能

代码写完后,我们需要验证功能是否正常,package.json加上

    "scripts": {
        "test": "node example"
    },
Nach dem Login kopieren

执行npm run test

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

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

(eslint和prettier读者可视情况选用)

打包

一般项目的打包使用webpack,而工具库的打包则使用rollup

安装 Rollup

通过下面的命令安装 Rollup:

npm install --save-dev rollup
Nach dem Login kopieren

创建配置文件

在根目录创建一个新文件 rollup.config.js

export default {
  input: "src/index.js",
  output: {
    file: "dist/drrp.js",
    format: "esm",
    name: &#39;drrp&#39;
  }
};
Nach dem Login kopieren
  • 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
Nach dem Login kopieren

根目录创建 .babelrc

{
    "presets": [
      [
        "@babel/preset-env",
        {
          "modules": false
        }
      ]
    ]
}
Nach dem Login kopieren

兼容 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
Nach dem Login kopieren

压缩 bundle

添加 UglifyJS 可以通过移除注上释、缩短变量名、重整代码来极大程度的减少 bundle 的体积大小 —— 这样在一定程度降低了代码的可读性,但是在网络通信上变得更有效率。

安装插件

用下面的命令来安装 rollup-plugin-uglify

npm install --save-dev rollup-plugin-uglify
Nach dem Login kopieren

完整配置

rollup.config.js 最终配置如下

import resolve from &#39;rollup-plugin-node-resolve&#39;;
import commonjs from &#39;rollup-plugin-commonjs&#39;;
import babel from &#39;rollup-plugin-babel&#39;;
import { uglify } from &#39;rollup-plugin-uglify&#39;;
import json from &#39;@rollup/plugin-json&#39;

const paths = {
    input: {
        root:  &#39;src/index.js&#39;,
    },
    output: {
        root:  &#39;dist/&#39;,
    },
};

const fileName = `drrq.js`;

export default {
    input: `${paths.input.root}`,
    output: {
        file: `${paths.output.root}${fileName}`,
        format: &#39;esm&#39;,
        name: &#39;drrq&#39;,
    },
    plugins: [
        json(),
        resolve(),
        commonjs(),
        babel({
            exclude: &#39;node_modules/**&#39;,
            runtimeHelpers: true,
        }),
        uglify(),
    ],
};
Nach dem Login kopieren

在package.json中加上

"scripts": {
    "build": "rollup -c"
},
Nach dem Login kopieren

即可执行npm run build将/src/index.js打包为/dist/drrq.js

发包前的准备

准备npm账号,通过npm login或npm adduser。这里有一个坑,终端内连接不上npm源,需要在上网工具内复制终端代理命令后到终端执行才能正常连接。

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

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

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

修改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": {
       ...
    }
}
Nach dem Login kopieren
  • 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就搞定啦

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

Führen Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf

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

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

更多node相关知识,请访问:nodejs 教程

Das obige ist der detaillierte Inhalt vonFühren Sie Sie Schritt für Schritt durch die Erstellung und Veröffentlichung von NPM-Paketen von Grund auf. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage