系列文章:
npm(五):组件发布npm包全流程 (使用rollup打包工具)
目录
一. npm包依赖配置
我们的项目可能依赖一个或多个外部依赖包,根据依赖包的不同用途,我们将他们配置在下面几个属性下:dependencies、devDependencies、peerDependencies、bundledDependencies、optionalDependencies
。
1. dependencies
dependencies
指定了项目运行所依赖的模块,开发环境和生产环境的依赖模块都可以配置到这里,例如
"dependencies": {
"lodash": "^4.17.13",
"moment": "^2.24.0",
}
2. devDependencies
有一些包有可能你只是在开发环境中用到,例如你用于检测代码规范的 eslint
,用于进行测试的 jest
,用户使用你的包时即使不安装这些依赖也可以正常运行,反而安装他们会耗费更多的时间和资源,所以你可以把这些依赖添加到 devDependencies
中,这些依赖照样会在你本地进行 npm install
时被安装和管理,但是不会被安装到生产环境:
"devDependencies": {
"jest": "^24.3.1",
"eslint": "^6.1.0",
}
3. peerDependencies
peerDependencies
用于指定你正在开发的模块所依赖的版本以及用户安装的依赖包版本的兼容性。
更多情况用在指定外部依赖
上面的说法可能有点太抽象,我们直接拿 ant-design 来举个例子,ant-design 的 package.json 中有如下配置:
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
当你正在开发一个项目,使用了 ant-design ,所以也肯定需要依赖 React。同时, ant-design 也是需要依赖 React 的,它要保持稳定运行所需要的 React 版本是16.0.0,而你开发时依赖的 React 版本是 15.x:
这时,ant-design 要使用 React,并将其引入:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
这时取到的是宿主环境也就是你的环境中的 React
版本,这就可能造成一些问题。
- 在 npm2 的时候,指定上面的 peerDependencies 将意味着强制宿主环境安装 react@>=16.0.0和react-dom@>=16.0.0 的版本。
- 在npm版本3到6中,peer dependencies不会自动安装,如果在树中发现peer dependency的无效版本,它会发出警告。
- 从npm v7开始,默认安装了peerDependencies。
因此我们总结下在插件使用 dependencies
声明依赖库的特点:
- 如果用户显式依赖了核心库,则可以忽略各插件的
peerDependency
声明;- 如果用户没有显式依赖核心库,则会提示用户按照
peerDependency
自行安装依赖
;- 当用户依赖的版本、各插件依赖的版本之间不相互兼容,会报错让用户自行修复;
warning " > vue-loader@15.8.3" has unmet peer dependency "css-loader@*".
warning "@nuxtjs/eslint-module > eslint-loader@3.0.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
warning "@nuxtjs/eslint-config > eslint-plugin-vue > vue-eslint-parser@5.0.0" has incorrect peer dependency "eslint@^5.0.0".
>
表示哪个项目中引入的插件
例如第一行就表名是根项目本身引用的vue-loader插件中要求根项目有安装css-loader
但跟项目没有安装css-loader, 所以就会有这个问题
has unmet peer dependency表示peerDependency声明的依赖没安装
has incorrect peer dependency 表示peerDependency声明的依赖安装了 但是版本没对上
4. optionalDependencies
某些场景下,依赖包可能不是强依赖的,这个依赖包的功能可有可无,当这个依赖包无法被获取到时,你希望 npm install 继续运行,而不会导致失败,你可以将这个依赖放到 optionalDependencies 中,注意 optionalDependencies 中的配置将会覆盖掉 dependencies 所以只需在一个地方进行配置。
当然,引用 optionalDependencies 中安装的依赖时,一定要做好异常处理,否则在模块获取不到时会导致报错。
5. bundledDependencies
和以上几个不同,bundledDependencies
的值是一个数组,数组里可以指定一些模块,这些模块将在这个包发布时被一起打包。
"bundledDependencies": ["package1" , "package2"]
二. npm2与npm3+ 安装依赖的区别
npm在安装依赖包时,会将依赖包下载到当前的node_modules目录中。每个包安装过后都会有自己的node_modules吗?这又涉及到不同版本的npm其对包依赖的目录组织结构有所不同。
1. npm2 依赖安装
npm2依赖安装的时候比较简单直接,直接按照包依赖的树形结构下载填充本地目录结构,也就是说每个包都会将该包的依赖组织到当前包所在的node_modules目录中。
npm2这样设计的原因可能是引用文章[2]的一句话:
因为 npm 设计的初衷就是考虑到了包依赖的版本错综复杂的关系,同一个包因为被依赖的关系原因会出现多个版本,简单地填充结构保证了无论是安装还是删除都会有统一的行为和结构。
这样,一个项目App 里模块 A 和 C 都依赖 B,无论被依赖的 B 是否是同一个版本,都会生成下面的目录结构:
很明显:
这种依赖的组织结构,虽然简单的实现多版本兼容,但是可能造成目录结构嵌套比较深,并且很可能造成相同模块的大量冗余问题。
2. npm3+依赖安装
npm3则对依赖安装进行了改造,采用”扁平结构“的思路来组织依赖包的目录结构。具体的就是npm install
时:
按照 package.json 里依赖的顺序依次解析,遇到新的包就把它放在第一级目录,后面如果遇到一级目录已经存在的包,会先判断版本,如果版本一样则忽略,否则会按照 npm2 的方式依次挂在依赖包目录下。
以上面的例子看一下对比结果:
这样,npm3+采用这种扁平结构部分的解决了npm2的痛点。
为什么说是部分解决呢,npm3+并没有完美解决npm2中的问题,在某些情况下甚至会退化到npm2的行为。
例如项目App里依赖模块A、C、D、E, 其中A、C、D依赖模块B v2.0, E依赖模块B v1.0,生成的npm3结构如下
可以看到B、C、D模块下包含了各自依赖的B v2.0,存在代码冗余的情况。
那么是否可以解决这代码冗余问题呢,在E模块依赖的模块B升级到V2.0前提下,我们可以通过npm dedupe
把所有二级的依赖模块B v2.0重定向到一级模块B下,如下图所示:
三. node_modules路径查找
上面说到了npm2与npm3依赖包组织结构的不同;那么如何找到对应的依赖包呢,例如项目访问webapck依赖包:
const webapck = require('webpack')
那么nodejs是怎么找到webpack模块的呢,这就是涉及到依赖包的路径查找问题。具体如下:
如果传递给
require()
的参数不是nodejs的核心模块,也不是以/
、../
或./
开头,
那么nodejs会尝试从当前模块所在目录开始,尝试在它的 node_modules 文件夹里加载相应模块,根据模块的package.json来加载对应的js文件;如果没有找到,那么就再向上一级目录移动,直到文件系统的根目录为止。
例如,假设在/home/wonyun/projects/foo.js
文件里调用了 require('bar.js')
,那么 nodejs 查找其位置的顺序依次为:
/home/wonyun/projects/node_modules/bar.js
/home/wonyun/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
若果追踪到文件系统的根目录也没有找到对应的依赖,那么nodejs就会找不到对应模块的报错。
声明一下:以上图片使用文献[2]的图片加以说明。
参考文献
1、【npm】详解npm的模块安装机制
2、npm2 npm3 yarn 的故事
3、What's the difference between dependencies, devDependencies and peerDependencies in npm package.json file?