npm发包 - zptime/blog GitHub Wiki

一. 如何发布自己的 npm 包

1. 创建 npm 账号

  1. 在 npm 官网https://www.npmjs.com/注册并创建 npm 账号

  2. 注册之后进入新首页https://www.npmjs.com/?track=newUserCreated,有如下提示:

  • It looks like you still do not have two-factor authentication(2FA) enabled on your npm account. To enable 2FA, please follow the instructions fount here(看起来您的 npm 帐户仍未启用双因素身份验证 (2FA)。要启用 2FA,请按照此处的说明进行操作)。
  • You have not verified yout email address. (您尚未验证您的电子邮件地址)
    • 点击这个提示,就会向邮箱发送验证邮件,在邮箱中操作即可

2. 创建 npm 包

先拿一个 utils 工具文件尝试,放入一些常用的工具函数,如手机号校验、金额格式化等

  1. 新建一个文件夹(shang-utils)
  2. 进入文件夹,运行 npm init 命令 如下图所示,会有一些初始化的配置项,大部分只要按 enter 就行,最后会生成一个 package.json 文件,之后也可以随时改。

图片效果展示

npm Docs 之 package.jsonhttps://docs.npmjs.com/cli/v8/configuring-npm/package-json

{
  "name": "shang-utils", // 包名,必须要独一无二
  "version": "1.0.0", // 版本号
  "author": "xxx", // 作者
  "description": "common toolkit", // 描述信息
  "keywords": ["utils", "format", "money", "phone"], // 关键词,提升SEO
  "repository": {
    // 代码托管位置
    "type": "git",
    "url": "https://github.com/xxx/shang-utils"
  },
  "license": "ISC", // 许可证
  "homepage": "https://your-package.org", // 包的主页或者文档首页
  "bugs": "https://github.com/xxx/shang-utils/issues", // 用户问题反馈地址
  "main": "index.js", // 入口文件
  "scripts": {
    // 存放可执行脚本
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    // 运行依赖
  },
  "devDependencies": {
    // 开发依赖
  }
}
  1. 完成工具函数文件
// a. index.js 入口文件
import Format from "./src/format";
import Validate from "./src/validate";

export default {
  Format,
  Validate,
};

// b. format.js 格式化文件
const Validate = {
  /**
   * 手机号校验
   */
  mobileCheck: (value) => /^[1][3,4,5,7,8][0-9]{9}$/.test(value),

  /**
   * 身份证校验
   */
  IDCardCheck: (value) =>
    /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(
      value
    ),

  /**
   * 邮箱校验
   */
  emailCheck: (value) =>
    /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(value),
};

export default Validate;


// c. validate.js 校验文件
const Format = {
  // 解决toFixed保留小数的问题
  formatToFixed: (money, decimals = 2) => {
    return (
      Math.round(
        (parseFloat(money) + Number.EPSILON) * Math.pow(10, decimals)
      ) / Math.pow(10, decimals)
    ).toFixed(decimals);
  },
  // 格式化金额展示: 12341234.246 -> $ 12,341,234.25
  formatMoney: (money, symbol = "", decimals = 2) =>
    formatToFixed(money, decimals)
      .replace(/\B(?=(\d{3})+\b)/g, ",")
      .replace(/^/, `${symbol}`),
};

export default Format;

3. npm 包上传 Github

  1. Github 上创建新仓库shang-utils

  2. 按照说明文档,上传项目,并初始化本地分支

git init
git add .
git commit -m "first commit"
git branch -M master
git remote add origin [email protected]:xxx/shang-utils.git
git push -u origin master

4. npm 包发布

  1. 检查 npm 源,如果是淘宝源,则需要改回 npm 源
// 查看npm镜像源地址
npm config get registry

// 切换npm镜像源

// 设置npm默认源
npm config set registry https://registry.npmjs.org/
// 设置npm镜像源为淘宝镜像
npm config set registry https://registry.npm.taobao.org/
  1. 在终端中切换到项目目录下,运行登陆命令,之后按照终端提示输入用户名、密码等信息即可
// 登陆
npm login

// 输入相关信息
Log in on https://registry.npmjs.org/
Username:  // 用户名
Password: // 密码
Email: (this IS public) // 邮箱
Enter one-time password: // 如果之前做过 双因素身份验证 (2FA),需要生成一次性密钥
Logged in as xxx on https://registry.npmjs.org/.

图片效果展示

  1. 运行发布命令
// 发布命令
npm publish

图片效果展示

我这个工具函数比较简单,一下就发布成功了,没有碰到其他问题。发布成功后,就可以登陆 npm 网站,查看发布包的情况了

图片效果展示

5. npm 包使用

