# Typescript 仿 axios 笔记
# 初始化
git clone https://github.com/alexjoverm/typescript-library-starter.git ts-axios
cd ts-axios
yarn install
目录文件介绍
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── code-of-conduct.md
├── node_modules
├── package-lock.json
├── package.json
├── rollup.config.ts // rollup 配置文件
├── src // 源码目录
├── test // 测试目录
├── tools // 发布到 GitHup pages 以及 发布到 npm 的一些配置脚本工具
├── tsconfig.json // TypeScript 编译配置文件
└── tslint.json // TypeScript lint 文件
根目录下创建 .prettierrc.json
,根据个人喜好配置
{
"tabWidth": 2,
"useTabs": false,
"endOfLine": "auto",
"singleQuote": true,
"semi": false,
"trailingComma": "none",
"bracketSpacing": true,
"printWidth": 100
}
# 编写简单 Demo
src/index
export interface AxiosRequestConfig {
url: string;
method?: Method;
data?: any;
params?: any;
}
// 定义method取值范围
export type Method =
| "get"
| "GET"
| "delete"
| "Delete"
| "head"
| "HEAD"
| "options"
| "OPTIONS"
| "post"
| "POST"
| "put"
| "PUT"
| "patch"
| "PATCH";
export function axios(config: AxiosRequestConfig) {
const { data, method = "get", url } = config;
const XHR = new XMLHttpRequest();
XHR.open(method, url, true);
XHR.send(data);
}
# 需求实现
我们要做的 axios
的需求如下:
- 在浏览器端使用 XMLHttpRequest 对象通讯
- 支持 Promise API
- 支持请求和响应的拦截器
- 支持请求数据和响应数据的转换
- 支持请求的取消
- JSON 数据的自动转换
- 客户端防止 XSRF
下面先开始从基础功能部分优化,基础功能部分包含以下:
- 处理请求
url
参数 - 处理请求
body
数据 - 处理请求
header
参数 - 处理响应数据
- 处理响应
header
- 处理响应
data
# 处理请求 url 参数
需求分析
axios({
method: "get",
url: "/base/get",
params: {
a: 1,
b: 2
}
});
我们希望最终请求的 url
是 /base/get?a=1&b=2
,这样服务端就可以通过请求的 url 解析到我们传来的参数数据了。实际上就是把 params
对象的 key 和 value 拼接到 url
上。
再来看几个更复杂的例子。
参数值为数组
axios({
method: "get",
url: "/base/get",
params: {
foo: ["bar", "baz"]
}
});
最终请求的 url
是 /base/get?foo[]=bar&foo[]=baz'
。
参数值为对象
axios({
method: "get",
url: "/base/get",
params: {
foo: {
bar: "baz"
}
}
});
最终请求的 url
是 /base/get?foo=%7B%22bar%22:%22baz%22%7D
,foo
后面拼接的是 {"bar":"baz"}
encode 后的结果。
参数值为 Date 类型
const date = new Date();
axios({
method: "get",
url: "/base/get",
params: {
date
}
});
最终请求的 url
是 /base/get?date=2019-04-01T05:55:39.030Z
,date
后面拼接的是 date.toISOString()
的结果。
特殊字符支持
对于字符 @
、:
、$
、,
、``、[
、]
,我们是允许出现在 url
中的,不希望被 encode。
axios({
method: "get",
url: "/base/get",
params: {
foo: "@:$, "
}
});
最终请求的 url
是 /base/get?foo=@:$+
,注意,我们会把空格 ``转换成+
。
空值忽略
对于值为 null
或者 undefined
的属性,我们是不会添加到 url 参数中的。
axios({
method: "get",
url: "/base/get",
params: {
foo: "bar",
baz: null
}
});
最终请求的 url
是 /base/get?foo=bar
。
丢弃 url 中的哈希标记
axios({
method: "get",
url: "/base/get#hash",
params: {
foo: "bar"
}
});
最终请求的 url
是 /base/get?foo=bar
保留 url 中已存在的参数
axios({
method: "get",
url: "/base/get?foo=bar",
params: {
bar: "baz"
}
});
最终请求的 url
是 /base/get?foo=bar&bar=baz
具体实现
在 utils
下新建 url.ts
import { isDate, isObject } from ".";
function encode(val: string): string {
return encodeURIComponent(val)
.replace(/%40/g, "@")
.replace(/%3A/gi, ":")
.replace(/%24/g, "$")
.replace(/%2C/gi, ",")
.replace(/%20/g, "+")
.replace(/%5B/gi, "[")
.replace(/%5D/gi, "]");
}
export function buildURL(url: string, params?: any) {
// 判断是否存在params
if (!params) {
return url;
}
// 最终存放的key-value数组
const parts: string[] = [];
Object.keys(params).forEach(key => {
let val = params[key];
// 排除val为null或者undefined
if (val === null || typeof val === "undefined") {
return;
}
// 临时存储values数组
let values: string[];
// 如果val是数组,key拼接[]字符串
if (Array.isArray(val)) {
values = val;
key += "[]";
} else {
// 强行塞进数组
values = [val];
}
// 判断是否是事件或者对象格式,分别处理
values.forEach(val => {
if (isDate(val)) {
val = val.toISOString();
} else if (isObject(val)) {
val = JSON.stringify(val);
}
// 最后处理特殊字符,空格等等
parts.push(`${encode(key)}=${encode(val)}`);
});
});
// &分割数组
let serializedParams = parts.join("&");
if (serializedParams) {
// 判断是否存在哈希,存在直接干掉哈希
const hashIndex = url.indexOf("#");
if (hashIndex !== -1) {
serializedParams = serializedParams.slice(0, hashIndex);
}
// 判断url是否已经存在?
url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams;
}
return url;
}
接着只需要在 app.ts
处模拟请求即可
# 处理请求 data 数据
上面处理 url
会存在 bug
,如果传入对象格式的data
,接口返回的 response
会是空值。这里需要对 config.data
判断处理
export function transformRequest(data: any) {
if (isObject(data)) {
return JSON.stringify(data)
}
return data
}
//index.ts
function processConfig(config: AxiosRequestConfig) {
...
config.data = transformBody(config)
}
// 转换data
function transformBody(config: AxiosRequestConfig) {
const { data } = config
return transformRequest(data)
}
# 处理请求 headers
做了请求数据的处理,把 data
转换成了 JSON 字符串,但是数据发送到服务端的时候,服务端并不能正常解析我们发送的数据。这是因为还需要改造请求头 Content-Type
// header.ts
function normalizeHearderName(headers: any, normalizedName: string): void {
if (!headers) {
return;
}
Object.keys(headers).forEach(name => {
if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
headers[normalizedName] = headers[name];
// 删除多余项
delete headers[name];
}
});
}
export function processHeaders(headers: any, data: any): any {
// 处理大小写问题
normalizeHearderName(headers, "Content-Type");
// 如果data是纯对象格式,则需要添加content-type头
if (isObject(data)) {
if (headers && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json;charset=utf-8";
}
}
return headers;
}
index.ts
和 xhr.ts
// xhr.ts加入
Object.keys(headers).forEach(name => {
// 当data为null,header就不需要存在了
if (data === null && name.toLowerCase() === "content-type") {
delete headers[name];
} else {
XML.setRequestHeader(name, headers[name]);
}
});
// index.ts
function processConfig(config: AxiosRequestConfig) {
config.url = transformUrl(config);
// 此处必定是先处理headers 然后处理data,因为data为对象时需要设置headers的content-type
config.headers = transformHeaders(config);
config.data = transformBody(config);
}
// 转换headers
function transformHeaders(config: AxiosRequestConfig) {
// 此处需要传入默认值
const { headers = {}, data } = config;
return processHeaders(headers, data);
}
至此,基础请求部分封装完成
# 处理响应数据
我们发送的请求都可以从网络层面接收到服务端返回的数据,但是代码层面并没有做任何关于返回数据的处理。我们希望能处理服务端响应的数据,并支持 Promise 链式调用的方式
axios({
method: "post",
url: "/base/post",
data: {
a: 1,
b: 2
}
}).then(res => {
console.log(res);
});
接口定义
根据需求,我们可以定义一个 AxiosResponse
接口类型,如下:
export interface AxiosResponse {
data: any;
status: number;
statusText: string;
headers: any;
config: AxiosRequestConfig;
request: any;
}
另外,axios
函数返回的是一个 Promise
对象,我们可以定义一个 AxiosPromise
接口,它继承于 Promise<AxiosResponse>
这个泛型接口:
export interface AxiosPromise extends Promise<AxiosResponse> {}
这样的话,当 axios
返回的是 AxiosPromise
类型,那么 resolve
函数中的参数就是一个 AxiosResponse
类型。
对于一个 AJAX 请求的 response
,我们是可以指定它的响应的数据类型的,通过设置 XMLHttpRequest
对象的 responseType
(opens new window) 属性,于是我们可以给 AxiosRequestConfig
类型添加一个可选属性:
export interface AxiosRequestConfig {
// ...
responseType?: XMLHttpRequestResponseType;
}
responseType
的类型是一个 XMLHttpRequestResponseType
类型,它的定义是 "" | "arraybuffer" | "blob" | "document" | "json" | "text"
字符串字面量类型。
实现获取响应数据逻辑
首先我们要在 xhr
函数添加 onreadystatechange
(opens new window) 事件处理函数,并且让 xhr
函数返回的是 AxiosPromise
类型。
export default function xhr(config: AxiosRequestConfig): AxiosPromise {
return new Promise(resolve => {
const { data, method = "get", url, headers, responseType } = config;
const XML = new XMLHttpRequest();
// 如果设置返回类型,则更新
if (responseType) {
XML.responseType = responseType;
}
XML.open(method.toUpperCase(), url, true);
// 监听请求状态
XML.onreadystatechange = function handleLoad() {
if (XML.readyState !== 4) {
return;
}
// 组装resolve的数据
const responseHeaders = parseHeaders(XML.getAllResponseHeaders());
// 返回数据 判断是否是否是文本
const responseData = responseType && responseType !== "text" ? XML.response : XML.responseText;
console.log(responseType);
const response: AxiosResponse = {
data: responseData,
config,
headers: responseHeaders,
status: XML.status,
request: XML,
statusText: XML.statusText
};
resolve(response);
};
// 处理headers,真正添加请求头
Object.keys(headers).forEach(name => {
// 当data为null,header就不需要存在了
if (data === null && name.toLowerCase() === "content-type") {
delete headers[name];
} else {
XML.setRequestHeader(name, headers[name]);
}
});
XML.send(data);
});
}
处理响应 header
我们通过 XMLHttpRequest
对象的 getAllResponseHeaders
方法获取到的值是如下一段字符串:
date: Fri, 05 Apr 2019 12:40:49 GMT
etag: W/"d-Ssxx4FRxEutDLwo2+xkkxKc4y0k"
connection: keep-alive
x-powered-by: Express
content-length: 13
content-type: application/json; charset=utf-8
接下来处理这段文本即可
export function parseHeaders(headers: string): any {
let parsed = Object.create(null);
// 判空
if (!headers) {
return parsed;
}
headers.split("\r\n").forEach(line => {
let [key, value] = line.split(":");
// 去空格转小写
key = key.trim().toLowerCase();
if (!key) {
return;
}
if (value) {
value = value.trim();
}
parsed[key] = value;
});
return parsed;
}
处理响应 data
我们希望默认不传入responseType
也将 string
类型的数据转换为对象格式
export function transformResponse(data: any): any {
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (error) {
console.log(error);
}
}
return data;
}
至此基础需求封装已经实现,接下来处理其他部分。
基础部分代码:传送门 (opens new window)
# ts-axios 异常处理
首先需要实现以下异常捕获
axios({
method: "get",
url: "/error/get"
})
.then(res => {
console.log(res);
})
.catch(e => {
console.log(e);
});
错误处理可以分为三种:
- 网络错误
- 超时错误
- 非 200 状态码错误
而这三部分均可以在 xhr.ts
中通过监听进行处理
// 监听请求状态
XML.onreadystatechange = function handleLoad() {
if (XML.readyState !== 4) {
return;
}
// 网络错误和超时错误时 status都为0
if (XML.status === 0) {
return;
}
// 组装resolve的数据
const responseHeaders = parseHeaders(XML.getAllResponseHeaders());
// 返回数据 判断是否是否是文本
const responseData = responseType && responseType !== "text" ? XML.response : XML.responseText;
const response: AxiosResponse = {
data: responseData,
config,
headers: responseHeaders,
status: XML.status,
request: XML,
statusText: XML.statusText
};
handleResponse(response);
};
// 网络异常
XML.onerror = function onError() {
reject(createError("Network Error", config, null, XML));
};
// 超时错误
if (timeout) {
XML.timeout = timeout;
}
XML.ontimeout = function onTimeOut() {
reject(createError(`Timeout of ${timeout} ms exceeded`, config, "10000", XML));
};
// 处理非200状态码
function handleResponse(response: AxiosResponse) {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(createError(`Request failed with status code ${response.status}`, config, null, XML, response));
}
}
我们希望对外提供的信息不仅仅包含错误文本信息,还包括了请求对象配置 config
,错误代码 code
,XMLHttpRequest
对象实例 request
以及自定义响应对象 response
axios({
method: "get",
url: "/error/timeout",
timeout: 2000
})
.then(res => {
console.log(res);
})
.catch((e: AxiosError) => {
console.log(e.message);
console.log(e.request);
console.log(e.code);
});
首先创建 AxiosError
类,用于外部使用
export interface AxiosError extends Error {
config: AxiosRequestConfig;
code?: string;
request?: any;
response?: AxiosResponse;
isAxiosError: boolean;
}
接着我们创建 error.ts
文件,然后实现 AxiosError
类,它是继承于 Error
类。
helpers/error.ts
:
import { AxiosRequestConfig, AxiosResponse } from "../types";
export class AxiosError extends Error {
isAxiosError: boolean;
config: AxiosRequestConfig;
code?: string | null;
request?: any;
response?: AxiosResponse;
constructor(
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
) {
super(message);
this.config = config;
this.code = code;
this.request = request;
this.response = response;
this.isAxiosError = true;
Object.setPrototypeOf(this, AxiosError.prototype);
}
}
export function createError(
message: string,
config: AxiosRequestConfig,
code?: string | null,
request?: any,
response?: AxiosResponse
): AxiosError {
const error = new AxiosError(message, config, code, request, response);
return error;
}
AxiosError
继承于 Error
类,添加了一些自己的属性:config
、code
、request
、response
、isAxiosError
等属性。这里要注意一点,我们使用了 Object.setPrototypeOf(this, AxiosError.prototype)
,这段代码的目的是为了解决 TypeScript 继承一些内置对象的时候的坑,参考 (opens new window)。
另外,为了方便使用,我们对外暴露了一个 createError
的工厂方法。
修改关于错误对象创建部分的逻辑,如下:
xhr.ts
:
import { createError } from "./helpers/error";
request.onerror = function handleError() {
reject(createError("Network Error", config, null, request));
};
request.ontimeout = function handleTimeout() {
reject(createError(`Timeout of ${config.timeout} ms exceeded`, config, "ECONNABORTED", request));
};
function handleResponse(response: AxiosResponse) {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(createError(`Request failed with status code ${response.status}`, config, null, request, response));
}
}
在 demo 中,TypeScript 并不能把 e
参数推断为 AxiosError
类型,于是我们需要手动指明类型,为了让外部应用能引入 AxiosError
类型,我们也需要把它们导出。
我们创建 axios.ts
文件,把之前的 index.ts
的代码拷贝过去,然后修改 index.ts
的代码。
index.ts
:
import axios from "./axios";
export * from "./types";
export default axios;
这样我们在 demo 中就可以引入 AxiosError
类型了。
examples/error/app.ts
:
import axios, { AxiosError } from "../../src/index";
axios({
method: "get",
url: "/error/timeout",
timeout: 2000
})
.then(res => {
console.log(res);
})
.catch((e: AxiosError) => {
console.log(e.message);
console.log(e.code);
});
# ts-axios 接口扩展
需求分析
为了用户更加方便地使用 axios 发送请求,我们可以为所有支持请求方法扩展一些接口:
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
如果使用了这些方法,我们就不必在 config
中指定 url
、method
、data
这些属性了。
从需求上来看,axios
不再单单是一个方法,更像是一个混合对象,本身是一个方法,又有很多方法属性,接下来我们就来实现这个混合对象。
接口定义
根据需求分析,混合对象 axios
本身是一个函数,我们再实现一个包括它属性方法的类,然后把这个类的原型属性和自身属性再拷贝到 axios
上。
我们先来给 axios
混合对象定义接口:
types/index.ts
:
export interface Axios {
request(config: AxiosRequestConfig): AxiosPromise;
get(url: string, config?: AxiosRequestConfig): AxiosPromise;
delete(url: string, config?: AxiosRequestConfig): AxiosPromise;
head(url: string, config?: AxiosRequestConfig): AxiosPromise;
options(url: string, config?: AxiosRequestConfig): AxiosPromise;
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
}
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
}
export interface AxiosRequestConfig {
url?: string;
// ...
}
首先定义一个 Axios
类型接口,它描述了 Axios
类中的公共方法,接着定义了 AxiosInstance
接口继承 Axios
,它就是一个混合类型的接口。
另外 AxiosRequestConfig
类型接口中的 url
属性变成了可选属性。
创建 axios 类
我们创建一个 Axios
类,来实现接口定义的公共方法。我们创建了一个 core
目录,用来存放发送请求核心流程的代码。我们在 core
目录下创建 Axios.ts
文件。
core / Axios.ts;
import { AxiosRequestConfig, AxiosPromise, Method } from "../types";
import dispatchRequest from "./dispatchRequest";
export default class Axios {
request(config: AxiosRequestConfig): AxiosPromise {
return dispatchRequest(config);
}
get(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("get", url, config);
}
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("delete", url, config);
}
head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("head", url, config);
}
options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithoutData("options", url, config);
}
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData("post", url, data, config);
}
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData("put", url, data, config);
}
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this._requestMethodWithData("patch", url, data, config);
}
_requestMethodWithoutData(method: Method, url: string, config?: AxiosRequestConfig) {
return this.request(
Object.assign(config || {}, {
method,
url
})
);
}
_requestMethodWithData(method: Method, url: string, data?: any, config?: AxiosRequestConfig) {
return this.request(
Object.assign(config || {}, {
method,
url,
data
})
);
}
}
其中 request
方法的功能和我们之前的 axios
函数功能是一致。axios
函数的功能就是发送请求,基于模块化编程的思想,我们把这部分功能抽出一个单独的模块,在 core
目录下创建 dispatchRequest
方法,把之前 axios.ts
的相关代码拷贝过去。另外我们把 xhr.ts
文件也迁移到 core
目录下。
core/dispatchRequest.ts
:
import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "../types";
import xhr from "./xhr";
import { buildURL } from "../helpers/url";
import { transformRequest, transformResponse } from "../helpers/data";
import { processHeaders } from "../helpers/headers";
export default function dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
processConfig(config);
return xhr(config).then(res => {
return transformResponseData(res);
});
}
function processConfig(config: AxiosRequestConfig): void {
config.url = transformURL(config);
config.headers = transformHeaders(config);
config.data = transformRequestData(config);
}
function transformURL(config: AxiosRequestConfig): string {
const { url, params } = config;
return buildURL(url, params);
}
function transformRequestData(config: AxiosRequestConfig): any {
return transformRequest(config.data);
}
function transformHeaders(config: AxiosRequestConfig) {
const { headers = {}, data } = config;
return processHeaders(headers, data);
}
function transformResponseData(res: AxiosResponse): AxiosResponse {
res.data = transformResponse(res.data);
return res;
}
回到 Axios.ts
文件,对于 get
、delete
、head
、options
、post
、patch
、put
这些方法,都是对外提供的语法糖,内部都是通过调用 request
方法实现发送请求,只不过在调用之前对 config
做了一层合并处理。
混合对象实现
混合对象实现思路很简单,首先这个对象是一个函数,其次这个对象要包括 Axios
类的所有原型属性和实例属性,我们首先来实现一个辅助函数 extend
。
helpers/util.ts
export function extend<T, U>(to: T, from: U): T & U {
for (const key in from) {
;(to as T & U)[key] = from[key] as any
}
return to as T & U
}
extend
方法的实现用到了交叉类型,并且用到了类型断言。extend
的最终目的是把 from
里的属性都扩展到 to
中,包括原型上的属性。
我们接下来对 axios.ts
文件做修改,我们用工厂模式去创建一个 axios
混合对象。
axios.ts
:
import { AxiosInstance } from "./types";
import Axios from "./core/Axios";
import { extend } from "./helpers/util";
function createInstance(): AxiosInstance {
const context = new Axios();
const instance = Axios.prototype.request.bind(context);
extend(instance, context);
return instance as AxiosInstance;
}
const axios = createInstance();
export default axios;
在 createInstance
工厂函数的内部,我们首先实例化了 Axios
实例 context
,接着创建instance
指向 Axios.prototype.request
方法,并绑定了上下文 context
;接着通过 extend
方法把 context
中的原型方法和实例方法全部拷贝到 instance
上,这样就实现了一个混合对象:instance
本身是一个函数,又拥有了 Axios
类的所有原型和实例属性,最终把这个 instance
返回。由于这里 TypeScript
不能正确推断 instance
的类型,我们把它断言成 AxiosInstance
类型。
这样我们就可以通过 createInstance
工厂函数创建了 axios
,当直接调用 axios
方法就相当于执行了 Axios
类的 request
方法发送请求,当然我们也可以调用 axios.get
、axios.post
等方法。
测试
在 examples
目录下创建 extend
目录,在 extend
目录下创建 index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Extend example</title>
</head>
<body>
<script src="/__build__/extend.js"></script>
</body>
</html>
接着创建 app.ts
作为入口文件:
import axios from "../../src/index";
axios({
url: "/extend/post",
method: "post",
data: {
msg: "hi"
}
});
axios.request({
url: "/extend/post",
method: "post",
data: {
msg: "hello"
}
});
axios.get("/extend/get");
axios.options("/extend/options");
axios.delete("/extend/delete");
axios.head("/extend/head");
axios.post("/extend/post", { msg: "post" });
axios.put("/extend/put", { msg: "put" });
axios.patch("/extend/patch", { msg: "patch" });
# ts-axios 拦截器
暂定
# ts-axios 配置化,取消
暂定
# 扩展功能实现
暂定
# 单元测试
单元测试是前端一个很重要的方向,鉴别一个开源库是否靠谱的一个标准是它的单元测试是否完善。有了完整的单元测试,未来你去重构现有代码或者是增加新的需求都会有十足的把握不出现 regression bug。
# Jest 安装和配置
# 辅助模块单元测试
# 请求模块单元测试
# 编译和发布
本地登录
npm login username: password: email:
修改打包的文件名,和主入口文件,例如
rollup.config.ts
const libraryName = "axios";
export default {
input: `src/index.ts`,
output: [
{ file: pkg.main, name: camelCase(libraryName), format: "umd", sourcemap: true },
{ file: pkg.module, format: "es", sourcemap: true }
],
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
external: [],
watch: {
include: "src/**"
},
plugins: [
// Allow json resolution
json(),
// Compile TypeScript files
typescript({ useTsconfigDeclarationDir: true }),
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
commonjs(),
// Allow node_modules resolution, so you can use 'external' to control
// which external modules to include in the bundle
// https://github.com/rollup/rollup-plugin-node-resolve#usage
resolve(),
// Resolve source maps to the original source
sourceMaps()
]
};
修改
package.json
主入口main
和module
以及typings
{ "main": "dist/axios.umd.js", "module": "dist/axios.es5.js", "typings": "dist/types/index.d.ts", "files": ["dist"] }
修改仓库信息
添加打包命令
{ "prepublish": "npm run test:prod && npm run build", "publish": "sh release.sh" }
添加打包脚本
#!/usr/bin/env sh set -e echo "Enter release version: " read VERSION read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r echo # (optional) move to a new line if [[ $REPLY =~ ^[Yy]$ ]] then echo "Releasing $VERSION ..." # commit git add -A git commit -m "[build] $VERSION" npm version $VERSION --message "[release] $VERSION" git push origin master # publish npm publish fi
#!/usr/bin/env sh
用来表示它是一个 shell 脚本。set -e
告诉脚本如果执行结果不为 true 则退出。echo "Enter release version: "
在控制台输出Enter release version:
。read VERSION
表示从标准输入读取值,并赋值给 $VERSION 变量。read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
,其中read -p
表示给出提示符,后面接着Releasing $VERSION - are you sure? (y/n)
提示符;-n 1
表示限定最多可以有 1 个字符可以作为有效读入;-r
表示禁止反斜线的转义功能。因为我们的 read 并没有指定变量名,那么默认这个输入读取值会赋值给$REPLY
变量。echo
输出空值表示跳到一个新行,#
在 shell 脚本中表示注释。if [[ $REPLY =~ ^[Yy]$ ]]
表示 shell 脚本中的流程控制语句,判断$REPLY
是不是大小写的y
,如果满足,则走到后面的then
逻辑。echo "Releasing $VERSION ..."
在控制台输出Releasing $VERSION ...
。git add -A
表示把代码所有变化提交到暂存区。git commit -m "[build] $VERSION"
表示提交代码,提交注释是[build] $VERSION
。npm version $VERSION --message "[release] $VERSION"
是修改package.json
中的version
字段到$VERSION
,并且提交一条修改记录,提交注释是[release] $VERSION
。git push origin master
是把代码发布到主干分支。npm publish
是把仓库发布到npm
上,我们会把dist
目录下的代码都发布到npm
上,因为我们在package.json
中配置的是files
是["dist"]
← TSX 笔记 git 及 linux 命令 →