前言

  • Hey, 我是 Immerse
  • 系列文章首发于【Immerse】,更多内容请关注该网站
  • 转载说明:转载请注明原文出处及版权声明!

1. 理解 NPM 包的结构

1.1 package.json 文件:包的核心

package.json文件是 NPM 包的中央配置,定义了包的各个方面,从基本元数据到复杂的发布配置。

{
"name": "my-awesome-package",
"version": "1.0.0",
"description": "一个令人惊叹的包",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"test": "jest"
},
"keywords": ["awesome", "package"],
"author": "Your Name <you@example.com>",
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^4.5.5",
"tsup": "^5.11.13",
"jest": "^27.4.7"
}
}

让我们详细解析一些关键字段:

  • nameversion:这两个字段组成了包在 NPM 注册表中的唯一标识符。
  • mainmoduletypes:这些指定了不同模块系统和 TypeScript 支持的入口点。
  • files:这个数组指定了发布包时应该包含哪些文件和目录。
  • scripts:这些是常见任务(如构建和测试)的命令快捷方式。

1.2 理解包的入口点

现代 JavaScript 生态系统支持多种模块格式。您的包应该通过提供多个入口点来适应不同的环境。

  1. main:主要入口点,通常用于 CommonJS (CJS)模块。
  2. module:用于 ECMAScript (ESM)模块的入口点。
  3. browser:用于浏览器环境的入口点。
  4. types:TypeScript 类型声明的入口点。

以下是一个包结构的示例:

my-awesome-package/
├── src/
│ ├── index.ts
│ └── utils.ts
├── dist/
│ ├── index.js (CJS构建)
│ ├── index.mjs (ESM构建)
│ ├── index.d.ts (TypeScript声明)
│ └── browser.js (浏览器特定构建)
├── package.json
└── tsconfig.json

对应的package.json配置:

{
"name": "my-awesome-package",
"version": "1.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"browser": "./dist/browser.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
}
}

2. 深入理解模块格式

2.1 CommonJS (CJS)

CommonJS 是 Node.js 的传统模块格式。它使用require()进行导入,使用module.exports进行导出。

// mathUtils.js
function add(a, b) {
return a + b;
} function subtract(a, b) {
return a - b;
} module.exports = {
add,
subtract,
}; // main.js
const mathUtils = require('./mathUtils');
console.log(mathUtils.add(5, 3)); // 输出: 8

2.2 ECMAScript 模块 (ESM)

ESM 是 JavaScript 模块的现代标准,使用importexport语句。

// mathUtils.mjs
export function add(a, b) {
return a + b;
} export function subtract(a, b) {
return a - b;
} // main.mjs
import { add, subtract } from './mathUtils.mjs';
console.log(add(5, 3)); // 输出: 8

2.3 通用模块定义 (UMD)

UMD 是一种允许模块在多种环境(CommonJS、AMD、全局变量)中工作的模式。

(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports);
} else {
// 浏览器全局变量
factory((root.mathUtils = {}));
}
})(typeof self !== 'undefined' ? self : this, function (exports) {
exports.add = function (a, b) {
return a + b;
};
exports.subtract = function (a, b) {
return a - b;
};
});

3. 高级包优化技术

3.1 Tree Shaking 和副作用

Tree shaking 是现代打包工具用来消除死代码的技术。要使您的包可以进行 tree shaking:

  1. 使用 ES 模块
  2. 避免副作用
  3. package.json中使用"sideEffects"字段
{
"name": "my-utils",
"version": "1.0.0",
"sideEffects": false
}

如果某些文件确实有副作用:

{
"name": "my-utils",
"version": "1.0.0",
"sideEffects": ["./src/polyfills.js", "*.css"]
}

3.2 代码分割和动态导入

对于大型包,考虑使用代码分割,允许用户只导入他们需要的部分:

// heavyFunction.js
export function heavyFunction() {
// ... 一些计算密集型操作
} // main.js
async function doHeavyWork() {
const { heavyFunction } = await import('./heavyFunction.js');
heavyFunction();
}

3.3 条件导出

使用条件导出为不同的环境或导入条件提供不同的入口点:

{
"name": "my-package",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"browser": "./dist/browser.js"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}

