构建并发布您的第一个 NPM 包

2025-06-08

构建并发布您的第一个 NPM 包

你创建了一段可复用的代码,想与大家分享,或者你只是有个想法,可以在不同的项目中派上用场。但你完全不知道如何开始编写代码、创建 npm 包,甚至不知道如何发布已有的代码。

我曾经创建过一些小型包,例如ICollectionsNgx-indexed-dbReact-indexed-db,现在我想帮助你创建并发布你的第一个包。本教程将专注于如何创建一个简单的包,我不会涉及一些对项目有益的内容,例如使用 TypeScript、语义发布、CI 等等。

我们将共同构建一个在日常应用中非常有用的自定义 React Hook,实现一个简单的切换状态。如果您不熟悉 React Hooks,请查看此链接:React Hooks 文档

这个想法是能够通过 NPM 运行来安装包

npm install useToggle

然后在任何项目中使用它,如下面的代码所示:

import React from 'react';
import useToggle from 'useToggle';

const App = () => {
  const [isLoading, toggleLoading] = useToggle(true);
  return (
    <div>
      <button onClick={toggleLoading}>Toggle</button>
      {isLoading ? <div>loading...</div> : <div>Content</div>}
    </div>
  );
};

export default App;

让我们开始创建一个我将命名为的文件夹useToggle,导航到文件夹内部并将其初始化为 npm 包。

在控制台中运行以下命令:

mkdir useToggle // to create the folder
cd useToggle // to navigate inside the folder
npm init // to initialize the the npm inside the folder

当我们运行时,npm init我们必须回答一些应该很简单的问题。这是我最后一条命令的最终结果:

{
  "name": "usetoggle",
  "version": "1.0.0",
  "description": "React hook to facilitate the state toggle",
  "main": "lib/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "react",
    "hooks",
    "toggle"
  ],
  "author": "Charles Assuncao",
  "license": "ISC"
}

安装依赖项

我们需要一些东西来创建项目,让我们通过 npm 安装它:

我们需要useStateReact 来让包工作,所以让我们将其作为常规依赖项进行安装

npm install react

我们将在这里使用 babel 来转换和压缩最终代码:

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

请注意,这次我们传递了标志--save-dev来表示该依赖项仅用于开发我们的代码,但它不是包工作的依赖项。

我们需要测试代码,确保一切按预期运行。记住:未经测试的代码就是有问题的代码!由于我们要创建自定义钩子,因此需要 React Hooks 测试库。

npm install --save-dev jest @testing-library/react-hooks react-test-renderer

动手,让我们编码吧!

编写测试

让我们开始编写测试,并更加关注代码的预期运行方式。测试驱动开发有几个优点,我强烈建议你深入阅读它。

创建我们要保存代码的文件夹:

mkdir src

在此文件夹中创建三个新文件:

index.js

useToggle.js

useToggle.spec.js

我们的项目现在基本上是这样的:

├── package-lock.json
├── package.json
├── node_modules
├── src
│   ├── index.js
│   ├── useToggle.js
│   ├── useToggle.spec.js

由于我们安装了 jest 来运行测试,我们现在需要在我们的package.json

"scripts": {
    "test": "jest"
}

我喜欢 Jest 的这种简洁,无需任何配置。现在我们可以运行npm run test并执行我们的 specs 文件了。接下来,让我们创建第一个测试:

//useToggle.spec.js

import { renderHook } from '@testing-library/react-hooks';
import useToggle from './useToggle';

describe('useToggle Hook', () => {
  test('Should initiate with false as default', () => {
    const { result } = renderHook(() => useToggle());
    expect(result.current[0]).toBe(false);
  });
});

这里发生了什么?

我们为我们的钩子“useToggle Hook”创建了一个测试套件,第一个测试是检查钩子中初始化的默认值。renderHook执行钩子并返回一个包含钩子返回值的对象,该值保存在 result.current 中。在我们的例子中,钩子将返回一个包含状态值的数组和一个用于修改状态的函数。所以基本上:

result.current[0] // is our state, by default false
result.current[1] // is the toggleState function

如果我们现在运行npm run test测试,结果会是红色。因为文件里什么都没有useToggle.js。所以,我们来创建一个简单的函数,让测试结果变成绿色:

//useToggle.js

export default function useToggle(initialState = false) {
  return [initialState];
}

现在运行测试,看到绿色代表幸福 

