全新全网最全的NPM发包教程

alt

今天我们将从一个空目录开始,一步步地介绍如何将一个包发布到npm,发布一个完全生产可用的包。这将包括:

  • 使用Git进行版本控制
  • 使用TypeScript编写代码并保持类型安全
  • 使用Prettier格式化代码
  • 使用 @arethetypeswrong/cli检查我们的导出
  • 使用tsup将TypeScript代码编译为CJS和ESM
  • 使用Vitest运行我们的测试
  • 使用GitHub Actions运行我们的CI流程
  • 使用Changesets进行版本控制和发布我们的包

.1:初始化仓库

运行以下命令以初始化一个新的git仓库:

git init

1.2:设置.gitignore

在项目的根目录创建一个.gitignore文件,并添加以下内容:

node_modules

1.3:创建初始提交

运行以下命令以创建初始提交:

git add .
git commit -m "Initial commit"

1.4:在GitHub上创建新仓库

使用GitHub CLI,运行以下命令创建一个新仓库。在这个例子中,我选择了tt-package-demo这个名字:

gh repo create tt-package-demo --source=. --public

1.5:推送到GitHub

运行以下命令将你的代码推送到GitHub:

git push --set-upstream origin main

在这一部分,我们将创建一个package.json文件,添加一个license字段,创建一个LICENSE文件,并添加一个README.md文件。

2.1:创建package.json文件

创建一个包含以下值的package.json文件:

{
  "name""tt-package-demo",
  "version""1.0.0",
  "description""A demo package for Total TypeScript",
  "keywords": ["demo""typescript"],
  "homepage""https://github.com/yourgithub/tt-package-demo",
  "bugs": {
    "url""https://github.com/yourgithub/tt-package-demo/issues"
  },
  "author""Matt Pocock <team@totaltypescript.com> (https://totaltypescript.com)",
  "repository": {
    "type""git",
    "url""git+https://github.com/yourgithub/tt-package-demo.git"
  },
  "files": ["dist"],
  "type""module"
}
  • name是人们将用来安装你的包的名称。它在npm上必须是唯一的。你可以免费创建组织作用域(例如 @total-typescript/demo),这可以帮助它变得唯一。
  • version是你的包的版本。它应该遵循语义版本控制: 0.0.1格式。每次发布新版本时,你都应该增加这个数字。
  • descriptionkeywords是你的包的简短描述。它们在npm注册表的搜索中列出。
  • homepage是你的包主页的URL。GitHub仓库是一个很好的默认选择,或者如果你有一个文档网站的话。
  • bugs是人们可以报告你的包问题的地方的URL。
  • author是你!你可以选择添加你的电子邮件和网站。如果你有多个贡献者,你可以使用相同的格式将它们指定为 contributors数组。
  • repository是你的包的仓库URL。这在npm注册表上创建了一个指向你的GitHub仓库的链接。
  • files是当人们安装你的包时应包括的文件数组。在这种情况下,我们包括了 dist文件夹。 README.mdpackage.jsonLICENSE默认被包括在内。
  • type设置为 module表示你的包使用ECMAScript模块,而不是CommonJS模块。

2.2:添加license字段

package.json中添加一个license字段。在这里选择一个许可证。我选择了MIT。

{
  "license""MIT"
}

2.3:添加LICENSE文件

创建一个名为LICENSE(无扩展名)的文件,包含你的许可证文本。对于MIT,这是:

MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

[year][fullname]占位符更改为当前年份和你的姓名。

2.4:添加README.md文件

创建一个README.md文件,描述你的包。这里是一个例子:

**tt-package-demo**

Total TypeScript的一个演示包。

这将在人们在npm注册表查看你的包时显示。

3.1:安装TypeScript

运行以下命令来安装TypeScript:

npm install --save-dev typescript

我们添加--save-dev来将TypeScript作为开发依赖安装。这意味着当人们安装你的包时,它不会被包含在内。

3.2:设置tsconfig.json

创建一个包含以下值的tsconfig.json

