构建发布一个npm
前提
下载 Node.js 和 NPM。
1
2node -v
npm -v选一个好的包名。包名必须是 pascal-case 并且全部小写。因为 NPM 上有 700k+ 的包,所以你需要在构建之前先去 www.npmjs.com/ 查询你的包名有没有被使用。在本文中,我取了 irene-awesome-gretter。
基本构建
选择一个合适的名字创建包文件夹。
1
mkdir irene-awesome-greeter && cd irene-awesome-greeter
新建
1
.gitignore
文件并写入 node_modules,新建
1
README
文件。
1
2echo "node_modules" >> .gitignore
echo "# Irene Awesome Greeter" >> README.mdGit 初始化包并关联远程仓库。下面 Git Repository Url 是远程仓库的 Url。
1
2
3
4git init
git add . && git commit -m "Initial commit"
git remote add origin <Git Repository Url>
git push -u origin masterNPM 初始化包,后续我们会修改生成的
1
package.json
1
npm init -y
至此,生成的目录结构如下:
添加 TypeScript as devDependencies
下载 TypeScript。
1
npm i -D typescript
下载完后,在根目录下多了
node_modules
文件夹和package-lock.json
文件。为了编译
TypeScript
,我们需要一个配置文件,在根目录下新建tsconfig.json
,内容如下。⚠️注意我(译者)在原文配置的基础上添加了 lib,指定了 es6,原因见 - 详情。1
2
3
4
5
6
7
8
9
10
11
12{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"strict": true,
"lib": ["es6"]
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]
}下面解释下配置文件字段:注意 ES6 = ES2015
target:编译之后生成的
JavaScript
文件需要遵循的标准,可选值:"ES3"(默认),"ES5","ES6"/"ES2015","ES2016","ES2017"或"ESNext"
。我们选择了es5
为了使包具有更好的浏览器兼容性。module:指定生成哪个模块系统代码,默认值:
target === "ES3" or "ES5" ? "CommonJS" : "ES6"
。declaration:是否生成对应的声明文件,默认值:false。在构建包时,应该设置为 true,这样
TypeScript
会将生成的声明文件和对应编译后的JavaScript
代码一起导出,以便包可以在TypeScript
和JavaScript
项目中同时使用。本项目中生成的声明文件是 /lib/index.d.ts。outDir:指定输出目录。编译后的
JavaScript
代码会在与tsconfig.json
同级的 lib 文件夹中。strict:是否启用所有严格类型检查选项,默认值:false。
lib:编译需要的库文件,例如你指定的 target 是
1
ES5
,但是在代码中使用了
1
ES6
特性,就需要在 lib 中加上 ES6。默认值:如果 lib 没有指定默认注入的库的列表,默认注入的库为:
- target ES5:DOM,ES5,ScriptHost。
- target ES6:DOM,ES6,DOM.Iterable,ScriptHost。
include:指定要编译的目录。
exclude:指定不编译的目录。
node_modules
和__tests__
只是在开发阶段使用,构建阶段无需编译。
更多的编译选项可参考 www.typescriptlang.org/docs/handbo…
创建
src
文件夹,在其下新建一个 index.ts,内容如下:1
export const Greeter = (name: string) => `Hello ${name}`;
在
package.json
中添加 build script。1
"build": "tsc"
在控制台运行 npm run build,你会看到生成了一个新的 lib 文件夹,里面是编译后的 js 代码和声明文件。
除了
package-lock.json
以外, 我们一般都不希望提交自动生成的文件到 Git 上去,因为只要改变了源文件,每次 build 的时候它们都不同,就会造成很多冲突,所以我们把 lib 文件夹放到.gitignore
中。1
2
3node_modules
/lib
复制代码
Formatting & Linting
下载
1
prettier tslint tslint-config-prettier
和
1
TypeScript
一样,它们只是在包开发阶段所需的工具,所以是 devDependencies。
1
npm i -D prettier tslint tslint-config-prettier
一个好的包应该包括严格的代码规范,尤其是有其他协作者共同开发时。
1
tslint-config-prettier
能防止
1
TSLint
和
1
Prettier
格式化规则的冲突。
在根目录下,新建
1
tslint.json
,添加如下内容:
1
2
3{
"extends": ["tslint:recommended", "tslint-config-prettier"]
}在根目录下,新建
1
.prettierrc
,添加如下内容:
1
2
3
4
5{
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true
}最后,在
1
package.json
中添加 lint 和 format script。
1
2"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
"lint": "tslint -p tsconfig.json"现在,你的
1
package.json
应该长这样:
在控制台运行 npm run lint / npm run format
1
2npm run lint
npm run format
移除不必要文件
在 .gitignore
中,我们添加 /lib 是因为不想 Git 远程仓库中有编译后的文件,但对于要发布的包则恰恰相反,我们不要源代码,只需要编译后的文件!有两种方式可以实现:
黑名单:新建一个
1
.npmignore
文件,在其中添加不要的文件(夹)。
1
2
3
4src
tsconfig.json
tslint.json
.prettierrc但是,这并不是一个好的实践,因为根目录下每次新增加一个文件(夹)都需要添加到
1
.npmignore
中,于是有了下面这种方式。
白名单:在
1
package.json
中设置一个要发布的文件(夹)白名单
1
"files": ["lib/**/*"]
就是这么的简单!只有 lib 文件夹会出现在发布的包里(README.md 和 package.json 会被默认添加)。更多关于黑名单 VS 白名单的内容可以参考
blog.npmjs.org/post/165769…
Jest Testing
一个好的包应该要包括单元测试。接下来,我们添加 Jest —— Facebook 开发的一个非常棒的测试框架。
因为我们是针对 ts 源文件编写测试用例,所以除了
1
jest
还需要
1
ts-jest @types/jest
,它们只是在开发阶段需要,所以添加到 devDependencies。
1
npm i -D jest ts-jest @types/jest
配置 Jest。有两种方式,在
1
package.json
添加 “jest” 字段 或者 新建一个单独的配置文件。我们选择后者,因为前者配置内容会被添加到发布的包中而后者不会。新建
1
jestconfig.json
,添加如下内容:
1
2
3
4
5
6
7{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}移除
1
package.json
中的 “test” script,添加一个新的 test script。
1
"test": "jest --config jestconfig.json"
现在,你的
1
package.json
长下面这样:
写一个测试用例
是时候写一个测试用例了。在 src
目录下,新建一个 __tests__
文件夹,在其中新建一个测试文件,文件名必须以 test.ts
结尾,例如:Greeter.test.ts。
1 | import { Greeter } from '../index'; |
这个测试用例验证了当输入 ‘Carl’ 的时候,Greeter 方法是否返回 ‘Hello Carl’。 接下来我们运行一下
1 | npm run test |
运行成功!可以看到测试用例通过了。
scripts in NPM
一个好的包应该尽可能自动化。接下来,我们来看看 NPM 中其他的 scripts:prepare,prepublishOnly,perversion,version,postversion
。
prepare:会在打包和发布包之前以及本地
1
npm install
(不带任何参数)时运行。
1
"prepare": "npm run build"
prepublishOnly:在
1
prepare
script 之前运行,并且仅在
1
npm publish
运行。在这里,我们可以运行
1
npm run test & npm run lint
以确保我们不会发布错误的不规范的代码。
1
"prepublishOnly": "npm run test && npm run lint"
preversion:在发布新版本包之前运行,为了更加确保新版本包的代码规范,我们可以在此运行
1
npm run lint
。
1
"preversion": "npm run lint"
version:在发布新版本包之后运行。如果您的包有关联远程 Git 仓库,像我们的情况一样,每次发布新版本时都会生成一个提交和一个新的版本标记,那么就可以在此添加规范代码的命令。又因为
1
version
script 在
1
git commit
之前运行,所以还可以在此添加
1
git add
。
1
"version": "npm run format && git add -A src"
postversion:在发布新版本包之后运行,在
1
git commit
之后运行,所以非常适合推送。
1
"postversion": "git push && git push --tags"
截至目前,我们的 NPM scripts 长下面这样:
1 | "scripts": { |
终极版 package.json
1 | { |
我们完善了 description,author,keywords 信息,修改了 main
,新增了 types
。 main
字段非常重要,因为指明了模块的入口。types
字段指明了声明文件的入口。
提交并推送到 Git
1 | git add -A && git commit -m "Setup Package" |
发布
在发布之前,如果你没有 NPM 账号的话,必须先注册一个。你可以在 www.npmjs.com/signup 上注册或者通过运行 npm adduser
注册。如果你已经有账号了,运行 npm login
登陆你的 NPM 账号。
1 | npm whoami |
显示出了自己的用户名就算登录成功了
好了!现在你可以发布了。
1 | npm publish |
可以看到,先运行 prepare
script,在其中运行了 npm run build
,然后运行 prepublishOnly
script,在其中运行了 npm run test && npm run lint
。
查看你的发布
现在,你可以去 NPM 上查看你刚刚发布的包。URL 是 https://npmjs.com/package/<your-package-name>
,我发布的包就是 npmjs.com/package/ire…
创建一个新版本
执行如下命令会创建一个新版本, preversion,version,postversion
scripts 会被执行:创建一个新的 tag 并且推送到我们的远程仓库。
1 | npm version patch |
我们再发布一次:
1 | npm publish |
现在,我们的包就有了一个 1.0.1 的版本:
如何撤销发布的包
终端执行 npm unpublish 例如:
1、npm unpublish
2、npm unpublish
不过撤包推荐用法: npm unpublish的推荐替代命令:npm deprecate [@] 使用这个命令,并不会在社区里撤销你已有的包,但会在任何人尝试安装这个包的时候得到警告 例如:npm deprecate z-tool ‘这个包我已经不再维护了哟~’
【注意】如果报权限方面的错,加上–force
发布错误集锦
1、需要提高版本号
1 | #1、发包 npm publish 失败 |
2、发包 npm publish 失败 解决方案:终端执行: npm publish –access public
参考:https://stackoverflow.com/questions/53420758/npm-publish-gives-unscoped-packages-cannot-be-private
1 | #1、发包 npm publish 失败 |
3、确保登录的用户账号正确
1 | sh-neverleave:npm neverleave$ npm publish |
4、登录时需要在username 前加‘~’,具体大家可以验证
1 | sh-neverleave:npm neverleave$ npm login |
5、无权限删除线上的包(撤包有时间限制,24小时) 解决方案:加上 –force
1 | sh-neverleave:z-tool neverleave$ npm unpublish z-tool |
6、删除npm市场的包同名的24小时后才能重新发布
1 | sh-neverleave:z-tool neverleave$ npm publish |
完结….