4. 版本管理和发布

4.1 语义化版本控制 (SemVer)

语义化版本使用三部分版本号:主版本号.次版本号.修订号

  • 主版本号:进行不兼容的 API 更改时
  • 次版本号:以向后兼容的方式添加功能时
  • 修订号:进行向后兼容的 bug 修复时
npm version patch -m "版本更新到 %s - 修复文档中的拼写错误"
npm version minor -m "版本更新到 %s - 添加新的实用函数"
npm version major -m "版本更新到 %s - 更改API结构"

4.2 预发布版本

对于预发布版本,使用带连字符的标签:

  • latest: 最新线上版本
  • alpha: 内部测试版本
  • beta: 公开测试版本
  • rc: 发行候选版本
    • Tips: 可以将这些标识符添加到版本号中,同时也可以添加额外版本:如:1.0.0-alpha.01.0.0-beta.11.0.0-rc.1
npm version prerelease --preid=alpha
# 1.0.0 -> 1.0.1-alpha.0 npm version prerelease --preid=beta
# 1.0.1-alpha.0 -> 1.0.1-beta.0 npm version prerelease --preid=rc
# 1.0.1-beta.0 -> 1.0.1-rc.0

4.3 使用标签发布

使用标签发布不同版本或预发布版本:

npm publish --tag next
npm publish --tag beta

用户可以安装特定版本:

npm install my-package@next
npm install my-package@beta

5. 持续集成和部署 (CI/CD)

5.1 使用 GitHub Actions 进行自动发布

创建一个.github/workflows/publish.yml文件:

name: 发布包

on:
release:
types: [created] jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: 'https://npm.pkg.github.com'
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

这个工作流程将在您创建新版本时自动将您的包发布到 NPM 和 GitHub Packages。

5.2 自动化版本更新

您可以在 CI/CD 管道中自动化版本更新。以下是使用 GitHub Action 的示例:

name: 更新版本

on:
push:
branches:
- main jobs:
bump-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: 更新版本
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
npm version patch -m "更新版本到 %s [skip ci]"
- name: 推送更改
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}

这个动作将在每次向主分支推送更改时自动更新包的修订版本号。

6. 包开发最佳实践

6.1 文档

良好的文档对于包的采用至关重要。考虑使用像 JSDoc 这样的工具进行内联文档:

/**
* 将两个数字相加。
* @param {number} a - 第一个数字。
* @param {number} b - 第二个数字。
* @returns {number} a和b的和。
*/
function add(a, b) {
return a + b;
}

6.2 测试

使用像 Jest 这样的框架实现全面的测试:

// math.js
export function add(a, b) {
return a + b;
} // math.test.js
import { add } from './math'; test('1 + 2 应该等于 3', () => {
expect(add(1, 2)).toBe(3);
});

6.3 代码检查和格式化

使用 ESLint 进行代码检查,使用 Prettier 进行代码格式化。以下是一个示例.eslintrc.js

module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {
// 在这里添加自定义规则
},
};

以及一个.prettierrc文件:

{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"printWidth": 100
}