在项目中安装依赖包

npm install shanglv-utils

安装成功后,可以在项目的 node_modules 中看见包文件

图片效果展示

使用举例:

// 导出方式
export { Format, Validate };

// 使用方式
import { Format, Validate } from "shanglv-utils";
Format.formatMoney(12341234.246, "$", 2); // $12,341,234.25
Validate.mobileCheck("123456"); // false

6. 更新 npm 包

如下所示:更新了一下说明文档,重新发布

// 自动更改版本号,并且commit
npm version xxx
// 控制台会返回下一个小版本号 如v1.0.1
npm version patch

// 重新发布
npm publish

图片效果展示

// patch:补丁号,修复bug,小变动,如 v1.0.0->v1.0.1
npm version patch

// minor:次版本号,增加新功能,如 v1.0.0->v1.1.0
npm version minor

// major:主版本号,不兼容的修改,如 v1.0.0->v2.0.0
npm version major

二. 使用 vite 发布自定义组件到 npm

之前是发布了工具函数,纯 JS 文件,现在尝试发布自定义组件

vite 初始化项目

#  创建项目
$ pnpm create vite vite-npm-package -- --template vue

# 运行项目
$ cd vite-npm-package
$ pnpm install
$ pnpm dev

编写组件

操作:新建 packages/button 目录,在该目录下创建 src/index.vue 文件和 index.js 文件

  1. packages/button/src/index.vue
<template>
  <button class="tButton">测试 发布 按钮组件</button>
</template>

<script>
  // name是必须要写的
  export default {
    name: "tButton",
  };
</script>

<style>
  .tButton {
    background-color: #1188ff;
    color: white;
    border: none;
    padding: 6px 12px;
  }
</style>
  1. packages/button/index.js
import tButton from "./src/index.vue";

// 如果想通过CDN方式引入,需要编写install函数
// 注册组件
tButton.install = function (Vue) {
  Vue.component(tButton.name, tButton);
};

export default tButton;

本地测试使用组件

<template>
  <div class="demo">
    <t-button class="block" />
  </div>
</template>

<script setup>
import tButton from "packages/button/src/index.vue";
</script>

<style scoped></style>

打包配置

操作:在 vite.config.js 中进行打包配置,然后运行打包命令

打包配置,可见 Vite 官网中文文档之构建生产版本:https://cn.vitejs.dev/guide/build.html

  1. 修改 vite.config.js 配置
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";

const resolve = (dir) => path.join(__dirname, dir);

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      "@": resolve("src"),
      packages: resolve("packages"),
    },
  },
  build: {
    lib: {
      // 注意此处的路径要配置正确,
      entry: resolve("packages/button/index.js"),
      name: "TButton",
      fileName: (format) => `tbutton.${format}.js`,
    },
    // 自定义构建配置,可直接调整底层Rollup选项;Rollup有一套预设
    // https://rollupjs.org/guide/en/#big-list-of-options
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ["vue"],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});
  1. 运行打包命令pnpm build,打包效果如下所示:

图片效果展示

打包后测试组件使用

<template>
  <div class="demo">
    <t-button />
  </div>
</template>

<script setup>
  import tButton from "../../dist/tbutton.es.js";
</script>

<style></style>

发布 npm 包