{
  "compilerOptions": {
    /* 基础选项:*/
    "esModuleInterop"true,
    "skipLibCheck"true,
    "target""es2022",
    "allowJs"true,
    "resolveJsonModule"true,
    "moduleDetection""force",
    "isolatedModules"true,
    "verbatimModuleSyntax"true,

    /* 严格性 */
    "strict"true,
    "noUncheckedIndexedAccess"true,
    "noImplicitOverride"true,

    /* 如果使用TypeScript转译:*/
    "module""NodeNext",
    "outDir""dist",
    "rootDir""src",
    "sourceMap"true,

    /* 如果你正在为库构建:*/
    "declaration"true,

    /* 如果你正在为monorepo中的库构建:*/
    "declarationMap"true
  }
}

这些选项在我的TSConfig备忘单中有详细解释。

3.3:为DOM配置你的tsconfig.json

如果你的代码在DOM中运行(即需要访问documentwindowlocalStorage等),则跳过此步骤。

如果你的代码不需要访问DOM API,将以下内容添加到你的tsconfig.json

{
  "compilerOptions": {
    // ...其他选项
    "lib": ["es2022"]
  }
}

这可以防止DOM类型声明在你的代码中可用。

如果你不确定,跳过此步骤。

3.4:创建一个源文件

创建一个名为src/utils.ts的文件,内容如下:

export const add = (a: number, b: number) => a + b;

3.5:创建一个索引文件

创建一个名为src/index.ts的文件,内容如下:

export { add } from "./utils.js";

.js扩展名看起来可能有些奇怪。本文有更多解释。

3.6:设置build脚本

将以下内容添加到你的package.jsonscripts对象中:

{
  "scripts": {
    "build""tsc"
  }
}

这将编译你的TypeScript代码为JavaScript。

3.7:运行构建

运行以下命令来编译你的TypeScript代码:

npm run build

这将在dist文件夹中创建编译后的JavaScript代码。

3.8:将dist添加到.gitignore

dist文件夹添加到你的.gitignore文件中:

dist

这将防止编译后的代码被包含在你的git仓库中。

3.9:设置ci脚本

将以下内容添加到你的package.jsonscripts对象中,设置ci脚本:

{
  "scripts": {
    "ci""npm run build"
  }
}

这为我们在CI上运行所有必需的操作提供了一个快捷方式。

4.1: 安装Prettier

运行以下命令安装Prettier:

npm install --save-dev prettier

4.2: 设置.prettierrc

创建一个.prettierrc文件,包含以下内容:

{
  "semi"true,
  "singleQuote"true,
  "trailingComma""all",
  "printWidth"80,
  "tabWidth"2
}

你可以向此文件添加更多选项以自定义Prettier的行为。你可以在这里找到完整的选项列表https://prettier.io/docs/en/options.html

4.3: 设置format脚本

package.json中添加一个format脚本,包含以下内容:

{
  "scripts": {
    "format""prettier --write ."
  }
}

这将使用Prettier格式化项目中的所有文件。

4.4: 运行format脚本

运行以下命令格式化项目中的所有文件:

npm run format

你可能会注意到一些文件发生了变化。提交它们:

git add .
git commit -m "Format code with Prettier"

4.5: 设置check-format脚本

package.json中添加一个check-format脚本,包含以下内容:

{
  "scripts": {
    "check-format""prettier --check ."
  }
}

这将检查项目中的所有文件是否已正确格式化。

4.6: 添加到我们的CI脚本

check-format脚本添加到package.json中的ci脚本:

{
  "scripts": {
    "ci""npm run build && npm run check-format"
  }
}

这将在CI流程中运行check-format脚本。

5: exports, main@arethetypeswrong/cli

在这一部分,我们将安装@arethetypeswrong/cli,设置check-exports脚本,运行check-exports脚本,设置main字段,再次运行check-exports脚本,设置ci脚本,并运行ci脚本。

@arethetypeswrong/cli是一个检查你的包导出是否正确的工具。这很重要,因为这些地方容易出错,可能会给使用你的包的人带来问题。

5.1: 安装@arethetypeswrong/cli

运行以下命令安装@arethetypeswrong/cli

npm install --save-dev @arethetypeswrong/cli

5.2: 设置check-exports脚本

