• 技术文章 >web前端 >js教程

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

    青灯夜游青灯夜游2022-12-08 20:26:57转载89
    都2202年了,不会有人还不会发布npm包吧?下面本篇文章给大家分享一下从0开始创建并发布npm的全过程,希望对大家有所帮助!

    大前端零基础入门到就业:进入学习

    背景

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

    阅读本文,你将收获:

    工具库准备

    创建一个新项目,包含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

    image.png

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

    (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'
      }
    };

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

    image.png

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

    image.png

    修改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": {
           ...
        }
    }

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

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

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

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

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

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

    最后,执行npm publish就搞定啦

    image.png

    image.png

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

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

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

    以上就是手把手带你从0开始创建并发布npm包的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    专题推荐:nodejs​ node npm
    上一篇:Angular开发问题记录:组件数据不能实时更新到视图上 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 聊聊Node.js中的 GC (垃圾回收)机制• Node学习之聊聊Cookie-Session登录验证的工作原理• Node实战:运用Cookie&Session进行登录验证
    1/1

    PHP中文网