操作:在 package.json 文件中进行 npm 发布的相关配置,然后进行 npm 发布操作

  1. 修改 package.json
{
  "name": "vite-npm-package",
  "version": "1.0.4",
  "description": "使用vite发布自定义组件到npm",
  "files": ["dist"],
  "main": "./dist/tbutton.umd.js",
  "module": "./dist/tbutton.es.js",
  "exports": {
    ".": {
      "import": "./dist/tbutton.es.js",
      "require": "./dist/tbutton.umd.js"
    },
    "./dist/style.css": {
      "import": "./dist/style.css",
      "require": "./dist/style.css"
    }
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "license": "MIT",
  "private": false,
  "author": "zptime",
  "dependencies": {
    "vue": "^3.2.23"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.10.2",
    "vite": "^2.7.0"
  }
}
  1. 按照之前的方式发布到 npm

注意:不要忘记每次更改发布都要更改版本号

// 更新版本号,小版本,如1.0.0 -> 1.0.1
npm version patch

// 重新发布 v1.0.4成功
npm publish

图片效果展示

发布后使用举例

  1. 安装依赖
pnpm i vite-npm-package

图片效果展示

  1. 使用
<template>
  <div class="demo">
    <tButton />
  </div>
</template>

<script setup>
  import tButton from "vite-npm-package";
  import "vite-npm-package/dist/style.css";
</script>

报错

报错信息:Internal server error: Failed to resolve entry for package "vite-npm-package". The package may have incorrect main/module/exports specified in its package.json: Failed to resolve entry for package "vite-npm-package". The package may have incorrect main/module/exports specified in its package.json.

报错解析:之前写的 tButton 组件中,没有设置 name 值,这个是必须要设置的

参考文档:

三. 使用 vite 将基于 Ant Design Vue 二次封装的组件发布到 npm

之前对 Ant Design TreeSelect 组件进行了二次封装,可查看文章Antd TreeSelect 树选择控件“二次封装”及封装原理,现在使用 vite 将该组件发布到 npm 上

整体打包发布流程和使用 vite 发布自定义组件到 npm基本一致,如下只列出差异

Ant Design Vue 组件库引入

  1. 安装依赖
pnpm i ant-design-vue
pnpm i vite-plugin-components -D
  1. 按需引入配置
// 2.1 新增src/libs/antdv.js
import { TreeSelect } from "ant-design-vue";
const components = [TreeSelect];

export function setupAntd(app) {
  components.forEach((component) => {
    app.use(component);
  });
}

// 2.2 main.js引入
import { setupAntd } from "./libs/antdv";

const app = createApp(App);
setupAntd(app);
app.mount("#app");

// 2.3 vite.config.js配置
import ViteComponents, { AntDesignVueResolver } from "vite-plugin-components";

export default defineConfig({
  plugins: [
    vue(),
    ViteComponents({
      customComponentResolvers: [AntDesignVueResolver()],
    }),
  ],
});

组件编写

  1. 新建 packages/treeSelect/src/index.vue
<template>
  <a-tree-select
    v-bind="$attrs"
    placeholder="请选择"
    style="width: 100%"
    :tree-data="departSource"
    :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
    show-search
    allow-clear
    tree-default-expand-all
    treeNodeFilterProp="title"
    :getPopupContainer="(triggerNode) => triggerNode.parentNode"
    @select="handleSelect"
  >
  </a-tree-select>
</template>

<script>
  import { watchEffect } from "vue";
  export default {
    name: "tTreeSelect",
    props: {
      // 数据源
      departSource: {
        type: Array,
        default: [],
      },
    },
    setup(props, context) {
      console.log("props:", props);
      console.log("context.attrs:", context.attrs); // $attrs
      console.log("context.slots:", context.slots); // $slots
      console.log("context.emit:", context.emit); // $slots
      console.log("context.expose:", context.expose); // vue3.2 新增

      watchEffect(() => {
        console.log(`value is: ` + context.attrs.value);
      });

      const handleSelect = (value, node, extra) => {
        console.log(value, node, extra);
      };

      return { handleSelect };
    },
  };
</script>

<style scoped></style>
  1. 新建 packages/treeSelect/index.js 文件
import tTreeSelect from "./src/index.vue";

tTreeSelect.install = function (Vue) {
  Vue.component(tTreeSelect.name, tTreeSelect);
};

export default tTreeSelect;

打包配置

  1. 修改 vite.config.js 文件
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import ViteComponents, { AntDesignVueResolver } from "vite-plugin-components";
import path from "path";

const resolve = (dir) => path.join(__dirname, dir);

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    ViteComponents({
      customComponentResolvers: [AntDesignVueResolver()],
    }),
  ],
  resolve: {
    alias: {
      "@": resolve("src"),
      packages: resolve("packages"),
    },
  },
  build: {
    lib: {
      // 2. 基于Ant Design Vue TreeSelect二次封装的组件的打包配置
      entry: resolve("packages/treeSelect/index.js"),
      name: "TTreeSelect",
      fileName: (format) => `tTreeSelect.${format}.js`,
    },
    // 自定义构建配置,可直接调整底层Rollup选项;Rollup有一套预设
    // https://rollupjs.org/guide/en/#big-list-of-options
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ["vue"],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});
  1. 修改 package.json 文件
{
  "name": "vite-npm-package",
  "version": "2.0.0",
  "description": "使用vite发布自定义组件到npm",
  "files": ["dist"],
  "main": "./dist/tTreeSelect.umd.js",
  "module": "./dist/tTreeSelect.es.js",
  "exports": {
    ".": {
      "import": "./dist/tTreeSelect.es.js",
      "require": "./dist/tTreeSelect.umd.js"
    },
    "./dist/style.css": {
      "import": "./dist/style.css",
      "require": "./dist/style.css"
    }
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "license": "MIT",
  "private": false,
  "author": "zptime",
  "dependencies": {
    "ant-design-vue": "^3.0.0-alpha.14",
    "vue": "^3.2.23"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.10.2",
    "vite": "^2.7.0",
    "vite-plugin-components": "^0.13.3"
  }
}

使用举例

<template>
  <div class="demo">
    <t-tree-select
      :depart-source="departTree"
      v-model:value="departId"
      :fieldNames="fieldNames"
    />
  </div>
</template>

<script setup>
  // 1. 本地引入
  import tTreeSelect from "packages/treeSelect/src/index.vue";

  // 2. 打包后引入(pnpm build)
  import tTreeSelect from "../../dist/tTreeSelect.es.js";
  import "../../dist/style.css";

  // 3. 发包到npm后使用 v2.0.0
  import tTreeSelect from "vite-npm-package";
  import "vite-npm-package/dist/style.css";

  const fieldNames = {
    children: "children",
    label: "name",
    key: "id",
    value: "id",
  };

  const departTree = [
    {
      id: 1,
      name: "根部门",
      departNumber: 6,
      children: [
        {
          id: 11,
          pid: 1,
          name: "一级部门1",
          departNumber: 2,
          children: [
            { id: 111, pid: 11, name: "二级部门1", departNumber: 0 },
            { id: 112, pid: 11, name: "二级部门2", departNumber: 0 },
          ],
        },
        { id: 21, pid: 1, name: "一级部门2", departNumber: 0 },
        { id: 31, pid: 1, name: "一级部门3", departNumber: 0 },
        { id: 41, pid: 1, name: "一级部门4", departNumber: 0 },
        { id: 51, pid: 1, name: "一级部门5", departNumber: 0 },
        {
          id: 61,
          pid: 1,
          name: "一级部门6",
          departNumber: 2,
          children: [
            { id: 611, pid: 61, name: "二级部门3", departNumber: 0 },
            { id: 612, pid: 61, name: "二级部门4", departNumber: 0 },
          ],
        },
      ],
    },
  ];

  const departId = 11;
</script>

<style scoped></style>

使用效果:

ic_npm_11 ic_npm_12

四. vite 发布自定义组件库到 npm

主要是之前的两个组件 tButton 和 tTreeSelect,一起发布

  1. 在 package 文件夹下新增 index.js 文件
import tButton from "./button";
import tTreeSelect from "./treeSelect";

const components = [tButton, tTreeSelect];

// 如果想通过CDN方式引入,需要编写install函数
// 注册组件
const install = function (Vue) {
  components.forEach((component) => {
    Vue.component(component.name, component);
  });
};

if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue);
}

