【零难度】构建发布一个npm依赖包

113 阅读5分钟

为了简单教学,一切从简。此npm包作为一个工具库,存放一些简单的工具函数。

我们先从如何使用此工具包来看

一. 如何安装

只需在你的前端项目终端输入安装依赖命令即可 npm install sy-front-end-utils

二. 如何使用

分为两种引入 CommonJS 和 ES Module

// CommonJS

const { formatDate } =require('sy-front-end-utils');
// ES Module

import { formatDate } from'sy-front-end-utils';

示例

const date = new Date('2024-01-08 14:30:00');
formatDate(date); // 返回: "2024-01-08 14:30:00"

毋庸置疑 就和使用第三方依赖一摸一样

三. 如何构建

首先在根目录放入主函数文件index.js

/**
 * 日期格式化
 * @param {Date} date 日期对象
 * @param {string} format 格式字符串
 * @returns {string} 格式化后的日期字符串
 */
export const formatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  const seconds = String(date.getSeconds()).padStart(2, '0');

  return format
    .replace('YYYY', year)
    .replace('MM', month)
    .replace('DD', day)
    .replace('HH', hours)
    .replace('mm', minutes)
    .replace('ss', seconds);
};

/**
 * 深拷贝对象
 * @param {Object} obj 要拷贝的对象
 * @returns {Object} 拷贝后的新对象
 */
export const deepClone = (obj) => {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  if (obj instanceof Date) {
    return new Date(obj);
  }

  if (obj instanceof Array) {
    return obj.map(item => deepClone(item));
  }

  if (obj instanceof Object) {
    const copy = {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        copy[key] = deepClone(obj[key]);
      }
    }
    return copy;
  }
};

/**
 * 防抖函数
 * @param {Function} func 要执行的函数
 * @param {number} wait 等待时间
 * @returns {Function} 防抖后的函数
 */
export const debounce = (func, wait = 300) => {
  let timeout;
  
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

/**
 * 节流函数
 * @param {Function} func 要执行的函数
 * @param {number} limit 时间间隔
 * @returns {Function} 节流后的函数
 */
export const throttle = (func, limit = 300) => {
  let inThrottle;
  return function executedFunction(...args) {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
};

/**
 * 生成指定范围内的随机整数
 * @param {number} min 最小值
 * @param {number} max 最大值
 * @returns {number} 随机整数
 */
export const randomInteger = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1)) + min;
};

/**
 * 检查是否为有效的电子邮件地址
 * @param {string} email 电子邮件地址
 * @returns {boolean} 是否有效
 */
export const isValidEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};

/**
 * 获取URL参数
 * @param {string} name 参数名
 * @returns {string|null} 参数值
 */
export const getUrlParam = (name) => {
  const urlParams = new URLSearchParams(window.location.search);
  return urlParams.get(name);
};

/**
 * 将对象转换为查询字符串
 * @param {Object} obj 要转换的对象
 * @returns {string} 查询字符串
 */
export const objectToQueryString = (obj) => {
  return Object.keys(obj)
    .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
    .join('&');
};

/**
 * 将驼峰命名转换为短横线命名
 * @param {string} str 驼峰命名的字符串
 * @returns {string} 短横线命名的字符串
 */
export const camelToKebabCase = (str) => {
  return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
};

/**
 * 将短横线命名转换为驼峰命名
 * @param {string} str 短横线命名的字符串
 * @returns {string} 驼峰命名的字符串
 */
export const kebabToCamelCase = (str) => {
  return str.replace(/-./g, (x) => x[1].toUpperCase());
};

package.json 文件

{
  "name": "your-package-name",
  "version": "1.0.0",
  "description": "你的包描述",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "你的名字",
  "license": "MIT"
}

在 package.json 中:

  • 修改 name 为你的包名

  • 更新 description 描述

  • 添加相关的 keywords

  • 修改 author 信息

做到这两步一个简单npm依赖包也就可以准备发布了

要发布肯定要将项目先打包

npm init -y

引入babel依赖帮我们在不同浏览器做兼容处理

npm install --save-dev @babel/core @babel/cli @babel/preset-env

package.json 文件中加入build命令

image.png

"build": "node build.js"

到这里npm包就可以发布了

可以先将发布命令告诉你

npm login

