简介
整篇文章大概会涉及到以下内容:
- package.json 中一些常用重要字段用途讲解,比如
sideEffects、publishConfig、exports、module、main、version等。 - 如何发布一个优秀的npm包。
从 package.json 出发
name
name 字段在 npm 注册表中唯一标识一个包。
如果你希望发布的包属于某个组织或团队,可以使用 scoped packages。Scoped packages 的名称以 @scope/ 开头,例如 @your-org/package-name。
其中Scoped packages允许你创建一个命名空间,从而避免命名冲突。例如,如果你有一个名为 utils 的包,可以将其命名为 @your-org/utils,这样即使其他开发者也有一个名为 utils 的包,也不会产生冲突。
version
version字段表示该项目包的版本号。
主版本号
当新版本无法兼容基于前一版本的代码时,则提高主版本号;
次版本号
当新版本新增了功能和特性,但仍兼容前一版本的代码时,则提高次版本号;
修订号
当新版本仅仅修正了漏洞或者增强了效率,仍然兼容前一版本代码,则提高修订号;
^ 兼容某个大版本
^意味着下载的包可能会是更高的次版本号或者修订版本号。(也就是主版本不能变,次版本、修订版本可以随意变)。
兼容某个大版本如:^1.1.2 ,表示>=1.1.2 <2.0.0,可以是1.1.2,1.1.3,.....,1.1.n,1.2.n,.....,1.n.n
~ 兼容某个次版本
而~意味着有可能会有更高的修订版本号。(也就是主版本、次版本不能变,修订版本可以随意变)。
兼容某个次版本 如:~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 文件中用于指定发布包时的一些配置选项。这个字段可以帮助你自定义发布行为,例如指定发布到哪个注册表、设置访问权限等。
例如(这样就会发布到指定服务器上(私服)):
"publishConfig": { "registry": "https://my.npmjs.org/",
}
但是需要注意的时,即使设置了private为false,且设置了publishConfig的registry为私服地址。发包后,仍会发布到私服,而不会发布到npm上。
typings
typings字段用来指定TypeScript的入口文件
files
files配置是一个数组,用来描述当把npm包作为依赖包安装时需要发布的文件列表。当npm包发布时,files指定的文件会被推送到npm服务器中,如果指定的是文件夹,那么该文件夹下面所有的文件都会被提交。比如可以在该文件中这样写:
// package.json"files": ["lib","es"],
如果有不想提交的文件,可以在项目根目录中新建一个.npmignore文件,并在其中说明不需要提交的文件,防止垃圾文件推送到npm上。这个文件的形式和.gitignore类似。写在这个文件中的文件即便被写在files属性里也会被排除在外。比如可以在该文件中这样写:
// .npmignore
node_modules.vscodebuild.DS_Store
main、module
main 字段用来指定加载的入口文件,在 browser 和 Node 环境中都可以使用。如果将项目发布为npm包,那么当使用 require 导入npm包时,返回的就是main字段所列出的文件的module.exports 属性。如果不指定该字段,默认是项目根目录下的index.js。如果没找到,就会报错。
module字段可以定义 npm 包的 ESM 规范的入口文件,browser 环境和 node 环境均可使用。如果 npm 包导出的是 ESM 规范的包,使用 module 来定义入口文件。
Webpack在进行项目构建时,有一个target选项,默认为Web,即构建Web应用。如果需要编译一些同构项目,如node项目,则只需将webpack.config.js的target选项设置为node进行构建即可。如果在Node环境中加载CommonJS模块,或者ESM,则只有main字段有效。
webpack 在 target: web 的情况下 mainFields 字段默认为 ['browser', 'module', 'main'] 。这样就意味着假如你使用 webpack 构建你的 Web 项目,无论你使用 ESM 还是 CJS 语法引入第三方包,本质上都是会优先查找 module 字段是否存在,之后才会去寻找 main 字段。
exports
exports的优先级是高于任何入口字段的(module、main、browser) 。
路径封装
exports 字段可以对于包中导出的路径进行封装。
比如以下代码:
{// 表示该包仅存在默认导出,默认导出为 ./index.js"exports": "./index.js"
}// 上述的写法相当于
{"exports": {".": "./index.js"}
}
定义了该字段后,该 Npm 包仅支持引入包自身,禁止引入其他子路径,相当于对于子路径的封装。
换句话说,仅仅只能引入 index.js。比如我们引入了未在 exports 中定义的模块,会报错。
如果想要该包引入其他模块,可以增加其他模块配置,如下:
{"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 两种方式同时引入,需要根据不同的引入方式来寻找不同的入口文件。
// package.json
{"exports": {// ESM 引入时的入口文件"import": "./index-module.js",// CJS 方式引入时寻找的路径"require": "./index-require.cjs"},
}// 相当于
{"exports": {"import": {".": "./index-module.js"},"require": {".": "./index-require.cjs"}},
}
可以看到 exports 关键字中定义的 key 为 import 和 require 分别表示两种不同的模块引入方式使用该包时引入的不同文件路径。
关于条件判断的 Key 值,除了上述的 import 和 require 分别代表的 ESM 引入和 CJS 引入的方式,NodeJS 同样提供了以下的条件匹配:
"import"- 当包通过ESM或加载时匹配import(),或者通过 ECMAScript 模块加载器的任何顶级导入或解析操作。"require"- 当包通过CJS加载时,匹配require()。"default"- 始终匹配的默认选项。可以是 CommonJS 或 ES 模块文件。这种情况应始终排在最后。 (他会匹配任意模块引入方式)
需要注意的是 exports 中的 key/value 顺序很重要,在发生条件匹配时较早的具有更高的优先级并优先于后面的配置。
当然上边我们提到的条件导出不仅仅适用于包的默认导出路径,同样也适用于子路径。比如:
{"exports": {".": "./index.js","./feature.js": {"import": "./feature-node.js","default": "./feature.js"}}
}
注意:
如果引入的 Npm 包中定义了
exports关键字来定义对应的入口文件导出,package.json中的module、main字段都是无效。此时自然
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中的配置项,在上文中已经大都提到了。
接下来主要介绍一下发包的流程,以及一些注意事项。
- 配置包名、版本。
- 配置包的入口,即main、module。通常我们的包需要支持node和esmodule。
- 配置包的类型文件入口,即typings。
- 配置package.json中的files,发布指定文件夹到服务器。
- 配置npm私有服务器地址,即publishConfig。
- 配置打包脚本,通常会先编译文件再发包。
打包脚本,比如:
"compile:rollup": "rollup -c --bundleConfigAsCjs","push": "yarn compile:rollup && yarn publish"
注意事项:
- 合理配置dependencies和devDependencies,因为发包后,dependencies会被跟随包一起构建。如果配置不合理,无意会增大包体积。
- 如果包发布到私服,使用方需要配置.npmrc文件,配置私服地址,从私服拉取npm包。
- 配置sideEffects,不会你的包不支持treeShaking吧。
- 合理配置npm包的入口,通常入口文件很简单,只是简单的导出。
比如,以下代码是笔者在开发一个vue组件库时的一个入口文件:
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';
最后
文章的内容到这里就要画上句号了,感谢每一位可以看到结尾的小伙伴。
希望大家可以从文章中的内容有所收获,当然也欢迎每一位小伙伴在评论区留下自己的见解我们互相讨论。
如果我的文章帮助你解决了一些困惑或者学到了一些东西,也希望你能帮忙点个关注。
