NPM 组件你应该知道的事

时间:2022-07-26
本文章向大家介绍NPM 组件你应该知道的事,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

开发一个 npm 组件, 你是否了解需要对外导出什么格式的代码?如何让 npm 组件体积尽可能小?

整篇文章按照如下目录进行讲解:

  • 为何需要打包
  • 组件打包输出格式
  • 如何打包 esm 模式代码(感兴趣选读)
  • 减少组件打包体积的最佳实践

为何需要打包

首先,这里的打包概念解释一下, 只要有输出到新目录,就称为打包(免得大家对打包理解概念不一致)。

  • 一份代码,多种消费方式
  • 使用新特性语法,由于一般项目中,会默认不对 node_module 中的库进行编译以提高整个项目的编译速度,所以作为 npm 包,要转换成 es5 ,免得消费方吐槽……

打包格式

按照目前主流的模块系统来区分,可以先看一张图片宏观了解一下:

esm

如果是用 npm 组件来使用, 都推荐使用这种导出模式。

产生方式:
  • rollup 声明 target 为 esm 或者 babel 编译之后生成一个新的目录 (iceworks 的做法)
  • package.json 中声明 module,指向 esm
使用方式:
  • 浏览器通过 <script type="module" /> 引入
  • 作为 npm 使用
特性

由于是静态的,所以可以使用 tree-shaking

umd

使用方式
  • 浏览器通过 <script /> 引入
  • 浏览器通过 requirejs 或 seajs 引入 【目前这个已经很少使用了】
如何产生
  • rollup 或者 webpack 声明 target 为 umd
  • package.json 中声明 unpkg,指向对应文件

commonjs

使用方式
  • node 端, npm 方式
如何产生
  • rollup 或者 webpack 声明 target 为 commonjs
  • package.json 中声明 main,指向对应文件 。

package.json 中引用优先级如下:target 为 web 时, 依次查找 browser、module 和 main。其他 target , 依次找 module 和 main。因此如果声明了 module, 会优先读取 module 中的路径。

因此, 在导出的时候,同时设置好 main 和 module 字段,这样就可以二者兼具了,在node端,浏览器端都可以正常使用。

webpack 如何打包 esm 模式

这里不讲 rollup , 毕竟写一个 target 就可以解决了。

大家都知道,webpack 的 target 没有支持 esm 模式, 而 rollup 提供了, 为此很多人也在吐槽,为什么 webpack 不做……

我们这使用的是 iceworks , 源码地址[1],它默认支持导出 esm 模式, 那就一起看看它的源码是怎么做的。它是用 build-plugin-component 这个来实现的。【贴部分源码,感兴趣的可以看看】

  • 如果不是 jsx 或 tsx 文件, 则直接 copy 到目标目录,否则经过 bable 处理, 并将后缀改成 js

jsx.png

  • 使用 babel-plugin-import 处理第三方依赖的组件库,且兼容没有 es 模块的第三方组件

ba.png

  • 将 ts 解析生成 d.ts 文件

d.png

  • bable 7 (@babel/preset-env ),若为 esm 模块, 则关闭 module 选项

其实它实现的很简单, 如果是 es 模块, 只是用 babel 将对应的 es6 语法编译成 es5 语法(且不选择modlue), 然后 copy 到新目录 es 下, 对于里面使用到的第三方依赖组件, 用 babel-plugin-import 做一下兼容处理。

组件打包体积的最佳实践

首先,尽可能提供 esm 的格式, 因为它可以走 tree-shaking ,摇掉不必要的文件。【webpack 只要开启 production 模式,就默认有 tree-shaking 功能】

tree-shaking

tree.png

定义

如果被标记为无副作用的模块没有被直接导出使用,打包工具会跳过进行模块的副作用分析评估。由此安全地删除文件中未使用的部分。

在打包阶段,webpack无法准确判断某个文件是否有副作用,所以默认认为所有文件都是有副作用的。也就是说这里sideEffects默认是true。

副作用:一个函数会、或者可能会对函数外部变量产生影响的行为。

  • 模块作用域

将package.json 中sideEffects 设置为 false ,则表示改模块全部忽略副作用

  • 局部文件

package.json 中 sideEffects 数组写对应文件,比如常见的写上 css 文件, 如 antd 的配置

  • 函数级别

/*@__PURE__*/ 声明函数无副作用

只要我们基本保证这个组件包没有对外部对象产生影响,就能设置 sideEffects: false 了

举个栗子:设置了 sideEffects: false 和 未设置 sideEffects: false 的情况如下, 可以看到体积确实减少了不少

image.png

更深入的 sideEffect 可以看看这篇文章[2]

peerDependency

对于消费方可能也用到的组件,写到 peerDependency 中。看下面一张图就可以理解。这样可以减少重复打包。

举一个栗子:

"peerDependencies": {
    "react": ">=16.12.0",
    "react-dom": ">=16.12.0"
}

如上的配置,可以让组件库下的 node_modules 不安装 react,同时指定组件库使用方需安装的 react/reactDOM 的版本。

external

对于打包成 umd 的文件,由于它无法分析是否存在 peerDependencies, 所以如果使用方已有 react、 react-dom 等库,需要在webpack打包时,将 external 剔除掉对应依赖。

wepack5 模块联邦

external 还是静态的,如果项目支持, 还可以使用 wepack5 Module Federation 方式,由使用方动态决定是否下载依赖。

总结

1. 对外提供组件时,同时提供 esm ,commonjs, umd 这3种方式,并且在package 中对应的字段进行声明,以确保这个包可以兼容多环境。

2. 尽可能提供 esm 模式,并且如果这个组件没有影响外部变量时,设置 sideEffect 为 false, 让使用方可以最大的 tree-shaking 。对于公用的依赖包,将其写入 peerDependencies 中。

3. 若要提供 umd 模式, 在打包时, 将对应公用依赖写入 external 剔除对应依赖。

参考资料

[1]

源码地址: https://github.com/ice-lab/iceworks