├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── lib ├── config.js ├── index.js ├── interceptor.js ├── request.js └── utils.js ├── package.json ├── scripts ├── merge.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── web ├── index.html └── index.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | ["transform-runtime", { 6 | "helpers": false, 7 | "polyfill": false, 8 | "regenerator": true, 9 | "moduleName": "babel-runtime" 10 | }] 11 | ] 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .temp 2 | .vscode 3 | /dist 4 | /node_modules 5 | /test 6 | yarn-error.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | .idea/* 4 | .DS_Store 5 | 6 | node_modules/* 7 | tests/test_ts.js 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | script: npm run build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # retrofit-cjs 2 | 3 | > **retrofit-cjs** 是一个基于JavaScript装饰器(Decorator)和 axios 实现的网络请求库, 支持Vue / React / react-native 等常用框架 4 | [](http://travis-ci.org/glangzh/retrofit-cjs) 5 | [](https://www.npmjs.com/package/retrofit-cjs) [](https://www.npmjs.com/package/retrofit-cjs) [](https://www.npmjs.com/package/retrofit-cjs) 6 | 7 | ## 使用方法 8 | **1. 安装** 9 | ```sh 10 | npm i retrofit-cjs --save 11 | ``` 12 | **Babel 转码器的支持** 13 | 14 | 安装 babel-plugin-transform-decorators-legacy 15 | ```sh 16 | npm i babel-plugin-transform-decorators-legacy -D 17 | ``` 18 | 配置 .babelrc 文件 19 | ```json 20 | "plugins": ["transform-decorators-legacy"] 21 | ``` 22 | **如果是使用 create-react-app 创建的项目,需要先弹出 webpack 配置** 23 | ```sh 24 | npm run eject 25 | ``` 26 | **安装 babel-plugin-transform-decorators-legacy,在 package.json 中配置 babel** 27 | ```json 28 | "babel": { 29 | "presets": [ 30 | "react-app" 31 | ], 32 | "plugins": [ 33 | "transform-decorators-legacy" 34 | ] 35 | } 36 | ``` 37 | **vue-cli3 已默认支持 Decorator** 38 | 39 | **2. 引入 retrofit-cjs** 40 | ```js 41 | import { GET, POST, Headers } from 'retrofit-cjs'; 42 | ``` 43 | 44 | ## 修饰器 45 | ##### 属性方法修饰器 46 | * [@GET](#GET) 47 | ```js 48 | @GET('/v1/topics') 49 | @GET('/v1/{0}', 'topics') 50 | @GET('https://cnodejs.org/api/v1/topics') 51 | @GET({url: '/v1/topics', params: {limit: 10}}) 52 | @GET('/v1/topic/{topicId}') 53 | ``` 54 | * [@POST](#POST) 55 | ```js 56 | @POST({url: '/user', data: {id: 1, name: 'glang'}}) 57 | ``` 58 | * [@PUT](#PUT) 59 | * [@DELETE](#DELETE) 60 | * [@HTTP](#HTTP) 61 | * [@Config](#Config) 62 | * 设置请求信息,只作用与当前请求 63 | * [查看请求配置](https://github.com/axios/axios#request-config) 64 | * [@Headers](#Headers) 65 | * 设置请求头,只作用与当前请求 66 | * [@Cancel](#Cancel) 67 | * [@Multipart](#Multipart) 68 | * [@FormUrlEncoded](#FormUrlEncoded) 69 | * 以表单方式提交数据,默认以json方式提交 70 | * [兼容适配处理方案](https://github.com/axios/axios#using-applicationx-www-form-urlencoded-format) 71 | * [通过请求拦截器处理](#AddReqInterceptor) 72 | ##### 类修饰器 73 | * [@Create](#Create) 74 | * **创建新的请求实例,后续其他操作都依赖与创建的实例,默认使用全局的请求实例。可配置请求基础信息,也可通过 @Config 和 @Headers 完成基础信息配置** 75 | * [@Config](#Config) 76 | * 配置全局请求信息,**若使用了 @Create 则作用与当前请求对象,否则作用与全局对象** 77 | * [@Headers](#Headers) 78 | * 配置全局请求头信息,**若使用了 @Create 则作用与当前请求对象,否则作用与全局对象** 79 | * [@AddReqInterceptor](#AddReqInterceptor) 添加请求拦截器 80 | * [@AddResInterceptor](#AddResInterceptor) 添加响应拦截器 81 | > 注意配置顺序,修饰器会从内向外执行 82 | ##### 一些工具修饰器 83 | * [@Debounce](#Debounce) 防抖 84 | ```js 85 | @Debounce(1000) // 1秒防抖 86 | @Debounce(1000, true) // 1秒防抖, 立即执行 87 | ``` 88 | * [@Throttle](#Throttle) 节流 89 | ```js 90 | @Throttle(1000, {leading: false}) // 忽略开始函数的的调用 91 | @Throttle(1000, {trailing: false}) // 忽略结尾函数的调用 92 | // 两者不能共存, 否则函数不能执行 93 | ``` 94 | * [@Timer](#Timer) 定时操作 95 | ```js 96 | @Timer(1000) // 延迟1秒执行 97 | @Timer(1000, true) // 延迟1秒执行, 立即执行修饰函数 98 | ``` 99 | * [@Interval](#Interval) 定时操作 100 | ```js 101 | @Interval(1000) // 每隔1秒执行一次 102 | @Interval(1000, true) // 每隔1秒执行一次, 立即执行修饰函数 103 | @Interval(1000, 10000) // 每隔1秒执行一次, 10秒后结束 104 | @Interval(1000, 10000, true) // 每隔1秒执行一次, 10秒后结束,立即执行修饰函数 105 | ``` 106 | * [@RetroPlugin](#RetroPlugin) Vue 插件:全局配置网络请求 107 | > **注意:在同一个方法上,@Debounce,@Throttle,@Timer,@Interval 和 @GET,@POST,@PUT,@DELETE,@HTTP是无法同时使用的** 108 | 109 | **3. 使用** 110 | 111 | 1. 推荐 112 | ```js 113 | @AddResInterceptor((res)=>{ 114 | // response result 115 | return res; 116 | }, (error)=>{ 117 | // response error 118 | }) 119 | @Config({timeout: 2000}) 120 | @Headers({'User-Agent': 'request'}) 121 | @Create({ 122 | baseURL: 'https://cnodejs.org/api', 123 | timeout: 1000, 124 | headers: { 125 | 'X-Custom-Header': 'foobar' 126 | } 127 | }) 128 | class TopicApi{ 129 | static getInstance(){ 130 | return new TopicApi(); 131 | } 132 | 133 | @Cancel((cancel) => { 134 | // cancel(); //取消当前请求 135 | }) 136 | @Config({timeout: 1000}) 137 | @Headers({'User-Agent': 'request'}) 138 | @GET('/v1/topics') 139 | // @GET('/v1/{0}', 'topics') 140 | // @GET('https://cnodejs.org/api/v1/topics') 141 | // @GET({url: '/v1/topics', params: {limit: 10}}) 142 | getTopicList(res){ 143 | // 处理结果 144 | return res; 145 | } 146 | 147 | @Debounce(2000) 148 | // @HTTP({ 149 | // url: '/v1/topic/5433d5e4e737cbe96dcef312', 150 | // method: 'get', 151 | // params: {} 152 | // }) 153 | @GET('/v1/topic/{topicId}') 154 | getTopicDetails(res){ 155 | // response result 156 | } 157 | 158 | // 以表单方式提交数据 159 | @FormUrlEncoded 160 | @POST('/user') 161 | // @POST({url: '/user', data: {id: 1, name: 'glang'}}) 162 | addUser(res) { 163 | 164 | } 165 | } 166 | 167 | let topicApi = TopicApi.getInstance(); 168 | // topicApi.getTopicDetails('topicId=5433d5e4e737cbe96dcef312', { 169 | // limit: 20 170 | // }); 171 | // 参数会按 {} 自动匹配 172 | topicApi.getTopicDetails({ 173 | topicId: '5433d5e4e737cbe96dcef312', 174 | limit: 20 175 | }); 176 | topicApi.addUser({id: 1, name: 'glang'}); 177 | ``` 178 | 2. react / react-native 179 | ```js 180 | import {Interval} from 'retrofit-cjs'; 181 | 182 | @Create({ 183 | baseURL: 'https://cnodejs.org/api' 184 | }) 185 | class App extends Component{ 186 | constructor(props) { 187 | super(props); 188 | // this.countdwon = this.countdwon.bind(this); 189 | } 190 | 191 | @GET('/v1/topics') 192 | getTopicList(res){ 193 | // 处理结果 194 | 195 | } 196 | 197 | @Interval(1000, 60 * 1000) 198 | countdwon(){ 199 | 200 | } 201 | } 202 | ``` 203 | 3. vue 204 | ```js 205 | export default { 206 | name: "app", 207 | mounted() { 208 | this.getList(); 209 | }, 210 | methods: { 211 | // @Config 只影响当前网络请求 212 | @Config({ 213 | baseURL: 'https://cnodejs.org/api', 214 | timeout: 1000 215 | }) 216 | @GET("/v1/topics") 217 | getList(res, err) { 218 | // 219 | } 220 | } 221 | } 222 | ``` 223 | ### @RetroPlugin 224 | > 使用Vue插件配置请求基本信息 225 | ```js 226 | // Vue 入口文件 227 | import Vue from 'vue' 228 | import {RetroPlugin} from 'retrofit-cjs'; 229 | 230 | Vue.use(RetroPlugin, { 231 | baseURL: 'https://cnodejs.org/api', 232 | timeout: 1000, 233 | headers: { 234 | 'X-Custom-Header': 'foobar' 235 | } 236 | }); 237 | ``` 238 | 239 | ### @AddReqInterceptor 240 | > 通过请求拦截器处理请求参数 241 | ```js 242 | @AddReqInterceptor((request)=>{ 243 | request.transformRequest = [function (data) { 244 | let ret = '' 245 | for (let it in data) { 246 | ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&' 247 | } 248 | return ret 249 | }] 250 | return request; 251 | }) 252 | class TopicApi{ 253 | ... 254 | } 255 | ``` 256 | 257 | 258 | 欢迎大佬们吐槽。 259 | 260 | # LICENSE 261 | 262 | MIT@[https://github.com/glangzh](glangzh) 263 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const CancelToken = axios.CancelToken; 3 | 4 | // Custom instance defaults 5 | export const Create = config => (target, property, descriptor) => { 6 | if (!descriptor) { // 避免在方法上添加 7 | target.prototype.axios = target.prototype.$axios = axios.create(config); 8 | } 9 | } 10 | 11 | // Config Defaults custom or global 12 | export const Config = config => (target, property, descriptor) => { 13 | if (typeof config === 'object') { 14 | if (descriptor) { 15 | descriptor.value._config = Object.assign({}, descriptor.value._config, config); 16 | } else { 17 | let instance = target.prototype.axios; 18 | config && Object.keys(config).forEach((key) => { 19 | if (!instance || config.global) { 20 | axios.defaults[key] = config[key]; 21 | } else { 22 | instance.defaults[key] = config[key]; 23 | } 24 | }) 25 | } 26 | } 27 | } 28 | 29 | // config header 30 | export const Headers = headers => (target, property, descriptor) => { 31 | if (typeof headers === 'object') { 32 | if (descriptor) { 33 | descriptor.value._headers = Object.assign({}, descriptor.value._headers, headers); 34 | } else { 35 | let instance = target.prototype.axios; 36 | headers && Object.keys(headers).forEach((key) => { 37 | if (!instance || headers.global) { 38 | if ('Content-Type' === key) { 39 | axios.defaults.headers.post[key] = headers[key]; 40 | } else { 41 | axios.defaults.headers.common[key] = headers[key]; 42 | } 43 | } else { 44 | if ('Content-Type' === key) { 45 | axios.defaults.headers.post[key] = headers[key]; 46 | } else { 47 | instance.defaults.headers.common[key] = headers[key]; 48 | } 49 | } 50 | }) 51 | } 52 | } 53 | } 54 | 55 | // cancel 56 | export const Cancel = (cb) => (target, property, descriptor) => { 57 | if (descriptor && cb) { 58 | descriptor.value._headers = Object.assign({}, descriptor.value._headers, { 59 | cancelToken: new CancelToken(ctoken => { 60 | cb.call(target, ctoken); 61 | }) 62 | }); 63 | } 64 | } 65 | 66 | // 发送form-encoded的数据(适用于 文件 上传的场景) 67 | export const Multipart = (target, property, descriptor) => { 68 | if (descriptor) { 69 | descriptor.value._headers = Object.assign({}, descriptor.value._headers, { 70 | 'Content-Type': 'multipart/form-data' 71 | }); 72 | } 73 | return descriptor; 74 | } 75 | 76 | export const FormUrlEncoded = (target, property, descriptor) => { 77 | if (descriptor) { 78 | descriptor.value._headers = Object.assign({}, descriptor.value._headers, { 79 | 'Content-Type': 'application/x-www-form-urlencoded' 80 | }); 81 | } 82 | return descriptor; 83 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET, 3 | POST, 4 | PUT, 5 | DELETE, 6 | HTTP 7 | } from './request'; 8 | import { 9 | Create, 10 | Config, 11 | Headers, 12 | Cancel, 13 | Multipart, 14 | FormUrlEncoded 15 | } from './config'; 16 | import { 17 | AddReqInterceptor, 18 | AddResInterceptor 19 | } from './interceptor'; 20 | import { 21 | Debounce, 22 | Throttle, 23 | Timer, 24 | Interval, 25 | RetroPlugin 26 | } from './utils'; 27 | 28 | export { 29 | GET, 30 | POST, 31 | PUT, 32 | DELETE, 33 | HTTP, 34 | Create, 35 | Config, 36 | Headers, 37 | Cancel, 38 | Multipart, 39 | FormUrlEncoded, 40 | AddReqInterceptor, 41 | AddResInterceptor, 42 | Debounce, 43 | Throttle, 44 | Timer, 45 | Interval, 46 | RetroPlugin 47 | } -------------------------------------------------------------------------------- /lib/interceptor.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | // interceptors 4 | export const AddReqInterceptor = (config, error, eject = false) => (target, property, descriptor) => { 5 | if (!descriptor) { 6 | _addReqInterceptor(target.axios || target.prototype.axios, config, error, typeof error === 'boolean' ? error : eject); 7 | } 8 | } 9 | 10 | export const AddResInterceptor = (response, error, eject = false) => (target, property, descriptor) => { 11 | if (!descriptor) { 12 | _addResInterceptor(target.axios || target.prototype.axios, response, error, typeof error === 'boolean' ? error : eject); 13 | } 14 | } 15 | const _addReqInterceptor = (instance, config, error) => { 16 | return !instance ? axios.interceptors.request.use(config, error) : instance.interceptors.request.use(config, error); 17 | } 18 | 19 | const _addResInterceptor = (instance, response, error) => { 20 | return !instance ? axios.interceptors.response.use(response, error) : instance.interceptors.response.use(response, error); 21 | } -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | export const GET = (...args) => request('get', args); 4 | 5 | export const POST = (...args) => request('post', args); 6 | 7 | export const PUT = (...args) => request('put', args); 8 | 9 | export const DELETE = (...args) => request('delete', args); 10 | 11 | export const HTTP = args => typeof args === 'object' && _Request(args); 12 | 13 | const request = (_method, args) => { 14 | return _Request(Object.assign({}, { method: _method }, _ParamsParse(args))) 15 | } 16 | 17 | const _Merge = (args = {}, options) => { 18 | let params = args.params || args.data || {}; 19 | if (options && options.length > 0) { 20 | for (let i = 0, len = options.length; i < len; i++) { 21 | let _option = options[i]; 22 | if (typeof _option === 'string') { 23 | _option = parseQuery(_option); // string -> object 24 | } 25 | 26 | args.url = args.url.replace(/\{(.*?)\}/g, (m, v) => { 27 | return _option[v] || m; 28 | }) 29 | params = Object.assign({}, params, _option); 30 | } 31 | } 32 | return params; 33 | } 34 | 35 | const _ParamsParse = (args) => { 36 | if (!args || args.length === 0) { 37 | return; 38 | } 39 | 40 | let _url = args[0], _params; 41 | if (args.length === 1) { 42 | if (typeof _url === 'object') { 43 | _params = _url.params || _url.data; 44 | _url = _url.url 45 | } 46 | } else { 47 | let _args = Array.prototype.slice.call(args, 1); 48 | _url = _url.replace(/\{(\d+)\}/g, (m, i) => { 49 | return _args[i]; 50 | }); 51 | } 52 | 53 | return { 54 | url: _url, 55 | params: _params 56 | } 57 | } 58 | 59 | const _Request = args => (target, property, descriptor) => { 60 | if (!descriptor) { 61 | process.env.NODE_ENV !== 'production' && console.warn(`http options only works on methods`); 62 | return; 63 | } 64 | 65 | let oldVal = descriptor.value; 66 | descriptor.value = function (..._args) { 67 | let instance = target.axios || this.axios || axios; 68 | let _headers = oldVal._headers || descriptor.value._headers; 69 | let _config = oldVal._config || descriptor.value._config; 70 | 71 | let _params = _Merge(args, _args); 72 | 73 | let config = { 74 | method: args.method, 75 | url: args.url 76 | } 77 | if (_headers) { 78 | config.headers = _headers; 79 | } 80 | if (_params) { 81 | if (args.method == 'get') { 82 | config.params = _params; 83 | } else if (args.method == 'put' || args.method == 'post' || args.method == 'patch') { 84 | config.data = _params; 85 | } else if (args.method == 'delete') { 86 | config.params = _params; 87 | config.data = _params; 88 | } 89 | } 90 | 91 | const req = instance(Object.assign({}, config, _config)); 92 | 93 | req.then((res) => { 94 | oldVal.call(this, res.data); 95 | }).catch((err) => { 96 | oldVal.call(this, {}, err); 97 | }) 98 | }; 99 | } 100 | 101 | const parseQuery = query => { 102 | const reg = /([^=&\s]+)[=\s]*([^&\s]*)/g; 103 | const obj = {}; 104 | while (reg.exec(query)) { 105 | obj[RegExp.$1] = RegExp.$2; 106 | } 107 | return obj; 108 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const DEFAULT_TIMEOUT = 300; 3 | 4 | /** 5 | * 防抖修饰器 6 | * @param {*} wait 执行间隔 7 | * @param {*} immediate 是否立即执行 8 | */ 9 | export const Debounce = (wait = DEFAULT_TIMEOUT, immediate) => (target, property, descriptor) => { 10 | if (descriptor) { 11 | let oldValue = descriptor.value; 12 | descriptor.value = (...args) => debounce(oldValue, wait, immediate)(target, args); 13 | } 14 | } 15 | 16 | const debounce = (func, wait, immediate) => { 17 | let timeout, args, context, timestamp; 18 | 19 | let later = function () { 20 | let last = Date.now() - timestamp; 21 | if (last < wait && last >= 0) { 22 | timeout = setTimeout(later, wait - last); 23 | } else { 24 | timeout = null; 25 | if (!immediate) { 26 | func.apply(context, args); 27 | if (!timeout) context = args = null; 28 | } 29 | } 30 | } 31 | 32 | return (ctx, _args) => { 33 | context = ctx; 34 | args = _args; 35 | 36 | timestamp = Date.now(); 37 | let callNow = immediate && !timeout; 38 | if (!timeout) timeout = setTimeout(later, wait); 39 | 40 | if (callNow) { 41 | func.apply(context, args); 42 | context = args = null; 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * 节流修饰器 49 | * @param {*} wait 执行间隔 50 | * @param {*} options 如果想忽略开始函数的的调用, 传入 {leading: false}。 51 | * 如果想忽略结尾函数的调用, 传入 {trailing: false}。 52 | * 两者不能共存, 否则函数不能执行。 53 | */ 54 | 55 | export const Throttle = (wait = DEFAULT_TIMEOUT, options) => (target, property, descriptor) => { 56 | if (descriptor) { 57 | let oldValue = descriptor.value; 58 | descriptor.value = (...args) => throttle(oldValue, wait, options)(target, args); 59 | } 60 | } 61 | 62 | const throttle = (func, wait, options) => { 63 | let context, args; 64 | let timeout = null; 65 | let previous = 0; 66 | if (!options) options = {}; 67 | let later = function () { 68 | previous = options.leading === false ? 0 : Date.now(); 69 | timeout = null; 70 | func.apply(context, args); 71 | if (!timeout) context = args = null; 72 | }; 73 | 74 | return (ctx, ..._args) => { 75 | let now = Date.now(); 76 | if (!previous && options.leading === false) previous = now; 77 | let remaining = wait - (now - previous); 78 | context = ctx; 79 | args = _args; 80 | if (remaining <= 0 || remaining > wait) { 81 | if (timeout) { 82 | clearTimeout(timeout); 83 | timeout = null; 84 | } 85 | previous = now; 86 | func.apply(context, args); 87 | if (!timeout) context = args = null; 88 | } else if (!timeout && options.trailing !== false) { 89 | timeout = setTimeout(later, remaining); 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * timeout 96 | * @param {*} millisec 时间间隔 97 | * @param {*} immediate 是否立即执行 98 | */ 99 | export const Timer = (millisec, immediate) => (target, property, descriptor) => { 100 | if (descriptor) { 101 | const timeOut = args => { 102 | setTimeout(() => descriptor.value.apply(target, args || []), millisec); 103 | } 104 | 105 | immediate ? timeOut() : descriptor.value = (...args) => timeOut(args); 106 | } 107 | } 108 | 109 | /** 110 | * interval 111 | * @param {*} millisec 时间间隔 112 | * @param {*} endtime 结束时间 113 | * @param {*} immediate 是否立即执行 114 | */ 115 | export const Interval = (millisec, endtime, immediate) => (target, property, descriptor) => { 116 | if (descriptor) { 117 | const _end = interval => setTimeout(() => clearInterval(interval), endtime); 118 | 119 | const _interval = (args = []) => { 120 | let interval = setInterval(() => { 121 | args.unshift(interval); 122 | descriptor.value.apply(target, args); 123 | }, millisec); 124 | return interval; 125 | } 126 | 127 | if ((typeof endtime === 'boolean' && endtime) || immediate) { 128 | let interval = _interval(); 129 | typeof endtime !== 'boolean' && endtime > millisec && _end(interval); 130 | } else { 131 | descriptor.value = (...args) => { 132 | let interval = _interval(args); 133 | endtime > millisec && _end(interval); 134 | } 135 | } 136 | } 137 | } 138 | 139 | 140 | /** 141 | * 通过vue插件 配置 axios 142 | */ 143 | export const RetroPlugin = { 144 | install(Vue, options = {}) { 145 | Vue.prototype.$axios = axios.create(options); 146 | Vue.mixin({ 147 | created() { 148 | let __proto__ = this.$options.__proto__; 149 | if (__proto__ && __proto__.methods) { 150 | __proto__.methods.axios = this.$axios; 151 | } 152 | } 153 | }); 154 | } 155 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "retrofit-cjs", 3 | "version": "1.0.0", 4 | "description": "一个基于js装饰器(Decorator)和 axios 的网络请求库", 5 | "main": "lib/index.js", 6 | "type": "javascript", 7 | "repository": "https://github.com/glangzh/retrofit-cjs", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "watch": "webpack --env.NODE_ENV=common --progress --watch", 11 | "start": "webpack-dev-server --env.NODE_ENV=dev --progress --watch", 12 | "serve": "webpack-dev-server --env.NODE_ENV=development --progress --watch", 13 | "dev": "webpack-dev-server --open --env.NODE_ENV=development --progress --watch --hot --inline", 14 | "common": "webpack --config scripts/webpack.common.js", 15 | "build": "webpack --env.NODE_ENV=common", 16 | "build:prod": "webpack --env.NODE_ENV=production" 17 | }, 18 | "keywords": [ 19 | "retrofit-cjs", 20 | "retrofit.js", 21 | "retrofit", 22 | "axios", 23 | "http", 24 | "network", 25 | "decorator", 26 | "annotations", 27 | "react", 28 | "vue" 29 | ], 30 | "devDependencies": { 31 | "babel-core": "^6.26.3", 32 | "babel-loader": "^7.1.5", 33 | "babel-plugin-transform-decorators": "^6.24.1", 34 | "babel-plugin-transform-decorators-legacy": "^1.3.5", 35 | "babel-plugin-transform-runtime": "^6.23.0", 36 | "babel-preset-env": "^1.7.0", 37 | "babel-preset-stage-0": "^6.24.1", 38 | "clean-webpack-plugin": "^0.1.19", 39 | "css-loader": "^1.0.0", 40 | "file-loader": "^1.1.11", 41 | "html-webpack-plugin": "^3.2.0", 42 | "node-sass": "^4.9.2", 43 | "sass-loader": "^7.0.3", 44 | "style-loader": "^0.21.0", 45 | "uglifyjs-webpack-plugin": "^1.2.7", 46 | "url-loader": "^1.0.1", 47 | "webpack": "^4.16.1", 48 | "webpack-cli": "^3.0.8", 49 | "webpack-dev-server": "^3.1.4" 50 | }, 51 | "dependencies": { 52 | "axios": "^0.18.0" 53 | }, 54 | "bugs": { 55 | "url": "https://github.com/glangzh/retrofit-cjs/issues" 56 | }, 57 | "homepage": "https://github.com/glangzh/retrofit-cjs", 58 | "author": "zhaofg", 59 | "license": "MIT" 60 | } 61 | -------------------------------------------------------------------------------- /scripts/merge.js: -------------------------------------------------------------------------------- 1 | // merge webpack配置文件 2 | function merge(origin, option) { 3 | if(option){ 4 | Object.keys(option).forEach(opt => { 5 | let _option = option[opt]; 6 | let _origin = origin[opt]; 7 | if(_origin){ 8 | if (_option instanceof Array) { 9 | origin[opt] = _origin.concat(option[opt]); 10 | } else if (_option instanceof Object) { 11 | origin[opt] = merge(_origin, _option); 12 | } else { 13 | origin[opt] = option[opt]; 14 | } 15 | } else { 16 | origin[opt] = option[opt]; 17 | } 18 | }); 19 | } 20 | return origin; 21 | } 22 | 23 | module.exports = merge; -------------------------------------------------------------------------------- /scripts/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const htmlWebpackPlugin = require('html-webpack-plugin'); 3 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 4 | const ROOT = path.resolve(__dirname, '..'); 5 | 6 | const config = { 7 | entry: path.join(ROOT, 'web/index.js'), 8 | module: { 9 | // 配置匹配规则 10 | rules: [ 11 | // test 用来配置匹配文件规则(正则) 12 | // use 是一个数组,按照从后往前的顺序执行加载 13 | // 打包 css 14 | { 15 | test: /\.css$/, 16 | use: ['style-loader', 'css-loader'] 17 | }, 18 | // 打包 图片文件 19 | { 20 | test: /\.(jpg|png|gif|jpeg)$/, 21 | use: 'url-loader' 22 | }, 23 | // 打包 字体文件 24 | { 25 | test: /\.(woff|woff2|eot|ttf|otf)$/, 26 | use: 'file-loader' 27 | }, 28 | // 打包 saas 29 | { 30 | test: /\.(scss|sass)$/, 31 | use: ['style-loader', 'css-loader', 'sass-loader'] 32 | }, 33 | // babel 34 | // exclude 排除,不需要编译的目录,提高编译速度 35 | { 36 | test: /\.js$/, 37 | use: 'babel-loader', 38 | exclude: /node_modules/ 39 | } 40 | ] 41 | }, 42 | plugins: [ 43 | new CleanWebpackPlugin(['dist']), 44 | new htmlWebpackPlugin({ 45 | // 模板页面路径 46 | template: path.join(ROOT, './web/index.html') 47 | }) 48 | ], 49 | output: { 50 | path: path.resolve(ROOT, 'dist'), 51 | filename: '[name].bundle.js' 52 | } 53 | }; 54 | 55 | module.exports = [config]; -------------------------------------------------------------------------------- /scripts/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('./merge'); 4 | const common = require('./webpack.common'); 5 | 6 | const config = merge(common, { 7 | mode: 'development', 8 | devtool: 'cheap-module-eval-source-map', 9 | devServer: { 10 | contentBase: path.join(__dirname, '../dist'), 11 | hot: true 12 | }, 13 | plugins: [ 14 | new webpack.HotModuleReplacementPlugin(), 15 | new webpack.NamedModulesPlugin() 16 | ] 17 | }); 18 | 19 | module.exports = config; -------------------------------------------------------------------------------- /scripts/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('./merge'); 3 | const common = require('./webpack.common.js'); 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 5 | 6 | // 生产环境中启用 source-map 选项 7 | // 避免在生产中使用 inline-*** 和 eval-***,因为它们可以增加 bundle 大小,并降低整体性能。 8 | module.exports = merge(common, { 9 | mode: 'production', 10 | optimization: { 11 | minimizer: [ 12 | new UglifyJsPlugin({ 13 | uglifyOptions: { 14 | compress: false 15 | } 16 | }) 17 | ] 18 | } 19 | }); -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |