如何发布一个npm包

如何发布一个npm包

背景

应公司发展需要,需要把目前我们所用的编辑器,进行封装开源。如我们所知进行开源的项目封装和我们平时所写的项目和业务还是有所区别的,所以花了一点时间去了解相关写法。

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
│  .babelrc
│ .gitignore
│ .npmignore
│ package-lock.json
│ package.json
│ README.md
├─components // 组件源代码目录

├─config // webpack 配置文件
│ webpack.base.js
│ webpack.dev.config.js
│ webpack.prod.config.js

├─demo // 案例demo文件
│ └─src

└─lib // 最终打包放到npm的文件

项目依赖

一般情况下,我们写 React 项目,用 create-react-app 脚手架开发比较方便,但是如果要写一个插件的话,用三方脚手架就显得有点臃肿了,所以我们这里需要自己配置一个开发打包工具。我的package.json文件配置如下

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
{
"name": "react-ritch-markdown", // 项目名
"version": "1.0.0", // 版本
"description": "一个基于antd的同时支持markdown和ritch的编辑器", // 基本介绍
"main": "lib/index.js", // 入口文件
"scripts": {
"start": "webpack-dev-server --config config/webpack.dev.config.js", // 启动命令
"build": "webpack --config config/webpack.prod.config.js", // 打包命令
"pub": "npm run build && npm publish" // 发布命令
},
"keywords": [ // 搜索关键词
"react",
"markdown",
"ritch"
],
"author": "clairoll", // 作者名
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.6",
"babel-plugin-import": "^1.13.3",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.2.0",
"less": "^4.0.0",
"less-loader": "^7.2.0",
"mini-css-extract-plugin": "^0.8.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"style-loader": "^1.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.7",
"webpack-dev-server": "^3.8.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"@liradb2000/markdown-it-mermaid": "^0.4.1",
"antd": "^4.9.4",
"axios": "^0.21.1",
"braft-editor": "^2.3.9",
"braft-extensions": "^0.1.0",
"highlight.js": "^10.5.0",
"markdown-it": "^11.0.0",
"markdown-it-toc": "^1.1.0",
"react-markdown-editor-lite": "^1.2.2"
}
}

webpack配置

webpack.base.config.js

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
/*
* @Autor: Clairoll
* @Date: 2020-12-24 14:02:18
* @LastEditTime: 2020-12-25 15:11:08
* @Email: [email protected]
* @description:
*/
module.exports = {
module: {
rules: [
{
// 使用 babel-loader 来编译处理 js 和 jsx 文件
test: /\.(js|jsx)$/,
use: "babel-loader",
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
},
{
test: /\.less$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{ loader: "less-loader" }
]
},
{
test:/\.(woff|svg|eot|ttf)\??.*$/,
use:{loader:'url-loader?nmae=fonts/[name].[md5:hash:hex:7].[ext]'},
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: false,
},
},
],
},

]
},
resolve: {
extensions: ["*", ".ts", ".tsx", ".js", ".jsx", ".json"]
// extensions默认值js,json使用此选项,会覆盖默认数组,这就意味着 webpack 不再尝试使用默认扩展来解析模块。对于使用其扩展导入的模块,例如,import SomeFile from "./somefile.ext",要想正确的解析,一个包含“*”的字符串必须包含在数组中。
},
};

webpack.dev.config.js

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
/*
* @Autor: Clairoll
* @Date: 2020-12-24 14:02:45
* @LastEditTime: 2020-12-24 15:34:02
* @Email: [email protected]
* @description:
*/
const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.js'); // 引用公共配置

const devConfig = {
mode: 'development', // 开发模式
entry: path.join(__dirname, "../demo/src/app.js"), // 项目入口,处理资源文件的依赖关系
output: {
path: path.join(__dirname, "../demo/src/"),
filename: "bundle.js", // 使用webpack-dev-sevrer启动开发服务时,并不会实际在`src`目录下生成bundle.js,打包好的文件是在内存中的,但并不影响我们使用。
},
devServer: {
contentBase: path.join(__dirname, '../demo/src/'),
compress: true,
port: 3001, // 启动端口为 3001 的服务
open: true // 自动打开浏览器
},
};
module.exports = merge(devConfig, baseConfig); // 将baseConfig和devConfig合并为一个配置

webpack.prod.config.js

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
/*
* @Autor: Clairoll
* @Date: 2020-12-24 14:03:10
* @LastEditTime: 2020-12-24 16:04:34
* @Email: [email protected]
* @description:
*/
const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.js'); // 引用公共的配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用于将组件的css打包成单独的文件输出到`lib`目录中
// 引入 注意是使用解构的方式引入的 名字一定要正确
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const prodConfig = {
mode: 'production', // 开发模式
entry: path.join(__dirname, "../components/index.jsx"),
output: {
path: path.join(__dirname, "../lib/"),
filename: "index.js",
libraryTarget: 'umd', // 采用通用模块定义
libraryExport: 'default', // 兼容 ES6 的模块系统、CommonJS 和 AMD 模块规范
},
plugins: [
new MiniCssExtractPlugin({
filename: "main.min.css" // 提取后的css的文件名
}),
new CleanWebpackPlugin()
],
externals: { // 定义外部依赖,避免把react和react-dom打包进去
react: {
root: "React",
commonjs2: "react",
commonjs: "react",
amd: "react"
},
"react-dom": {
root: "ReactDOM",
commonjs2: "react-dom",
commonjs: "react-dom",
amd: "react-dom"
},
"antd": {
root: "antd",
commonjs2: "antd",
commonjs: "antd",
amd: "antd"
}
},
};

module.exports = merge(prodConfig, baseConfig); // 将baseConfig和prodConfig合并为一个配置

注意:

  1. entry的入口文件位置,由开发环境的src/index.js改成了组件的出口src/components/index.js,表示此处只负责输出组件。
  2. output的libraryTarget需要为commonjs2。
  3. 通过nodeExternals()将打包组件内的react等依赖给去除了,减小了包的体积,在引用该包时,只要其环境下有相关包,就可以正常使用。

调试验证

在上传到npm 之前我们需要验证自己的组件在打包过后是否可以使用。

第一步:打包 npm run build

第二步: 在组件项目的根目录下运行 npm link 将组件映射到本地库

第三步:进入案例demo文件夹下,运行 npm link 包名 然后修改引入方式

如果项目正常运行则表示成功,反之则存在问题

发布到npm

  1. 准备npm账号,npm官网地址:https://www.npmjs.com/

  2. 在组件的项目根目录下登录npm

1
2
npm login

按照提示输入username、password、email
登录后,可以通过npm whoami来查看登录用户信息

1
2
npm whoami

  1. 发布组件到npm上
1
2
npm run pub

上次更新 2021-01-28