Fork me on GitHub

使用rollup和ts编写一个去重请求的axios

  近期看了些社区关于axios一些增强封装的文章,就想着也动手玩玩(先随便实现个去重请求 feature。打包工具准备选择rollup,语言则直接使用ts,选型单纯是扩展技术体系,在实(踩)践(坑)中提升熟练度。

TypeScript

优缺点

  社区上对ts的讨论其实是非常多的,结合个人分析整理的优缺点如下:

  优点:

  • coding过程中,IDE 可以提供成员属性、类型等智能提示
  • 静态校验(JS 本身只有运行时才会抛出异常),主要走 AST 词法分析那一套,在我们编写逻辑代码时,进行一些引用(比如常见的变量拼写错误低级问题)、句法问题上的分析,错误会标红并予以可能的解决方案说明
  • interfacetype这些对成员类型的定义使后续维护者能够更容易分析整个项目的流程

  缺点:

  • 接入成本比较大(学习成本、工作量成本、小组工作人员意愿成本等)
  • 打包过程中额外的编译时间
  • 可能一些第三方库没有编写typings.d.ts之类的类型声明,导致引入后ts抛错

安装&初始化项目配置

1
2
3
4
5
6
// 全局安装ts
npm i -g typescript
// 安装后可以查看下版本,确认下是否安装成功
tsc -v
// 在当前目录下生成 tsconfig.json文件
tsc --init

  建议通过命令直接生成ts的配置文件,因为其中提供了全量的配置信息并提供了注释声明,我们仅需要在我们的配置行打开注释并设置我们的内容即可:

Rollup

  Rollup个人最早听说的时候是它有个tree-shaking的功能,就是可以移除代码里的一些dead code,从而减小你打包出来项目的体积,不过当时我一直使用的是webpack进行项目构建,且后续webpack新版本中也新增了类似的功能,就没有使用过该打包工具了。

  根据一些社区的说法,Rollup似乎在构建应用和构建库之间更适合后者,基本给出的理由都是从代码体积、打包后的可读性上来说。比如webpack会写入依赖构建图谱,里面会有类似__webpack_require__的工具方法编织的IIFE等。

  当然,在我个人看来理由还是比较牵强…RollupWebpack都是工程化上的一种选型,技术细节决定了它们最后打包出来的产物不同,并且没有在数据上达到一个量级的差距。所以我认为从开发者做事的角度来说,相关社区配套资源的物料是否丰富才是更大的影响我们选型的因素。

安装&初始化项目配置

1
2
3
4
// 全局安装rollup
npm i -g rollup
// 创建rollup.config.js配置文件
touch rollup.config.js

  下面看下我的具体配置,关键点是其中的plugins,如果缺少了其中的任意一个都会抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import typescript from '@rollup/plugin-typescript'

export default {
input: 'lib/main.ts',
output: {
file: 'dist/bundle.js',
format: 'es',
sourcemap: true,
},
plugins: [typescript(), resolve(), json(), commonjs()],
}

  input没什么好多说的,写个入口文件就完事,跟我们在webpack里的entry配置差不多;在output中要注意的是如果我们导出的是umd风格的包,就须要在output中配置nameglobals以及第一层配置添加external项。

  我所说的output的细节可以看下react-redux的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import nodeResolve from '@rollup/plugin-node-resolve'
import babel from '@rollup/plugin-babel'
import replace from '@rollup/plugin-replace'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
import pkg from './package.json'

const env = process.env.NODE_ENV

const config = {
input: 'src/index.js',
external: Object.keys(pkg.peerDependencies || {}).concat('react-dom'),
output: {
format: 'umd',
name: 'ReactRedux',
globals: {
react: 'React',
redux: 'Redux',
'react-dom': 'ReactDOM',
},
},
plugins: [
nodeResolve(),
babel({
exclude: '**/node_modules/**',
babelHelpers: 'runtime',
}),
replace({
'process.env.NODE_ENV': JSON.stringify(env),
}),
commonjs(),
],
}

if (env === 'production') {
config.plugins.push(
terser({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false,
},
})
)
}

export default config

  plugins的配置我认为是和webpack差异最大的地方,在webpack中的plugins是用来在tapable派发的不同的 hooks 中进行特定时机额外操作的,诸如HtmlWebpackPlugin入口模版配置,ProgressBar显示打包进度等。

  而在Rollup中的plugins更像是webpack里的loader,它们的功能都是进行模块解读,没有这块辅助,我们的打包流就无法正常工作。

  下面说说我在配置中遇到的plugins问题。

Rollup 的 plugins 配置

  最全的配置见官网

  • @rollup/plugin-node-resolve: 用于协助Rollup找到外部依赖的模块如axioslodash等,不添加就会报Unresolved dependencies问题,即无法将第三方依赖打入我们的代码中。
  • @rollup/plugin-commonjs: 使用这个插件主要是因为社区里实际上很多库还是打包成cjs格式的,当我们想通过 es 的import方式导入就需要通过该插件进行转化才行,如qs等。
  • @rollup/plugin-json: 提供了json格式文件的处理能力,在引入axios时,如其中的package.json文件无法读取,命令行会提示安装该插件辅助。
  • ‘@rollup/plugin-typescript: 由于我们使用了ts,可以直接通过该插件替代tsc的命令动作。

Axios 封装

  由于本文仅实现一个重复请求取消的简单功能,所以大概讲下思路和记录遇到的一些问题。

  先说说思路,之前实际上写过一篇axio 源码阅读的文章,里面有一个非常关键的细节就是interceptors的实现。这个方法可以让我们在请求过程中进行中间件的定制,也就意味着我们可以在中间判断请求是否重复,从而进行取消。

  那么取消重复请求问题就可以拆解成两步:是否重复如何取消

  判断请求是否重复,我们可以通过将请求url、请求方法method及请求参数data(post)或者params(get)进行字符串序列化(借助第三方库qs)并生成请求Map来进行后续过滤。

  取消请求的本质是xhrabort方法,当然设置timeout的情况下超时请求也会被canceled

  编写完代码逻辑再结合前阵子文章一套完整的代码规范需要什么后,我们有如下目录结构:

  打包:rollup -c生成bundle.js

  压缩:npm pack生成.tgz压缩包,可以直接发布npm也可以本地安装调试。

遇到的问题

  • axios的设计上,它关键的Cancel方法是直接挂到它默认导出的实例上去的(通过axios.Cancel = require('./cancel/Cancel');),所以当我们调用axios.create(config)方法再创建一个新实例时,就会丢失那些原始默认导出实例上的绑定方法,那些方法需要我们手动进行拷贝。
  • Could not find a declaration file for module:出现在安装我的方法库后,查阅资料后发现是我的ts缺少对文件的描述说明文件typings.d.ts。设置方案如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
// package.json中
// 配置typings项并指向我们代码中的说明文件
"typings": "lib/index.d.ts",

// index.d.ts
// 简单声明一下,但是要注意tsconfig.json中的配置
declare module 'with-wrapped-axios'

// tsconfig.json
"declaration": true, /* Generates corresponding '.d.ts' file. */
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
"outDir": "./dist", /* Redirect output structure to the directory. */

效果

  启一个umidemo,安装本地编写的依赖包,在mock目录下设置一个1.5s超时的mock接口,点击模拟试验一下:

  可以发现连续点击五次按钮,仅最后一次正常到了超时才响应timeout并且canceled,前4个重复请求都被监测到并且主动abort,符合预期。