package.json中添加一个check-exports脚本,包含以下内容:

{
  "scripts": {
    "check-exports""attw --pack ."
  }
}

这将检查你的包的所有导出是否正确。

5.3: 运行check-exports脚本

运行以下命令检查你的包的所有导出是否正确:

npm run check-exports

你应该会发现有多个错误:

┌───────────────────┬──────────────────────┐
│                   │ "tt-package-demo"    │
├───────────────────┼──────────────────────┤
│ node10            │ 💀 Resolution failed │
├───────────────────┼──────────────────────┤
│ node16 (from CJS) │ 💀 Resolution failed │
├───────────────────┼──────────────────────┤
│ node16 (from ESM) │ 💀 Resolution failed │
├───────────────────┼──────────────────────┤
│ bundler           │ 💀 Resolution failed │
└───────────────────┴──────────────────────┘

这表明没有Node的版本或任何打包器可以使用我们的包。

让我们来解决这个问题。

5.4: 设置main

package.json中添加一个main字段,包含以下内容:

{
  "main""dist/index.js"
}

这告诉Node在哪里可以找到你包的入口点。

5.5: 再次尝试check-exports

运行以下命令检查你的包的所有导出是否正确:

npm run check-exports

你应该只会注意到一个警告:

┌───────────────────┬──────────────────────────────┐
│                   │ "tt-package-demo"            │
├───────────────────┼──────────────────────────────┤
│ node10            │ 🟢                           │
├───────────────────┼──────────────────────────────┤
│ node16 (from CJS) │ ⚠️ ESM (dynamic import only) │
├───────────────────┼──────────────────────────────┤
│ node16 (from ESM) │ 🟢 (ESM)                     │
├───────────────────┼──────────────────────────────┤
│ bundler           │ 🟢                           │
└───────────────────┴──────────────────────────────┘

这告诉我们我们的包与运行ESM的系统兼容。使用CJS的人(通常是在旧系统中)需要使用动态导入来导入它。

5.6 修复CJS警告

如果你不想支持CJS(我建议这样做),将check-exports脚本更改为:

{
  "scripts": {
    "check-exports""attw --pack . --ignore-rules=cjs-resolves-to-esm"
  }
}

现在,运行check-exports将显示一切正常:

┌───────────────────┬───────────────────┐
│                   │ "tt-package-demo" │
├───────────────────┼───────────────────┤
│ node10            │ 🟢                │
├───────────────────┼───────────────────┤
│ node16 (from CJS) │ 🟢 (ESM)          │
├───────────────────┼───────────────────┤
│ node16 (from ESM) │ 🟢 (ESM)          │
├───────────────────┼───────────────────┤
│ bundler           │ 🟢                │
└───────────────────┴───────────────────┘

如果你想同时发布CJS和ESM代码,可以跳过这一步。

5.7: 添加到我们的CI脚本

check-exports脚本添加到package.json中的ci脚本:

{
  "scripts": {
    "ci""npm run build && npm run check-format && npm run check-exports"
  }
}

如果你想同时发布CJS和ESM代码,你可以使用tsup。这是基于esbuild构建的工具,它可以将你的TypeScript代码编译成两种格式。

我个人的建议是跳过这一步,只发布ES模块。这会显著简化你的设置,并避免许多同时发布CJS和ESM的陷阱,比如双包危险。

但如果你想这样做,请继续。

6.1: 安装tsup

运行以下命令安装tsup

npm install --save-dev tsup

6.2: 创建tsup.config.ts文件

创建一个包含以下内容的tsup.config.ts文件:

import { defineConfig } from "tsup";

export default defineConfig({
  entryPoints: ["src/index.ts"],
  format: ["cjs""esm"],
  dts: true,
  outDir: "dist",
  clean: true,
});
  • entryPoints是你的包的入口点数组。在这种情况下,我们使用 src/index.ts
  • format是输出格式的数组。我们使用 cjs(CommonJS)和 esm(ECMAScript模块)。
  • dts是一个布尔值,告诉 tsup生成声明文件。
  • outDir是编译代码的输出目录。
  • clean告诉 tsup在构建之前清理输出目录。

