由浅入深package.json,发布一个优秀的npm包

简介

整篇文章大概会涉及到以下内容:

  1. package.json 中一些常用重要字段用途讲解,比如 sideEffectspublishConfigexportsmodulemainversion等。
  2. 如何发布一个优秀的npm包。

从 package.json 出发

name

name 字段在 npm 注册表中唯一标识一个包。

如果你希望发布的包属于某个组织或团队,可以使用 scoped packages。Scoped packages 的名称以 @scope/ 开头,例如 @your-org/package-name

其中Scoped packages允许你创建一个命名空间,从而避免命名冲突。例如,如果你有一个名为 utils 的包,可以将其命名为 @your-org/utils,这样即使其他开发者也有一个名为 utils 的包,也不会产生冲突。

version

version字段表示该项目包的版本号。

主版本号

当新版本无法兼容基于前一版本的代码时,则提高主版本号;

次版本号

当新版本新增了功能和特性,但仍兼容前一版本的代码时,则提高次版本号;

修订号

当新版本仅仅修正了漏洞或者增强了效率,仍然兼容前一版本代码,则提高修订号;

^ 兼容某个大版本

^意味着下载的包可能会是更高的次版本号或者修订版本号。(也就是主版本不能变,次版本、修订版本可以随意变)。

js 复制代码
兼容某个大版本

如:^1.1.2 ,表示>=1.1.2 <2.0.0,可以是1.1.2,1.1.3,.....,1.1.n,1.2.n,.....,1.n.n 

~ 兼容某个次版本

~意味着有可能会有更高的修订版本号。(也就是主版本、次版本不能变,修订版本可以随意变)。

js 复制代码
兼容某个次版本 

如:~1.1.2,表示>=1.1.2 <1.2.0,可以是1.1.2,1.1.3,1.1.4,.....,1.1.n 

private

private 字段用于指示该包是否为私有包。如果将 private 字段设置为 true,则该包不会被发布到 npm 公共注册表。这对于确保某些包仅用于内部开发或特定项目非常有用。

publishConfig

publishConfig 字段在 package.json 文件中用于指定发布包时的一些配置选项。这个字段可以帮助你自定义发布行为,例如指定发布到哪个注册表、设置访问权限等。

例如(这样就会发布到指定服务器上(私服)):

js 复制代码
"publishConfig": { 
    "registry": "https://my.npmjs.org/", 
}

但是需要注意的时,即使设置了private为false,且设置了publishConfig的registry为私服地址。发包后,仍会发布到私服,而不会发布到npm上。

typings

typings字段用来指定TypeScript的入口文件

files

files配置是一个数组,用来描述当把npm包作为依赖包安装时需要发布的文件列表。当npm包发布时,files指定的文件会被推送到npm服务器中,如果指定的是文件夹,那么该文件夹下面所有的文件都会被提交。比如可以在该文件中这样写:

js 复制代码
// package.json
 "files": [
    "lib",
    "es"
  ],

如果有不想提交的文件,可以在项目根目录中新建一个.npmignore文件,并在其中说明不需要提交的文件,防止垃圾文件推送到npm上。这个文件的形式和.gitignore类似。写在这个文件中的文件即便被写在files属性里也会被排除在外。比如可以在该文件中这样写:

js 复制代码
// .npmignore
node_modules

.vscode

build

.DS_Store

mainmodule

main 字段用来指定加载的入口文件 ,在 browserNode 环境中都可以使用。如果将项目发布为npm包,那么当使用 require 导入npm包时,返回的就是main字段所列出的文件的module.exports 属性。如果不指定该字段,默认是项目根目录下的index.js。如果没找到,就会报错。

module字段可以定义 npm 包的 ESM 规范的入口文件,browser 环境和 node 环境均可使用。如果 npm 包导出的是 ESM 规范的包,使用 module 来定义入口文件。

Webpack在进行项目构建时,有一个target选项,默认为Web,即构建Web应用。如果需要编译一些同构项目,如node项目,则只需将webpack.config.jstarget选项设置为node进行构建即可。如果在Node环境中加载CommonJS模块,或者ESM,则只有main字段有效。

webpack 在 target: web 的情况下 mainFields 字段默认为 ['browser', 'module', 'main'] 。这样就意味着假如你使用 webpack 构建你的 Web 项目,无论你使用 ESM 还是 CJS 语法引入第三方包,本质上都是会优先查找 module 字段是否存在,之后才会去寻找 main 字段。

exports

