从本地测试到私有仓库发布的NPM包开发

我们在使用 node 开发过程中,经常会下载很多第三方包,那些包都是别人封装好的,我们只需要引用即可使用,但是有些时候,我们需要自己封装一些包,这个时候,我们就需要自己动手做一个 npm 包了。

下面我将介绍如何做一个 npm 包,以及 npm 包发布。

首先是一个项目的目录结构,可以看出和我们平时开发 vue 项目很像,只是多了一个packages目录,用来存放我们的主要项目文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
www
├─lib 模块集编译目录
├─node_modules 扩展包文件目录
├─packages 工具包目录
├─public 入口文件目录
│ ├─favicon.ico 站点图标
│ └─index.html 入口文件
├─src 页面主目录
│ ├─router 路由文件夹
│ ├─views 页面文件夹
│ ├─app.vue 主页入口
│ └─main.js 主核心文件
├─test 自动化测试用例目录
│ ├─unit 单元测试用例目录
│ └─... 更多
├─.eslintignore 代码格式检测忽略配置文件
├─.eslintrc.js 代码格式检测配置文件
├─.gitignore git忽略配置文件
├─.npmignore npm忽略配置文件
├─.prettierrc.js IDE格式化代码配置文件
├─babel.config.js JS编译器babel配置文件
├─jsconfig.json JavaScript 语言服务配置选项文件
├─package.json 项目包的所有相关信息文件
├─pnpm-lock.yaml pnpm包锁文件
├─README.md README 文件
└─vue.config.js 项目配置文件

在这个项目中,我们需要将我们对外提供的内容都放到 packages 中,其中 /packages/index.js 中统一引入后对外暴露。

1
2
3
4
5
// /packages/index.js

import api from "./utils/api";
import axios from "./axios/index";
export default { api: api, axios: axios };

其中 /src/ 目录下,也可以引入自已 packages 中的库,进行本地页面上测试,或在 /test/ 目录中编用库的单元测试用例进行验证。

我们需要重点关注以下几个文件的内容:

1、.npmrc

这个文件里定义了 npm 的注册地址,默认是 https://registry.npmjs.org/

如果要发布到 npm 的私有仓库,需要修改为私有仓库的注册地址。

1
registry=https://registry.npmjs.org/

2、package.json

这个文件里定义了包的一些信息,比如版本号,描述,作者,许可证等,其中脚本 lib 就是将 packages/index.js 进行编译后,输出到 lib 目录下,供其他项目引用的处理命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
{
"name": "@webape/api",
"version": "0.0.1",
"description": "api请求库",
"private": false,
"keywords": ["api"],
"author": {
"name": "webape",
"email": "webape@qq.com"
},
"license": "ISC",
"publishConfig": {
"@webape:registry": "http://npm.webape"
},
"main": "lib/ng.umd.js",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lib": "vue-cli-service build --target lib --name ng --dest lib packages/index.js",
"lint": "vue-cli-service lint src packages"
},
"dependencies": {
"axios": "^1.4.0",
"core-js": "^3.32.0",
"crypto-js": "^4.1.1",
"element-ui": "^2.15.13",
"qs": "^6.11.2",
"vue": "^2.7.14",
"vue-router": "^3.6.5",
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/core": "^7.22.10",
"@babel/eslint-parser": "^7.22.10",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-plugin-unit-jest": "~5.0.8",
"@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/test-utils": "^1.3.6",
"@vue/vue2-jest": "^27.0.0",
"babel-jest": "^27.5.1",
"babel-polyfill": "^6.26.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-vue": "^8.7.1",
"jest": "^27.5.1",
"jest-serializer-vue": "2.0.2",
"less": "^4.2.0",
"less-loader": "^8.1.1",
"msw": "^1.2.5",
"prettier": "^2.8.8",
"vue-template-compiler": "^2.7.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {},
"overrides": [
{
"files": ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
"env": {
"jest": true
}
}
]
},
"browserslist": ["> 1%", "last 2 versions", "not dead"],
"jest": {
"preset": "@vue/cli-plugin-unit-jest",
"moduleNameMapper": {
"^@tests/(.*)$": "<rootDir>/tests/$1",
"^@packages/(.*)$": "<rootDir>/packages/$1"
},
"transformIgnorePatterns": ["/node_modules[/\\\\]/"]
}
}

3、.npmignore

这个文件里定义了 npm 推送包时需要忽略的文件列表,默认是忽略 node_modules 目录下的文件,这里可以忽略其他不需要发布的文件。

一般情况下,我们只需要推送 package.json 文件和 lib 文件夹即可,所以可以添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dist
node_modules
packages
pnpm-store
public
src
tests
.env.*
.eslintignore
.eslintrc.js
.gitignore
.npmignore.js
.npmrc.js
.prettierrc.js
babel.config.js
devCookie.txt
jsconfig.json
pnpm-lock.yaml
vue.config.js

4、vue.config.js

这个文件里定义了 vue-cli 的配置,这里可以定义一些打包时的配置,比如打包时的路径,打包时的环境变量等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const { defineConfig } = require("@vue/cli-service");

const path = require("path");
const fs = require("fs");

function resolve(dir) {
return path.join(__dirname, dir);
}

const Timestamp = new Date().getTime();

module.exports = defineConfig({
publicPath: process.env.NODE_ENV === "production" ? "/page/embed/" : "./",
productionSourceMap: process.env.NODE_ENV !== "production",
outputDir: "dist",
assetsDir: "static",
lintOnSave: true,
devServer: {
host: "0.0.0.0",
port: 8080,
compress: false,
proxy: {
"/api": {
target: "https://webapet.net/api/",
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
secure: false,
},
},
},
chainWebpack: (config) => {
config.resolve.alias.set("@", resolve("src"));
config.resolve.alias.set("@packages", resolve("packages"));

// 删除预加载和预获取
config.plugins.delete("preload");
config.plugins.delete("prefetch");
},

configureWebpack: {
performance: {
hints: "warning",
maxEntrypointSize: 50000000,
maxAssetSize: 30000000,
assetFilter: function (assetFilename) {
return assetFilename.endsWith(".js");
},
},
},
// 以避免构建后的代码中出现未转译的第三方依赖
transpileDependencies: [/node_modules[/\\\\]/],

css: {
extract: {
filename: `./static/css/[name].${process.env.VUE_APP_VERSION}.${Timestamp}.css`,
chunkFilename: `./static/css/[name].${process.env.VUE_APP_VERSION}.${Timestamp}.css`,
},
},
});

这里有项目代码根目录的部分文件示例源文件

至于想自已搭建私有 npm 仓库,可以查看npm 私服搭建

构建

1
npm run lib

推送

1
npm publish

一些 npm 常用操作命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 强制删除指定版本的库
npm unpublish @webape/demo@0.0.1 --force
# 强制删除库
npm unpublish @webape/demo --force
# 废弃指定版本的库
npm deprecate @webape/demo@0.0.1 '不在更新了'
# 注册用户
npm adduser --registry http://npm.webape
# 登录用户
npm login --registry http://npm.webape
# 设置新密码
npm profile set password