我们的函数已经返回了默认初始值为 false 的数组。让我们思考并创建一些测试来测试我们的钩子如何工作:

//useToggle.spec.js
import { renderHook, act } from '@testing-library/react-hooks';
import useToggle from './useToggle';

describe('useToggle Hook', () => {
  test('Should initiate with false as default', () => {
    const { result } = renderHook(() => useToggle());
    expect(result.current[0]).toBe(false);
  });

  test('Should initiate with the provided value', () => {
    const { result } = renderHook(() => useToggle(true));
    expect(result.current[0]).toBe(true);
  });

  test('Should toggle the value from false to true', () => {
    const { result } = renderHook(() => useToggle());
    act(() => {
      result.current[1]();
    });
    expect(result.current[0]).toBe(true);
  });
});

前两个测试会通过,我们的useToggle函数返回的是满足前两个测试要求的状态模拟。但我们的钩子实际上并没有执行任何操作。所以让我们改变一下,让测试再次顺利通过。

import { useState } from 'react';

export default function useToggle(initialState = false) {
  const [state, setState] = useState(initialState);

  function toggleState() {
    setState(!state);
  }

  return [state, toggleState];
}

我们从 react 中导入useState,并使用它来保存我们的初始值并通过函数来​​改变它,setState但不是返回setState函数,而是创建一个闭包来切换状态值,使其按照我们的预期表现。

现在运行测试,所有测试都通过后,控制台会闪闪发光。不过,我们再创建一些测试,只是为了好玩。最终的测试文件将如下所示:

import { renderHook, act } from '@testing-library/react-hooks';
import useToggle from './useToggle';

describe('useToggle Hook', () => {
  test('Should initiate with false as default', () => {
    const { result } = renderHook(() => useToggle());
    expect(result.current[0]).toBe(false);
  });

  test('Should initiate with the provided value', () => {
    const { result } = renderHook(() => useToggle(true));
    expect(result.current[0]).toBe(true);
  });

  test('Should toggle the value from false to true', () => {
    const { result } = renderHook(() => useToggle());
    act(() => {
      result.current[1]();
    });
    expect(result.current[0]).toBe(true);
  });

  test('Should toggle the value from true to false', () => {
    const { result } = renderHook(() => useToggle(true));
    act(() => {
      result.current[1]();
    });
    expect(result.current[0]).toBe(false);
  });

  test('Should execute multiple toggles', () => {
    const { result } = renderHook(() => useToggle()); //init false
    // false -> true
    act(() => {
      result.current[1]();
    });
    // true -> false
    act(() => {
      result.current[1]();
    });
    // false -> true
    act(() => {
      result.current[1]();
    });
    // true -> false
    act(() => {
      result.current[1]();
    });
    expect(result.current[0]).toBe(false);
  });
});

最后但同样重要的是,我们应该从入口点导出钩子index.js。只需一行代码即可完成:

// index.js

export { default } from './useToggle';

建筑

让我们配置构建脚本,我们需要用到 Babel。所以,我们创建一个 Babel 配置文件 (babel.config.js)。我们的配置应该非常简单:

//babel.config.js

module.exports = {
  presets: ['@babel/preset-env', 'minify'],
};

并在我们的 package.json 中创建一个构建脚本:

"scripts": {
    "test": "jest",
    "build": "babel src --out-dir lib"
}

现在我们可以运行npm run build并且它将生成lib/index.js文件。

发布

为了发布它,我们需要对项目进行一些小的修改package.json。让我们配置一下应该包含在包中的文件,以及一个每次尝试发布包时都会运行的特殊脚本。此外,我们将把 React 依赖项更改为 peerDependency,因为我们期望使用我们包的项目已经有自己的 React 版本:

"files": [
    "lib"
  ],
  "scripts": {
    ...
    "prepublish": "npm run build"
  }, 
  . . .
  "peerDependencies": {
    "react": "^16.9.0"
  },

运行npm login并使用我们之前在 npm 站点创建的凭证。成功登录后,您可以运行 now npm publish。现在,您的软件包已经存在于 npm 软件包的世界中,任何人都可以使用它,只需运行npm install useToggle

鏂囩珷鏉ユ簮锛�https://dev.to/assuncaocharles/building-and-publishing-your-first-npm-package-1kpb
PREV
React.memo(明智地使用我)
NEXT
Async/await 仍然会给你带来很多惊喜!