exports的优先级是高于任何入口字段的(modulemainbrowser

路径封装

exports 字段可以对于包中导出的路径进行封装。

比如以下代码:

js 复制代码
{
  // 表示该包仅存在默认导出,默认导出为 ./index.js
  "exports": "./index.js"
}

// 上述的写法相当于
{
  "exports": {
    ".": "./index.js"
  }
}

定义了该字段后,该 Npm 包仅支持引入包自身,禁止引入其他子路径,相当于对于子路径的封装。

换句话说,仅仅只能引入 index.js。比如我们引入了未在 exports 中定义的模块,会报错。

如果想要该包引入其他模块,可以增加其他模块配置,如下:

js 复制代码
{
  "exports": {
    // . 表示引入包默认的导出文件路径, 比如 import lsx from 'lsx'
    // 这里的 . 即表示未携带任何路径的 lsx,相当于默认导出 ./index.js 文件的内容
    ".": "./index.js",
    // 同时额外定义一个可以被引入的子路径,下面的代码相当于导出./src/submodule.js文件的内容
    // 可以通过 import lsxSub from 'lsx/submodule.js' 进行引入 /src/submodule.js 的文件
    "./submodule.js": "./src/submodule.js"
  }
}

条件导出

通常我们编写的 NPM 包支持被 ESM 和 CJS 两种方式同时引入,需要根据不同的引入方式来寻找不同的入口文件。

js 复制代码
// package.json
{
  "exports": {
    // ESM 引入时的入口文件
    "import": "./index-module.js",
    // CJS 方式引入时寻找的路径
    "require": "./index-require.cjs"
  },
}

// 相当于
{
  "exports": {
    "import": {
        ".":  "./index-module.js"
    },
    "require": {
        ".": "./index-require.cjs"
    }
  },
}

可以看到 exports 关键字中定义的 key 为 importrequire 分别表示两种不同的模块引入方式使用该包时引入的不同文件路径。

关于条件判断的 Key 值,除了上述的 importrequire 分别代表的 ESM 引入和 CJS 引入的方式,NodeJS 同样提供了以下的条件匹配:

  • "import"- 当包通过 ESM 或加载时匹配 import(),或者通过 ECMAScript 模块加载器的任何顶级导入或解析操作。
  • "require"- 当包通过 CJS 加载时,匹配require()
  • "default"- 始终匹配的默认选项。可以是 CommonJS 或 ES 模块文件。这种情况应始终排在最后。 (他会匹配任意模块引入方式)

需要注意的是 exports 中的 key/value 顺序很重要,在发生条件匹配时较早的具有更高的优先级并优先于后面的配置。

当然上边我们提到的条件导出不仅仅适用于包的默认导出路径,同样也适用于子路径。比如:

js 复制代码
{
  "exports": {
    ".": "./index.js",
    "./feature.js": {
      "import": "./feature-node.js",
      "default": "./feature.js"
    }
  }
}

注意:

如果引入的 Npm 包中定义了 exports 关键字来定义对应的入口文件导出,package.json中的 modulemain 字段都是无效。

此时自然 webpack 中的 resolve.mainFields 字段也会失去它的效果,需要通过 resolve.conditionNames 字段来定义对应的环境。

也就是说,在引入的 Npm 包的 package.json 中如果存在 exports 关键字时,构建配置的 resolve.mainFields 是无效的。

如果未设置 resolve.conditionNames 字段,那么默认 webpack 会按照你当前的运行环境以及引入方式从而去 npm 包中的 exports 字段查找对应匹配的文件。

sideEffects

如果需要treeShaking,这是一个极为重要的配置。该配置用于告诉webpack,这个npm是不是有副作用的,这在减小业务包体积极为有用。比如开发组件库时,通常会把样式文件配置到这里,说明样式是存在副作用的时候。那么wenpack在打包时就不会剔除掉这些代码。

发布一个优秀的npm包

通常来讲,一个好的npm包中的package.json中的配置项,在上文中已经大都提到了。

接下来主要介绍一下发包的流程,以及一些注意事项。

  1. 配置包名、版本。
  2. 配置包的入口,即main、module。通常我们的包需要支持node和esmodule。
  3. 配置包的类型文件入口,即typings。
  4. 配置package.json中的files,发布指定文件夹到服务器。
  5. 配置npm私有服务器地址,即publishConfig。
  6. 配置打包脚本,通常会先编译文件再发包。

打包脚本,比如:

js 复制代码
    "compile:rollup": "rollup -c --bundleConfigAsCjs",
    "push": "yarn compile:rollup && yarn publish"

注意事项:

  1. 合理配置dependencies和devDependencies,因为发包后,dependencies会被跟随包一起构建。如果配置不合理,无意会增大包体积。
  2. 如果包发布到私服,使用方需要配置.npmrc文件,配置私服地址,从私服拉取npm包。
  3. 配置sideEffects,不会你的包不支持treeShaking吧。
  4. 合理配置npm包的入口,通常入口文件很简单,只是简单的导出。

比如,以下代码是笔者在开发一个vue组件库时的一个入口文件:

js 复制代码
export * from './types';

export * from './components/LevelReading/interface';

// 四线三格
export { default as EnglishLine } from './components/EnglishLine/index.vue';

// 分级阅读
export { default as LevelReading } from './components/LevelReading/index.vue';

// 单词卡
export { default as WordCard } from './components/WordCard/index.vue';

最后

文章的内容到这里就要画上句号了,感谢每一位可以看到结尾的小伙伴。

希望大家可以从文章中的内容有所收获,当然也欢迎每一位小伙伴在评论区留下自己的见解我们互相讨论。

如果我的文章帮助你解决了一些困惑或者学到了一些东西,也希望你能帮忙点个关注。

相关推荐
hackeroink44 分钟前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css