6.3: 更改build脚本

package.json中的build脚本更改为以下内容:

{
  "scripts": {
    "build""tsup"
  }
}

我们现在将运行tsup来编译我们的代码,而不是tsc

由于篇幅限制,我将继续翻译剩余部分。

6.4: 添加exports字段

package.json中添加一个exports字段,包含以下内容:

{
  "exports": {
    "./package.json""./package.json",
    ".": {
      "import""./dist/index.js",
      "default""./dist/index.cjs"
    }
  }
}

exports字段告诉使用你包的程序如何找到你的包的CJS和ESM版本。在这种情况下,我们指引使用import的人到dist/index.js,使用require的人到dist/index.cjs

还建议将./package.json添加到exports字段。这是因为某些工具需要轻松访问你的package.json文件。

6.5: 再次尝试check-exports

运行以下命令检查你的包的所有导出是否正确:

npm run check-exports

现在,一切应该都是绿色的:

┌───────────────────┬───────────────────┐
│                   │ "tt-package-demo" │
├───────────────────┼───────────────────┤
│ node10            │ 🟢                │
├───────────────────┼───────────────────┤
│ node16 (from CJS) │ 🟢 (CJS)          │
├───────────────────┼───────────────────┤
│ node16 (from ESM) │ 🟢 (ESM)          │
├───────────────────┼───────────────────┤
│ bundler           │ 🟢                │
└───────────────────┴───────────────────┘

6.6: 将TypeScript转为linter

我们不再运行tsc来编译代码。而且tsup实际上并不检查代码中的错误 - 它只是将其转换为JavaScript。

这意味着如果我们的代码中有TypeScript错误,我们的ci脚本也不会报错。哎呀。

让我们来解决这个问题。

6.6.1: 在tsconfig.json中添加noEmit

tsconfig.json中添加一个noEmit字段:

{
  "compilerOptions": {
    // ...其他选项
    "noEmit"true
  }
}
6.6.2: 从tsconfig.json中移除未使用的字段

从你的tsconfig.json中移除以下字段:

  • outDir
  • rootDir
  • sourceMap
  • declaration
  • declarationMap

在我们新的'linting'设置中不再需要它们。

6.6.3: 将module更改为Preserve

可选地,你现在可以将tsconfig.json中的module更改为Preserve

{
  "compilerOptions": {
    // ...其他选项
    "module""Preserve"
  }
}

这意味着你不再需要使用.js扩展名来导入你的文件。这意味着index.ts可以这样写:

export * from "./utils";
6.6.4: 添加lint脚本

package.json中添加一个lint脚本,包含以下内容:

{
  "scripts": {
    "lint""tsc"
  }
}

这将使用TypeScript作为linter运行。

6.6.5: 将lint添加到你的ci脚本

lint脚本添加到package.json中的ci脚本:

{
  "scripts": {
    "ci""npm run build && npm run check-format && npm run check-exports && npm run lint"
  }
}

现在,我们的CI流程中将包括TypeScript错误。

7.1: 安装vitest

运行以下命令安装vitest

npm install --save-dev vitest

7.2: 创建测试

创建一个名为src/utils.test.ts的文件,包含以下内容:

import { add } from "./utils";
import { test, expect } from "vitest";

test("add"() => {
  expect(add(12)).toBe(3);
});

这是一个简单的测试,检查add函数是否返回正确的值。

7.3: 设置test脚本

package.json中添加一个test脚本,包含以下内容:

{
  "scripts": {
    "test""vitest run"
  }
}

vitest run会一次性运行项目中的所有测试,不进入监视模式。

7.4: 运行test脚本

运行以下命令来运行你的测试:

npm run test

你应该看到以下输出:

✓ src/utils.test.ts (1)
   ✓ add

测试文件 1 个通过 (1)
    测试用例 1 个通过 (1)

这表明你的测试已成功通过。

7.5: 设置dev脚本

一种常见的工作流程是在开发时以监视模式运行测试。在package.json中添加一个dev脚本,包含以下内容:

{
  "scripts": {
    "dev""vitest"
  }
}

这将以监视模式运行你的测试。

