最近对公司前端关键依赖进行了一次升级,了解了 npm 更多实用的命令,以及新增的 eslint 规则缘由等等。
npm 基础
平时常用的 npm CLI 命令,就那么几个,一些非常实用的工具,反而被自己忽略了。今天查阅了一些 npm 提供的所有命名。
下面是一些对于工作中可能能提供帮助的命令,其他一些包开发者才会用到的就不列举在这里了
npm 实用命令
# config
npm config edit
npm config list
# dedupe 搜索本地依赖树,试图通过将依赖项向上移动来简化整体结构,从而可以更有效的被多个包共享
npm dedupe
# 安全审查与修复:检查是否有已知的风险,如果有任何风险被检测到,将计算影响和适当的补救措施。如果使用了 fix 参数,则
npm audit
npm audit fix
# 树结构展示本地安装的包的版本,在解决版本冲突时很好用
npm ls <pkg>
# 检测过期包
npm outdated
# 将某个包版本标记为过期,包管理这才会用到,列举的目的就是别和 outdated 冲突
npm deprecate
# 直接在浏览器中访问包
npm repo <pkg>
# 查看 node_modules 地址
npm root -g
# 移除没有直接联系的包,在 node_modules 中存在,但没有在 package 依赖性列举出的
npm prune
# 方便内部包开发迭代,而不需要频繁地编译
npm link
# 使用本地或远程包运行一个运行
npx
npm dedupe
有时候你通过 npm ls
会看到如下的依赖树
a
+-- b <-- depends on c@1.0.x
+-- c@1.0.3
`-- d <-- depends on c@1.x
`-- c@1.9.9
在安装过程中,对于 b 而言的依赖 c@1.0.3 被放置在树的顶层,虽然 c@1.0.3 于此同时已经满足 d 包的要求,更新 c@1.9.9 依赖会被使用,因此 npm 默认会选择更新的,即使这样会导致重复。
这时候运行 npm dedupe
命令,将会使 npm 意识到重复以及重新评估,删除嵌套的 c 模块,因为 root 级别的 c 已经可以满足了。
如果你希望安装时,不是喜欢更新,而是删除重复,使用 npm install --perfer-dedupe
或者设置如下
npm config set prefer-dedupe true
npm link
关于 npm link 具体工作原理分为两步
- 创建全局链接:在一个 package 目录中执行
npm link
将会在全局目录{prefix}/lib/node_modules/<package>
创建一个符号链接,链接到刚执行 npm link 的软件包,同时会链接{prefix}/bin/{name}
下的所有 bin。prefix 参数使用全局的 prefix 值(通过npm prefix -g
可看到) - 将全局目标连接到项目中:在项目中,执行 npm link package-name 将会创建一个从全局安装下的 package 到当前目录的 node_modules 下的链接
npm 使用 demo
cd ~/projects/node-redis # go into the package directory
npm link # creates global link
cd ~/projects/node-bloggy # go into some other package directory.
npm link redis # link-install the package
使用 npm link 的方式,所有的小调整都不需要发包就可以验证正确性,尤其是对于不能确定正确的测试性改动,更加具有优越性
npm 包管理
在执行 npm install 的时候,按照 package.json 里依赖的顺序依次解析
node_modules 路径查找
- 从当前模块所在目录开始,尝试在它的 node_modules 文件夹里加载模块
- 如果没有找到,那就再向上一级目录移动,知道文件系统的根目录位置
- 若果追踪到文件系统的根目录也没有找到对应的依赖,那么 nodejs 就会找不到对应模块的报错
npm2 依赖安装
- 比较简单粗暴,直接按照包依赖的树形结构下载填充本地目录结构,也就是说每个包都会将该包的依赖组织到当前包所在的node_modules目录中。
- 这种依赖的组织结构,虽然简单的实现多版本兼容,但是可能造成目录结构嵌套比较深,并且很可能造成相同模块的大量冗余问题。
npm3+ 依赖安装
- 采用扁平结构的思路来组织依赖包的目录结构,按照 package.json 里依赖的顺序依次解析,遇到新的包就把它放在第一级目录,后面如果遇到一级目录已经存在的包,会先判断版本,如果版本兼容,则会忽略,否则按照 npm2 的方式依次挂在依赖包目录下
- 部分解决了 npm2 的痛点,在某些情况下甚至会退化到 npm2 的行为,所以就有了
npm dedupe
命令进行补充
在 npm3 的模式下,即使我们在自身 package.json 中采用锁定版本的方式,但间接依赖如果并没有锁定版本,还是会导致不同人安装下来的包会不一样。
为解决 npm3 的问题,在 npm5 版本后,npm install 会自动生成一个 package-lock.json 文件。当包中有 package-lock.json 文件时,npm install 执行时,如果 package.json 和 package-lock.json 中的版本兼容,会根据 package-lock.json 中的版本下载;如果不兼容,将会根据 package.json 的版本,更新 package-lock.json 中的版本,已保证 package-lock.json 中的版本兼容 package.json。
依赖的区别
- dependencies
- 无论是开发环境还是生产环境都必须使用的依赖
- devDependencies
- 在开发环境使用的依赖
- 和 dependencies 最大的区别在于 npm install --production 时就只会安装 dependencies 中的依赖,如果是 node 服务项目,就可以采用这样的方式用于服务运行时安装和打包,减少包大小。
- optionalDependencies
- 可选择的依赖包
- 某些依赖即使下载失败或者没有找到时,项目依然可以正常运行
- optionalDependencies 会覆盖 dependencies 中的同名依赖包,所以不要把一个包同时写进两个对象中
- peerDependencies
- 同等依赖
- 指定你当前的包兼容的宿主必须要安装的包的版本。npm3 中,不会再强制安装 peerDependencies 中所指定的包,而是通过警告的方式来提示我们,此时就需要手动在 package.json 文件中手动添加依赖
- bundledDependencies
- 捆绑依赖包,使用较少
- 比较特别:不是一个键值对的对象,而是一个数组,数组里是包名的字符串
eslint 报错
更新后,eslint 新增了很多错误,这里大致做一下总结。
错误信息提示:Shorthand method signature is forbidden. Use a function property instead.
,具体规则为 method-signature-style
,提供了 property
和 method
两个选项,默认 property
,没啥特殊目的,就是为了统一风格。
// method shorthand syntax
interface T1 {
func(arg: string): number;
}
// regular property with function type
interface T2 {
func: (arg: string) => number;
}
错误信息提示:All imports in the declaration are only used as types. Use import type.
,在 TypeScript 3.8 中提供了 import type 和 export type 语法,专门用于导入导出类型。目的为了协助 TypeScript 编译器清除掉运行时不需要的类型信息,
错误信息提示:A record is preferred over an index signature.
,这条规则其实很有用,对于对于初级 TypeScript 开发者而言,当他想声明一个键值对象时,往往可能会如下做
interface {
[key: string]: string
}
这条规则直接限制为必须使用 Record,在我看来确实优雅很多,使用如下
type a = Record<string, string>
下面三条都属于 ban-types
,主要是考虑到如下观点
- 有些内置类型有别名
- 有些类型是危险
默认配置了如下最佳实践
- 对于原始类型,你应该使用小写,而不是大写,比如应该使用
number
,而不是Number
- 避免使用
Function
类型,因为它提供的安全性是有限的- 对于参数类型没有显示
- 它可以接受类声明,但这会导致错误,因为没有使用
new
的方式进行调用
- 避免使用
Object
和{}
,因为它意味着任何非空值(除 null、undefined 之外)- 会对开发者造成困惑,认为是任何对象类型
{}
不是意味着任何空对象,而是任何非空值,它的表现类似于空的 interface,因为同时也有 no-empty-interface 的规则- TS 没有表示空对象的类型,你可以使用一个类似的
Record<string, never>
- 当你想表示任何对象时,可以使用
Record<string, unknown>
,当你想表示任何值时,可以使用unknown
- 避免使用 object 类型,因为它很难断言键是否存在
- 推荐使用
Record<string, unknown>
- 推荐使用
以下是相关在项目升级中碰到的问题
- 错误信息提示:
Don't use Function as a type. The Function type accepts any function-like value.
,至于这一条错误信息,没啥好说的,站好挨就打好了,当初写代码图方便,就直接 Function 了事了,但这种方式提供的类型限制是不够的。 - 错误信息提示:
Don't use Object as a type. The Object type actually means "any non-nullish value", so it is marginally better than unknown.
- 错误信息提示:
Don't use object as a type. The object type is currently hard to use ([see this issue](https://github.com/microsoft/TypeScript/issues/21732)).Consider using Record<string, unknown> instead, as it allows you to more easily inspect and use the keys.
报错信息:Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
,解决方案就是把报错的文件加入到 tsconfig.include 下,但觉得有点其他,因为其他平级的类似文件却没有报错!
关于 eslint 规则,团队成员直接将 rule 禁用掉的方式来修复掉错误,真的让人很无语。
antd
本次 antd 升级还算稳定。在项目中简单测试发现有个小功能表现异常,排除了不是新版 React 导致的问题后,锁定了是 antd 新版引入的 bug。
问题表现为:antd 新版有个小问题,modal 搭配 tooltip 使用,tooltip 存在不消失的情形。暂时给 antd 提交了一个 issue。
antd 类型导入的问题:有时候我们需要 antd 中某个组件的某些类型声明,常见的场景比如:某个事件回调的函数类型声明
- 当然你可以直接将 rc-xxx 组件声明再 package.json 中,然后从里面导入,从而为所欲为。当很明显这种方式非常不优雅,而且向后兼容性巨差
- 正确的方式是:直接从 antd 导出某个组件 Props 类型,然后通过类型方式的方式,获得所有的类型声明
关于 typescript 类型声明例子
import type { MenuProps } from 'antd';
// 如果你要定义 onClick 事件的回调函数
const handleClick: MenuProps['click'] = useCallback(() => {}})
three
本次升级从 r114 -> r126 版本,可能需要关心的 feature 或 fix 有
- 移除 Scene.dispose() 方法
- 支持 Texture.dispose() 方法
- 添加 Object3D.clear() 方法
- 添加 InstancedMesh.dispose() 方法
- 废弃 Geometry 类,全面使用 BufferGeometry
- 把 TypeScript 交由 @type 管理
three 有详细的升级指导
map
这里主要涉及到 maptalks 的升级,考虑到一些实际情况,升级暂时待定,但记录破坏性的更新内容。
maptalks
新版本 0.49.1 相比目前使用版本主要变化
- 为了更好的兼容三维渲染,世界坐标系改为与 opengl 坐标系一致
扩展:坐标转换 API
- distanceToVector3
- coordinateToVector3
之前同事了解到 MapBox 出中国区服务了,但貌似只是一个 MapBox 官网的壳而已。
mapbox
mapbox 中国
- 中国地图服务
- 全球化的地图和数据
- 更改地图的每一个像素,更可以叠加上你的数据图层
- 教程
小小指南
总结下几个问题
- 以下依赖可以删除:当初是因为 react-scripts 存在 bug 而导致的,更新后该问题得到了解决
- @babel/core
- eslint-plugin-import
- 你可能会忘记更新但又比较重要的依赖
- prettier:最新版(2.2.1)的默认项有差别,最典型的就是参数括号问题
- stylelint:最新版(13.12.0)的也有所差别,比如新版会格式化掉多余的空行
- 其他工作
- antd/lib => antd/es
- 分清 devDependency 与 dependency
- 关于 antd 某些特殊类型的导入
其他
热重载好像变得高级了,而且我没有注册 hot。先不研究了,哈哈哈……