npm run build

npm publish

没错就是这么简单,只需要在终端依次输入这三个命令,没有报错就成功了

请先别急,让我们继续完善一些

以下为可选选项,需要则加

a. 创建README.md文件

用来介绍你的依赖包

b. 加入测试用例

1.安装 Jest 和相关依赖 npm install --save-dev jest @babel/core @babel/preset-env babel-jest

  1. 创建 babel 配置文件,以支持 ES 模块语法
{
  "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
} 
  1. 创建测试文件夹和测试文件
import { 
  formatDate, 
  deepClone, 
  debounce, 
  isValidEmail,
  randomInteger,
  camelToKebabCase,
  kebabToCamelCase 
} from '../index.js';

describe('工具函数测试', () => {
  // 测试 formatDate
  test('formatDate 格式化日期', () => {
    const date = new Date('2024-01-08 14:30:00');
    expect(formatDate(date)).toBe('2024-01-08 14:30:00');
  });

  // 测试 deepClone
  test('deepClone 深拷贝对象', () => {
    const original = { a: 1, b: { c: 2 } };
    const cloned = deepClone(original);
    cloned.b.c = 3;
    expect(original.b.c).toBe(2);
    expect(cloned.b.c).toBe(3);
  });

  // 测试 isValidEmail
  test('isValidEmail 验证邮箱格式', () => {
    expect(isValidEmail('test@example.com')).toBe(true);
    expect(isValidEmail('invalid-email')).toBe(false);
  });

  // 测试 randomInteger
  test('randomInteger 生成随机整数', () => {
    const num = randomInteger(1, 10);
    expect(num).toBeGreaterThanOrEqual(1);
    expect(num).toBeLessThanOrEqual(10);
  });

  // 测试 camelToKebabCase
  test('camelToKebabCase 驼峰转短横线', () => {
    expect(camelToKebabCase('backgroundColor')).toBe('background-color');
  });

  // 测试 kebabToCamelCase
  test('kebabToCamelCase 短横线转驼峰', () => {
    expect(kebabToCamelCase('background-color')).toBe('backgroundColor');
  });
}); 
  1. 修改 package.json 的测试脚本
 "scripts": {
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "build": "node build.js"
  },
  1. 运行测试 npm test 监视模式(在开发时很有用)npm run test:watch

提示:Jest 会生成测试覆盖率报告,你可以在 coverage 目录中查看详细报告。

c. 兼容commonJs和ES模块兼容

  1. 修改package.json并创建相应的入口文件
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "exports": {
    ".": {
      "require": "./dist/index.cjs",
      "import": "./dist/index.mjs"
    }
  },
  1. 创建构建脚本 build.js
import { build } from 'esbuild';

// 构建 ESM 版本
build({
  entryPoints: ['./index.js'],
  outfile: 'dist/index.mjs',
  bundle: true,
  format: 'esm',
  platform: 'neutral',
  target: ['es2015'],
  minify: true,
  sourcemap: true
}).catch(() => process.exit(1));

// 构建 CommonJS 版本
build({
  entryPoints: ['./index.js'],
  outfile: 'dist/index.cjs',
  bundle: true,
  format: 'cjs',
  platform: 'neutral',
  target: ['es2015'],
  minify: true,
  sourcemap: true
}).catch(() => process.exit(1)); 
  1. 安装构建依赖

npm install --save-dev esbuild

d.压缩文件

在build.js中有两个参数

   minify: true //压缩代码,移除空格和注释

   sourcemap: true //生成 source map 文件,方便调试

这样会生成压缩后的文件:

  • dist/index.mjs(压缩后的 ES 模块版本)

  • dist/index.mjs.map(ES 模块的 source map)

  • dist/index.cjs(压缩后的 CommonJS 版本)

  • dist/index.cjs.map(CommonJS 的 source map)

四. 如何发布

必须使用npm源 npm config set registry https://registry.npmjs.org/ 如果使用是淘宝源,无法发布。可能都无法登录,因为淘宝源仅有只读属性

# 1. 登录到 npm(如果还没登录)

npm login

# 2. 构建项目

npm run build

# 3. 发布新版本

npm publish

发布完成后可更换为淘宝源npm config set registry https://registry.npmmirror.com

五. 仓库地址

gitee仓库