export default {
  install,
  tButton,
  tTreeSelect,
};
  1. 修改 vite.config.js 配置
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import ViteComponents, { AntDesignVueResolver } from "vite-plugin-components";
import path from "path";

const resolve = (dir) => path.join(__dirname, dir);

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    ViteComponents({
      customComponentResolvers: [AntDesignVueResolver()],
    }),
  ],
  resolve: {
    alias: {
      "@": resolve("src"),
      packages: resolve("packages"),
    },
  },
  build: {
    lib: {
      // 3. 发布自定义组件库,包含tButton 和 tTreeSelect两个组件
      entry: resolve("packages/index.js"),
      name: "tAntDesign",
      fileName: (format) => `tAntDesign.${format}.js`,
    },
    rollupOptions: {
      external: ["vue"],
      output: {
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});
  1. 修改 package.json 文件
{
  "name": "vite-npm-package",
  "version": "3.0.0",
  "description": "使用vite发布自定义组件库到npm",
  "files": ["dist"],
  "main": "./dist/tAntDesign.umd.js",
  "module": "./dist/tAntDesign.es.js",
  "exports": {
    ".": {
      "import": "./dist/tAntDesign.es.js",
      "require": "./dist/tAntDesign.umd.js"
    },
    "./dist/style.css": {
      "import": "./dist/style.css",
      "require": "./dist/style.css"
    }
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "license": "MIT",
  "private": false,
  "author": "zptime",
  "dependencies": {
    "ant-design-vue": "^3.0.0-alpha.14",
    "vite-npm-package": "^2.0.0",
    "vue": "^3.2.23"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.10.2",
    "vite": "^2.7.0",
    "vite-plugin-components": "^0.13.3"
  }
}
  1. 打包发布
// 打包
pnpm build

// 发布
npm publish
  1. 使用测试
<template>
  <div class="demo">
    本地引入
    <t-button class="block" />
    <t-tree-select
      :depart-source="departTree"
      v-model:value="departId"
      :fieldNames="fieldNames"
    />
  </div>
</template>

<script setup>
  import tAntDesign from "vite-npm-package";
  import "vite-npm-package/dist/style.css";
  const { tButton, tTreeSelect } = tAntDesign;
</script>
⚠️ **GitHub.com Fallback** ⚠️