7.6: 添加到我们的CI脚本

test脚本添加到package.json中的ci脚本:

{
  "scripts": {
    "ci""npm run build && npm run check-format && npm run check-exports && npm run lint && npm run test"
  }
}

8.1: 创建我们的工作流程

创建一个.github/workflows/ci.yml文件,包含以下内容:

name: CI

on:
  pull_request:
  push:
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: npm install

      - name: Run CI
        run: npm run ci

这个文件是GitHub用来运行你的CI流程的指令。

  • name是工作流程的名称。
  • on指定了工作流程应该何时运行。在这种情况下,它在拉取请求和推送到 main分支时运行。
  • concurrency防止多个工作流程实例同时运行,使用 cancel-in-progress取消任何正在进行的运行。
  • jobs是一组要运行的工作。在这种情况下,我们有一个名为 ci的工作。
  • actions/checkout@v4从仓库检出代码。
  • actions/setup-node@v4设置Node.js和npm。
  • npm install安装项目的依赖项。
  • npm run ci运行项目的CI脚本。

如果我们的CI流程的任何部分失败,工作流程将失败,GitHub会通过在提交旁边显示一个红色的叉来通知我们。

8.2: 测试我们的工作流程

将更改推送到GitHub,并在仓库的Actions选项卡中检查。你应该看到你的工作流程正在运行。

这将在对存储库进行的每次提交和每次PR时给出警告。

9.1: 安装@changesets/cli

运行以下命令初始化Changesets:

npm install --save-dev @changesets/cli

9.2: 初始化Changesets

运行以下命令初始化Changesets:

npx changeset init

这将在你的项目中创建一个.changeset文件夹,包含一个config.json文件。这也是你的changeset将存放的地方。

9.3: 使changeset发布公开

.changeset/config.json中,将access字段更改为public

// .changeset/config.json
{
  "access""public"
}

如果不更改此字段,changesets不会将你的包发布到npm。

9.4: 将commit设置为true

.changeset/config.json中,将commit字段更改为true

// .changeset/config.json
{
  "commit"true
}

这将在版本控制后将changeset提交到你的仓库。

9.5: 设置本地发布脚本(续)

{
  "scripts": {
    "local-release""changeset version && changeset publish"
  }
}

这个脚本将运行CI流程,然后发布你的包到npm。这是当你想从本地机器发布新版本的包时运行的命令。

9.6: 在prepublishOnly中运行CI(续)

{
  "scripts": {
    "prepublishOnly""npm run ci"
  }
}

这将确保在将包发布到npm之前自动运行CI流程。

9.7: 添加一个更改集

运行以下命令添加一个更改集:

npx changeset

这将打开一个交互式提示,你可以在这里添加一个更改集。更改集是一种将更改分组并赋予它们版本号的方式。

将此版本标记为patch,并添加一个描述,如“初始发布”。

这将在.changeset文件夹中创建一个新的更改集文件。

9.8: 提交你的更改

将更改提交到你的仓库:

git add .
git commit -m "Prepare for initial release"

9.9: 运行本地发布脚本

运行以下命令发布你的包:

npm run local-release

这将运行CI流程,对你的包进行版本控制,并将其发布到npm。

它还将在你的仓库中创建一个CHANGELOG.md文件,详细记录此版本中的更改。每次你发布时,这都会更新。

9.10: 在npm上查看你的包

访问以下网址查看你的包:

http://npmjs.com/package/<your-package-name>

你应该能够在那里看到你的包!你做到了!你已经成功地发布到了npm!

总结

现在,你已经完全设置好了你的包。你已经设置了:

  • 一个带有最新设置的TypeScript项目
  • Prettier,既可以格式化你的代码,也可以检查它是否正确格式化
  • @arethetypeswrong/cli,检查你的包的导出是否正确
  • tsup,将你的TypeScript代码编译成JavaScript
  • vitest,运行你的测试
  • GitHub Actions,运行你的CI流程
  • Changesets,帮助你的包进行版本控制和发布

github还支持设置Changesets GitHub操作和PR机器人,可以更好的实现自动化。

本文由 mdnice 多平台发布

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值