从事前端开发的同学,相信对网络请求库 axios 不陌生;那么在面向文档编程的同时,是否有思考过该库是如何被设计的?又是如何实现的呢?作为一名已经使用该库多年的前端开发者,接下来会花一段时间来研究其实现原理,并将所学到的记录下来,作为学习笔记。
本文将从 axios 目录结构和构建流程谈起,目的是从整体上认识 axios,分析的库版本为:0.26.1。
目录结构
想要研究开源库的源码实现,需要对其目录结构有整体上的认识,并且了解一级目录及根目录下文件的作用,从而了解其模糊划分和功能。那么,axios 的目录结构如下:
.
├── dist // 构建打包产物
├── examples // 提供的例子
├── lib // axios 代码实现都在该目录下
├── node_modules // 安装依赖
├── sandbox // 官方提供的 serve
├── test // 测试用例
├── CHANGELOG.md // changelog
├── CODE_OF_CONDUCT.md // 贡献者契约行为准则
├── COLLABORATOR_GUIDE.md // 合作指南
├── CONTRIBUTING.md // 参与开源指南
├── COOKBOOK.md // 常用功能指南
├── ECOSYSTEM.md // axios 相关库和资源列表
├── Gruntfile.js // grunt 配置文件
├── LICENSE // 所使用的开源协议描述
├── README.md // 使用指南
├── SECURITY.md // 查看该文件可得知提交安全漏洞方式
├── UPGRADE_GUIDE.md // 升级指南
├── bower.json // bower 配置文件
├── index.d.ts // typescript 类型声明文件
├── index.js // 入口文件
├── karma.conf.js // 单元测试框架 karma 配置文件
├── package-lock.json // 依赖库锁定版本文件
├── package.json // package.json
├── tsconfig.json // typescript 配置文件
├── tslint.json // tslint 配置文件
└── webpack.config.js // webpack 配置文件
构建流程
在分析 axios 构建流程前,先简单地介绍构建工具 Grunt。Grunt 是一个自动化、借助任务运行器调度任务执行的 JavaScript 构建工具。它依赖于 Gruntfile.js 或者 Gruntfile.coffee 配置文件,用来配置或者定义任务,并加载插件;也就是说,当执行 Grunt 命令,Grunt 先加载 Gruntfile.js 配置文件,然后执行其定义的任务,最终输出构建产物。至于 Grunt 更详情的信息,可参考官网。
平时我们在使用 axios 时,要么通过 npm 方式安装,要么通过链接引入等方式,那么是否有思考过其产物是如何生成的?也就是说,通过输入源代码,经过构建,最终输出构建产物。那么,整个构建流程又是如何实现的吗?
先来看下 package.json 文件:
{
"scripts": {
"build": "cross-env NODE_ENV=production grunt build",
}
}
当在控制台执行命令 npm run build 时,通过 package.json 文件可知,最终会执行命令 grunt build,进而会执行 Gruntfile.js 文件定义的任务以及加载插件,那么来看下其是如何定义的?
// eslint-disable-next-line strict
module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
meta: {
banner: '/* <%= pkg.name %> v<%= pkg.version %> | (c) <%= grunt.template.today("yyyy") %> by Matt Zabriskie */\n'
},
clean: {
dist: 'dist/**'
},
package2bower: {
all: {
fields: [
'name',
'description',
'version',
'homepage',
'license',
'keywords'
]
}
},
package2env: {
all: {}
},
usebanner: {
all: {
options: {
banner: '<%= meta.banner %>',
linebreak: false
},
files: {
src: ['dist/*.js']
}
}
},
eslint: {
target: ['lib/**/*.js']
},
karma: {
options: {
configFile: 'karma.conf.js'
},
single: {
singleRun: true
},
continuous: {
singleRun: false
}
},
mochaTest: {
test: {
src: ['test/unit/**/*.js']
},
options: {
timeout: 30000
}
},
watch: {
build: {
files: ['lib/**/*.js'],
tasks: ['build']
},
test: {
files: ['lib/**/*.js', 'test/**/*.js', '!test/typescript/axios.js', '!test/typescript/out.js'],
tasks: ['test']
}
},
webpack: require('./webpack.config.js')
});
grunt.registerMultiTask('package2bower', 'Sync package.json to bower.json', function() {
var npm = grunt.file.readJSON('package.json');
var bower = grunt.file.readJSON('bower.json');
var fields = this.data.fields || [];
for (var i = 0, l = fields.length; i < l; i++) {
var field = fields[i];
bower[field] = npm[field];
}
grunt.file.write('bower.json', JSON.stringify(bower, null, 2));
});
grunt.registerMultiTask('package2env', 'Sync package.json to env.json', function() {
var npm = grunt.file.readJSON('package.json');
grunt.file.write('./lib/env/data.js', [
'module.exports = ',
JSON.stringify({
version: npm.version
}, null, 2),
';'].join(''));
});
grunt.registerTask('test', 'Run the jasmine and mocha tests', ['eslint', 'mochaTest', 'karma:single']);
grunt.registerTask('build', 'Run webpack and bundle the source', ['clean', 'webpack']);
grunt.registerTask('version', 'Sync version info for a release', ['usebanner', 'package2bower', 'package2env']);
};
首先,grunt 需要调用方法 initCofnig 做初始化操作,比如定义任务、加载插件等。那么,从配置文件可看出,定义的任务有 clean、package2bower、package2env、usebanner、eslint、karma、mochaTest、watch、webpack等。
接着,需要注册已经定义的任务,可调用方法 registerMultiTask 或者 registerTask 注册任务。
这里对任务 webpack 做进一步说明,在 Grunt 中借助插件 grunt-webpack 使用 webpack 来执行构建打包,那么我们来看下 webpack 配置文件 webpack.config.js 是如何定义的?
const TerserPlugin = require('terser-webpack-plugin');
var webpack = require('webpack');
var config = {};
function generateConfig(name) {
var compress = name.indexOf('min') > -1;
var config = {
entry: './index.js',
output: {
path: __dirname + '/dist/',
filename: name + '.js',
sourceMapFilename: name + '.map',
library: 'axios',
libraryTarget: 'umd',
globalObject: 'this'
},
node: {
process: false
},
devtool: 'source-map',
mode: compress ? 'production' : 'development'
};
return config;
}
['axios', 'axios.min'].forEach(function (key) {
config[key] = generateConfig(key);
});
module.exports = config;
可以看出配置文件逻辑挺简单的,由于最终要构建出 axios.js 和 axios.min.js,于是为它们各自生成配置存储在变量 config,将其导出并作为参数传给 Grunt,Grunt 根据这份配置,并且借助插件 grunt-webpack 执行构建打包,最终构建出产物。