NPM 包开发与优化全面指南的更多相关文章

  1. npm包开发与发布

    把通用的功能开发成npm包,便用使用和维护,更重要的是可以分享给广大的开发者,是不是很激动人心! 那么,步骤如下: 1.创建项目 创建项目目录,npm init ,根据需要输入配置信息(建完后也可以在 ...

  2. 记npm包开发全过程

    概述 为什么开发npm包? 如何开发? 如何写单元测试? package.json 如何发布模块? 如何使用? 为什么开发npm模块? NPM的全称是Node Package Manager,是一个N ...

  3. npm包开发(whale-makelink)

    whale-makelink是一个npm工具,是强业务的工具,可以将当前工程目录下的项目文件夹,在README中生成项目的链接地址.Demo. 一.npm init 使用npm init生成packa ...

  4. 如何开发一个npm包并发布

    一.安装nodejs 不多说了,网上教程多得是 二.创建自己的npm包 目录结构 npm-test a.js b.js package.json 开发 为了简单便于理解,就开发一个简单地hello程序 ...

  5. VS Code项目中通过npm包的方式共享代码片段的方案实现

    VS Code项目中通过npm包的方式共享代码片段的方案实现 上周在 "VS Code项目中共享自定义的代码片段方案" 的文章中提到过一个共享代码片段的方案,上周经过调研后并没有发 ...

  6. 在2018年如何优雅的开发一个typescript语言的npm包?

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由小明plus发表 很多时候,我们可能想要用 typescript 语言来创建一些模块,并提交到 npm 供别人使用, 那么在 2018 ...

  7. 从零系列--开发npm包(一)

    一.目的 主要是纪录和回顾自己开发的一些步骤以及遇到的一些问题和解决方案 二.准备工作 1.IDE 选择 VS Code 2.安装node 环境 (https://nodejs.org/zh-cn/) ...

  8. 如何开发一个npm包并发布到npm中央仓库

    转自: https://liaolongdong.com/2019/01/24/publish-public-npm.html 如何开发一个npm包并发布到npm中央仓库需求背景:平时在项目工作中可能 ...

  9. 快速开发一个npm包(轮子)

    动机 很多人都想写一个自己的轮子,可是开始动手的时候你总会遇到以下问题 一个基本的 js 库应该如何编写 基本的前端项目都要哪些文件 又要怎么打包发布到 npm 上 你的 es6 语法如何才能让别人识 ...

  10. 开发一个健壮的npm包

    项目地址:loan-calculate-utils npm包的发布.更新查看上一篇文章 开发一个基础的npm包 目前我们的目录是这个样子: . ├── source 源代码目录 │   └── ind ...

随机推荐

  1. vue 识别\n \t 等字符(转载)

    使用.text(str)时,str中有"\n",但是页面显示却没有换行,需要在所在的 div添加属性 style="white-space: pre-line" ...

  2. [学习笔记] LCA - 图论

    [NOIP2013 提高组] 货车运输 最大生成树+LCA+倍增 好家伙,这道题我写了一个晚上,调了两个晚上,对于这道题我颇有感触.但这道题确实好,实实在在的蓝题,让我发现了许多关于LCA的问题. 首 ...

  3. 初探 Rust 语言与环境搭建

    1. Rust 简介 Rust 的历史 起源:Rust 语言最初由 Mozilla 研究员 Graydon Hoare 于 2006 年开始设计,并于 2009 年首次公开. 开发:Rust 是 Mo ...

  4. Ubuntu 配置默认编辑器

    在 Ubuntu 系统中,可以通过多种方式配置默认的文本编辑器,以便在使用命令行工具(如 crontab 或 visudo)时自动使用你喜欢的编辑器.以下是几种常见的方法: 使用 update-alt ...

  5. Seata 1.3.0 Oracle 回滚测试验证 报错 ORA-02289: 序列不存在

    使用Seata 1.3.0版本,测试A服务调用B服务,且A方法中,手动写了一个异常,测试是否正常回滚(Mysql已经测试过) 发现报错:ORA-02289: 序列不存在 一看就是undo_log这张表 ...

  6. shell脚本字符串截取方法整理

    首先先声明一个变量str,下面演示以该变量为例: str='https://www.baidu.com/about.html' 1.#号截取,删除左边字符,保留右边字符 echo ${str#*//} ...

  7. HTML & CSS – Practice Projects

    前言 学完了 w3school 就要练练手了. 这篇是记入我学习的过程, 和知识点. update: 2022-02-27 用文章来表达太难了, 用视频比较合理. 所以我就没有继续写了. 这里记入几篇 ...

  8. [TK] HH的项链 离线树状数组解法

    实际上这题很难和树状数组联系起来,我感觉效率也不是很高,感觉不是正解 怎么使用树状数组 这道题我们很容易想到一点:同种物品在一个区间内只能出现一次,先不考虑别的问题,我们想出下面这种使用树状数组的思路 ...

  9. Phpstorm 插件

    Symfony Plugin - 支持 Symfony 2,3,4 ...Laravel Plugin - 支持 Laravel.env files support - 支持.env 文件BashSu ...

  10. Linux进程调度-组调度及带宽控制

    1. 概述 组调度(task_group)是使用Linux cgroup(control group)的cpu子系统来实现的,可以将进程进行分组,按组来分配CPU资源等. 比如,看一个实际的例子: A ...