kuitos / axios-extensions Goto Github PK
View Code? Open in Web Editor NEWπ± axios extensions lib, including throttle, cache, retry features etc...
License: MIT License
π± axios extensions lib, including throttle, cache, retry features etc...
License: MIT License
POST requests are not cached. It is requested for every request. Is there some limitation or Do I need to configure it differently?
In my scenario for GET requests I usually receive a 202 response with a location header, which designates that I need to poll the location for the async response until a non 202 response is returned. So I use an Axios response interceptor to basically poll for the async response until it resolves and then return the result.
Like so:
const myClient= function() {
var client = axios.create({
baseURL: serviceRoot+'/',
timeout: 3000,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
auth: {
username,
password
}
});
/** Here we intercept responses coming from the server to check if the response is async, in which case we start polling and resolve the response the completed operation result */
client.interceptors.response.use(async function (response) {
if (response.headers.location) {
var asyncResLocation = response.headers.location;
response = await retry(async () => {
try {
let asyncRes = httpParser.parseResponse((await client.get(asyncResLocation)).data); // unwrap async response
if (asyncRes.statusCode == 200) {
if (typeof asyncRes.body === 'string') {
let jsonRes = JSON.parse(asyncRes.body);
return jsonRes.value || jsoneRes;
}
return asyncRes;
}
throw new Error(asyncRes);
}
catch (e) {
throw new Error(e);
}
}, {
minTimeout: ASYNC_RESPONSE_POLLING_INTERVAL_MILLISECONDS,
retries: ASYNC_RESPONSE_RETRY_COUNT
}); // End polling
}
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
}
);
return client;
}
Can your awesome cache extension use the calculated result returned from the interceptor as the cache value? I tried a straight forward approach from your docs and couldn't get it to cache the interceptor return value.
Having as a dependency leads to have duplicated dependencies and may lead to inconsistencies
$ npm ls axios
[email protected]
βββ [email protected]
βββ¬ [email protected]
βββ [email protected]
Hello there,
First of all thank you for your magnificient work.
I'm starting to work on a Vue SPA for the first time using Vue-Cli. I used your projet to have throttling and caching on my Ajax requests. It seems to work well but I would like to have logs on the console so my developpers can know what is happening.
I know I'm supposed to add this to my webpack.config:
new webpack.DefinePlugin({ 'process.env.LOGGER_LEVEL': JSON.stringify('info') })
So on my ".env" file I tried to put this:
LOGGER_LEVEL='info'
or even this
LOGGER_LEVEL=info
But no luck...
Do you mind to help me?
Thank you,
Denis
I saw the package only uses isEmpty
from lodash.
So maybe just install lodash.isempty
instead of lodash
?
Or use own function something like
function isEmpty(obj) {
if (!obj) return true;
return !Object.keys(obj).length;
}
It will help reduce the production size.
The package is awesome! Very easy to use. Thank you!
getting Syntax error
Syntax error
bundle.js (1377,24)
const naiveLength = () => 1
Working fine in chrome and Edge.
sorry may seem obvious, but is it safe to use both throttle and cache adapters ?
import axios from 'axios';
import { cacheAdapterEnhancer, throttleAdapterEnhancer } from 'axios-extensions';
// enhance the original axios adapter with throttle and cache enhancer
const enhancedAdapter = throttleAdapterEnhancer(cacheAdapterEnhancer(axios.defaults.adapter, true))
const http = axios.create({
baseURL: '/',
headers: { 'Cache-Control': 'no-cache' },
adapter: throttleAdapterEnhancer(enhancedAdapter, 2 * 1000)
});
Hi, great library!
I wanted to suggest/request the use of a CHANGELOG.md file at the root of the repo that outlines changes (breaking, features, fixes, etc.) between releases π
I'd like to not delete the cache key on failed requests. Would it make sense for an extra option cache failed responses too?
In our API calls we pass a languageid & siteid as header data, which will give us different results. For this it would be handy for us to allow passing a custom cachekey generator with the options.
Do you agree with this idea? If yes, then we can also create a Pull Request with this change if you don't have time to add it?
In throttleAdapterEnhancer & cacheAdapterEnhancer:
var customCacheKey = options.cacheKeyGenerator && options.cacheKeyGenerator(config, buildSortedURL(url, params, paramsSerializer));
var index_1 = customCacheKey || buildSortedURL(url, params, paramsSerializer);
and then in the calling method we can pass the cacheKeyGenerator
to the options.
cacheAdapterEnhancer(axios.defaults.adapter, { cacheKeyGenerator: (config, originalKey) => { /*custom logic*/}});
Hello, as of today, with the latest version of both axios extensions and lru-cache, I get the following error while compiling with typescript:
ERROR in /.../node_modules/axios-extensions/lib/index.d.ts(6,8):
TS1192: Module '"/.../node_modules/@types/lru-cache/index"' has no default export.
Version: typescript 2.9.2
And it indeed is true; I had to change line
import Cache from 'lru-cache';
to
import {Cache} from 'lru-cache';
And it compiles succesfully.
First of all, thank you for the library π
But I got an issue while using this library in CI today:
[lint-ci] Cannot find module '/app/node_modules/axios-extensions/vuejsapp/build/webpack.base.conf.js'
[lint-ci] Error: Cannot find module '/app/node_modules/axios-extensions/vuejsapp/build/webpack.base.conf.js'
[lint-ci] at Function.Module._resolveFilename (module.js:547:15)
[lint-ci] at Function.Module._load (module.js:474:25)
[lint-ci] at Module.require (module.js:596:17)
[lint-ci] at require (internal/module.js:11:18)
[lint-ci] at Object.exports.resolve (/app/node_modules/eslint-import-resolver-webpack/index.js:68:25)
[lint-ci] at v2 (/app/node_modules/eslint-module-utils/resolve.js:94:23)
[lint-ci] at withResolver (/app/node_modules/eslint-module-utils/resolve.js:99:16)
[lint-ci] at fullResolve (/app/node_modules/eslint-module-utils/resolve.js:116:22)
[lint-ci] at Function.relative (/app/node_modules/eslint-module-utils/resolve.js:61:10)
[lint-ci] at remotePath (/app/node_modules/eslint-plugin-import/lib/ExportMap.js:379:30)
[lint-ci] npm ERR! code ELIFECYCLE
[lint-ci] npm ERR! errno 1
After a quick investigation, I've realized that ESLint on CI is trying to extend your config with ESLint config in my project (even though node_modules
is ignored). I think it can be fixed by marking your ESLint config as root, so if your library is placed inside a dir it will not automatically extend the rules defined in the parent directory.
"eslintConfig": {
"root": true, // <-- ADD IT HERE PLEASE
"extends": "shuyun",
"rules": {
"object-curly-spacing": "off",
"no-process-env": "off"
}
},
By default, ESLint will look for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place
"root": true
inside theeslintConfig
field of thepackage.json
file or in the.eslintrc.*
file at your projectβs root level. ESLint will stop looking in parent folders once it finds a configuration with"root": true
.
From: https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
Especially useful for web API's with a rate limit.
It's about being able to queue requests to stay below the limit rate. Or, sometimes API's methods have a nonce and might be rejected if the requests are received in a different order.
No request is dropped.
It would be possible to indicate the minimum amount of time to pass before the next request is made.
Hi guys,
Thanks for this package?
But, I have an issue with the custom cache.
Sometimes, I want to custom cache for each APIs value different.
Ex: GET /users
have cache options: {maxAge: FIVE_MINUTES, max: 100}
but GET /posts
have cache options {maxAge: TEN_MINUTES}
Thank you in advance.
Hi there
Currently, the only way to remove the log output from this module is to set NODE_ENV
to production.
Moreover, axios
is expected to run in Node.js and in browsers: I don't think there is a way to set such a variable in the later environment.
Could we consider using the debug
package instead of console.log ?
(See https://github.com/visionmedia/debug#browser-support)
Using response promises as cache items makes impossible to use options like max
in node-lru-cache library because size of cached item is evaluated in cache's set
method, when the promise is not resolved.
I'm creating issues on both this repo and 3846masa/axios-cookiejar-support hoping that someone either here or there will have some ideas.
Basically I have this:
const axios = require('axios')
const axiosCookieJarSupport = require('axios-cookiejar-support').default
const axiosExtensions = require('axios-extensions')
const LRUCache = require('lru-cache')
// Even tried specifying my own cookie jar
// const tough = require('tough-cookie')
// let cookiejar = new tough.CookieJar()
axiosCookieJarSupport(axios)
let cache = new LRUCache({ maxAge: 300000 })
let cacheAdapter = axiosExtensions.cacheAdapterEnhancer(axios.defaults.adapter, {
defaultCache: cache,
enabledByDefault: true
})
let instance = axios.create({
adapter: cacheAdapter,
baseURL: 'http://example.com',
headers: {
'Cache-Control': 'no-cache'
},
jar: true, // cookiejar,
withCredentials: true
})
// Results in error
instance.get('/')
.then(r => instance.get('/'))
.then(r => console.log(r))
.catch(e => console.error(e))
// This results in an error as well
// async function test() {
// try {
// var r1 = await instance.get('/')
// console.log(r1)
// var r2 = await instance.get('/')
// console.log(r2)
// } catch (e) {
// console.error(e)
// }
// }
// test()
The first request works fine (because there's not a cached version of the request) but then the second one (cached) results in the following error:
TypeError: Cannot read property 'jar' of undefined
at /my-project/node_modules/axios-cookiejar-support/lib/interceptors/response.js:28:15
at new Promise (<anonymous>)
at responseInterceptor (/my-project/node_modules/axios-cookiejar-support/lib/interceptors/response.js:21:10)
at instance.interceptors.response.use.res (/my-project/node_modules/axios-cookiejar-support/lib/index.js:47:67)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
As soon as I remove either config.adapter or config.jar, everything works fine.
I'm having a hard time thinking of how this should even function. I guess that after the requests are cached then cookies are no longer required and should be ignored. Or maybe cached as well?
I'm going to keep playing with both packages and try to figure something out but if anyone has any ideas I'd greatly appreciate it. π
Issue on the other repo: 3846masa/axios-cookiejar-support#157
I currently set qs
as axios.defaults.paramsSerializer
so I can use nested parameters. These extensions do not respect the serializer and are hardcoded to use querystring
.
Hi,
I would like to know if cacheAdapterEnhancer
will work in react-native.
Thanks for reply.
Project looks great! This request is for .js (ES6 or ES5) files to committed into /dist subdirectory, for direct use in Javascript projects.
axios-extensions works great for all my requests except for one: get gravatars.
If I use axios only, it works well.
axios.get(
'https://www.gravatar.com/avatar/46b999a5b1498f5ef58f767845942269?d=404', {
responseType: 'arraybuffer'
})
.then(res => {
// res.data is correct array buffer
})
.catch(err => {});
But if I change to
const http = axios.create({
baseURL: '/',
headers: {
'Cache-Control': 'no-cache'
},
adapter: cacheAdapterEnhancer(axios.defaults.adapter, true)
});
http.get(
'https://www.gravatar.com/avatar/46b999a5b1498f5ef58f767845942269?d=404', {
responseType: 'arraybuffer'
})
.then(res => {})
.catch(err => {});
It will throw the error:
Any idea? Thanks
Building a typescript project with axios and the following code
axios.create({
adapter: throttleAdapterEnhancer(cacheAdapterEnhancer(axios.defaults.adapter, true)),
headers: { 'Cache-Control': 'no-cache' },
});
outputs
/issuer.ts(33,61): error TS2345: Argument of type 'AxiosAdapter' is not assignable to parameter of type 'AxiosAdapter'.
Type 'AxiosPromise<any>' is not assignable to type 'AxiosPromise'.
Types of property 'then' are incompatible.
Type '<TResult1 = AxiosResponse<any>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<any>) => TR...' is not assignable to type '<TResult1 = AxiosResponse, TResult2 = never>(onfulfilled?: (value: AxiosResponse) => TResult1 | P...'.
Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
Types of parameters 'value' and 'value' are incompatible.
Type 'AxiosResponse<any>' is not assignable to type 'AxiosResponse'.
Types of property 'config' are incompatible.
Type 'AxiosRequestConfig' is not assignable to type 'AxiosRequestConfig'. Two different types with this name exist, but they are unrelated.
Types of property 'adapter' are incompatible.
Type 'AxiosAdapter' is not assignable to type 'AxiosAdapter'. Two different types with this name exist, but they are unrelated.
The environment is as follows:
$ npm ls axios typescript
[email protected]
βββ [email protected]
βββ¬ [email protected]
β βββ [email protected]
βββ [email protected]
Maybe related to #13
I am working on a big project, we didn't and don't plan to enable esModuleInterop
.
So that makes us unable to integrate this good library. As a library, assuming the consumer's setting seems not a good idea.
When I use CacheFlag to control caching, CacheFlag is not working in react native as expected. config[cacheFlag] is always undefined in cacheAdapterEnhancer.js. Hence it is not caching
My code is as below
const axiosClient = axios.create({
baseURL: HOST,
headers: {
'Content-type': 'application/json',
'Cache-Control': 'no-cache'
},
adapter: cacheAdapterEnhancer(axios.defaults.adapter, { enabledByDefault: false, cacheFlag: 'useCache'})
});
then in a function
return await axiosClient.get(URL, params, {useCache : true});
If i change in cacheAdapterEnhancer.js
var useCache = (config[cacheFlag] !== void 0 && config[cacheFlag] !== null) ? config[cacheFlag] : enabledByDefault;
to
var useCache = (cacheFlag !== void 0 && cacheFlag !== null) ? cacheFlag : enabledByDefault;
Now the flag works as expected. Kindly help with the issue
Hi, first I want to say this is an awesome lib! Thanks for sharing.
I'm not sure I am using it well in the following scenario:
1- cache all requests
2- forceUpdate all requests in order to have the latest data from server
3- if a request fails because of lost connection, I start a so called "offline mode" and set forceUpdate to false
4- I can use my app offline if I go on all the places I already went to (because they are cached)
5- all the places BUT the first one, the one that failed at step 3. It's either not cached or cached with failure. Even if I had it cached previously, I lost the data.
My question is: is it possible to keep the cache even if the axios call is rejected?
cacheAdapterEnhancer method expects as first value an adapter of type "AxiosAdapter" but the axios.defaults.adapter type is "AxiosAdapter | undefined". This is causing an error when using TypeSCript
Feature Request
It would be useful to allow clients to explicitly clear the cache in cases where data has become stale. This could be easily achieved by exposing the cache on the wrapped adapter, but would change the API slightly.
e.g.
const cache = new LRUCache({ maxAge: cacheAge });
return {
cache,
adapter: config => {
const { url, method, params } = config;
...
usage:
const cacheAdapter = cacheAdapterEnhancer(axios.defaults.adapter, true)
const http = axios.create({
baseURL: '/',
headers: { 'Cache-Control': 'no-cache' },
adapter: cacheAdapter.adapter
});
...
cacheAdapter.cache.reset()
Hi,
Is it possible to cache a request, even when the validateStatus
return false
or when you got a timeout ? I need to save this state to not have thousands of timed out requests in my heroku backlog. I use your extension to handle a status website for a AAA game.
Thanks.
would it be possible to allow a forceUpdate (or clear the cache for that matter) to happen on a Post/Put/Delete request? that way if you do simply just local state updates when adding/updating/deleting on the ui but if the component were to mount again, it would see that the cache is no longer there and make the API request to generate a new cache
I've ran into this error when specifying the headers option to include 'Cache-Control no-cache' as specified in the readme.
Failed to load resource: the server responded with a status of 404 (Not Found)
Response for preflight has invalid HTTP status code 404
The API server in question is https://api.scryfall.com
, I tried some suggested workarounds online like specifying the following extra headers,
headers: {
'Cache-Control': 'no-cache',
'Content-type': 'application/json',
'Access-Control-Allow-Methods': 'GET, POST',
'Access-Control-Allow-Headers': 'Content-type, Accept'
}
but the server still rejects requests. For now im simply not specifying Cache-Control in the HTTP client's headers, any idea how I could fix this issue?
Here is a test script you can use to duplicate the issue,
import axios from 'axios'
import { cacheAdapterEnhancer } from 'axios-extensions'
const api = axios.create({
baseURL: 'https://api.scryfall.com/',
headers: {'Cache-Control': 'no-cache'},
adapter: cacheAdapterEnhancer(axios.defaults.adapter, true)
})
export async function searchCards(query) {
return new Promise((resolve, reject) => {
api.get(`cards/search?q=${query}&pretty=true`).then((res) => {
resolve(res.data.data)
}).catch((e) => {
reject(e)
})
})
}
let cards
try {
cards = await searchCards('wizard')
console.log(cards)
} catch(e) {
console.log(e)
}
I am having 2 chache
Is there any way by which I can check if data is available in gribCache. I dont want to show loader if cache is available in gribCache. To do that I have to make sure that cache is present in memory and dont show loader as it will be fast.
const defaultCache = new LRUCache({ maxAge: AppConst.httpCacheAge });
const gribCache = new LRUCache({ maxAge: AppConst.gribCacheAge });
const httpClient = axios.create({
adapter:cacheAdapterEnhancer(axios.defaults.adapter, true, "cache", defaultCache)
});
return httpClient.get(url, {
cache: customCache
? gribCache
: true,
timeout: (customCache === AppConst.apiCaches.gribCache
? AppConst.httpRequestTimeoutsGrib
: AppConst.httpRequestTimeouts)
}).then(function (response) {
if (response) {
return response.data;
}
}, function (err) {
return null;
});
Hey,
If I am working with a site that is using the pre-built axios
(on the window
) is it possible to use this?
I notice this is no dist/
directory or the likes so thought I would ask here before I start digging.
Cheers.
When using throttle &/or cache it would be beneficial to have a
fast return Cache, but update (if possible) Cache in background as a post event.
so instead of (pseudo code):
.get('my/url')
->respond with getFromCache('my/url')
do this:
.get('my/url')
->respond with getFromCache('my/url')
send('my/url')
->updateCache with result from my/url request
When using a cache like function, the get method if a promise returns null and no and the error in the axios adapter function.
example: this example i use sessionStorage but i use localforage in production
export default {
async get (index) {
console.log("get", index);
const v = sessionStorage.getItem(index);
return v;
},
async set (index, value) {
const v = await value;
console.log("set", index, v);
sessionStorage.setItem(index, JSON.parse(JSON.stringify(v)));
},
del (index) {
console.log("del", index);
sessionStorage.removeItem(index);
},
};
Hi, if you have 2 components and one of them is doing some modification on retrieved data, modification is reflected in other component.
Repository field is pretty useful, it allows to open github page right from npm page.
Also it helpful for people who wants to contribute.
npm WARN [email protected] No repository field.
npm WARN [email protected] No license field.
axios.ts file
const axiosAppInstance = axios.create({
// @ts-ignore
adapter: cacheAdapterEnhancer(axios.defaults.adapter, { enabledByDefault: false, cacheFlag: 'useCache'}),
baseURL: appConfig.getRequired('abc'),
});
abc.ts file
axiosAppInstance.get('xyz',{ useCache: true }),
TypeScript error
Argument of type '{ useCache: boolean; }' is not assignable to parameter of type 'AxiosRequestConfig'.
Object literal may only specify known properties, and 'useCache' does not exist in type 'AxiosRequestConfig'.ts
It might be better to pass arguments to adapters as an object.
I mean, instead of:
cacheAdapterEnhancer(adapter, false, 'cache', new LRUCache({ maxAge: FIVE_MINUTES }))
use
const options = { cacheEnabledByDefault: false, enableCacheFlag: 'cache', defaultCache: new LRUCache({ maxAge: FIVE_MINUTES }) };
cacheAdapterEnhancer(adapter, options)
1) easier to use default values of given params
For example, currently if I want to pass only custom cache I have to define all other arguments as undefined
. And it looks weird:
const cache = new LRUCache({ maxAge: 1000 * 60 * 10 })
cacheAdapterEnhancer(axios.defaults.adapter, undefined, undefined, cache);
// compare with
cacheAdapterEnhancer(axios.defaults.adapter, { defaultCache: cache });
2) more explicit (self-descriptive) arguments
cacheAdapterEnhancer(axios.defaults.adapter, false); // <-- what's false here?
// compare with
cacheAdapterEnhancer(axios.defaults.adapter, { cacheEnabledByDefault: false } );
It's hard to recognize what is false
without checking the docs. It is not good developer experience
What do you think about that @kuitos ?
This is a simple, useful extension that I would love to use. However, it is not very browser friendly for these reasons:
isEmpty
method, which only needs to validate simple object
types.url
and querystring
for other similarly simple tasks.I propose that all of the above functions be replaced with lightweight, browser friendly alternatives.
Is it currently possible to use this library as a persistent cache ? Meaning that, after restarting the application, the cache can be loaded into memory and directly reused ?
I would need something like this in my current node project .
An idea for an enhancement to throttle or cache adapters as a flag usePendingRequest
.
If there is a pending [similar] request, then the call returns a promise which will resolve the same as the current request.
An example of this need can be in a framework like React or Angular where a UserName component is mounted in 2 separate DOM locations and they both make the request to fetch user data. If they both mount within milliseconds, neither will see a cached request and start a new request. This feature would reduce simultaneous "like" requests in this situation by returning a promise for the first request made.
I'm happy to contribute this effort, please advise :-)
Hi, I would like to cache OPTIONS calls along with GET requests. Is it possible?
If I understand correctly, currently the cache is only enabled if method === 'get'
. However I think it's a common use case to cache OPTIONS requests as well.
The included LRUCache uses const's, which fails when I try to process it trough Uglify - could we use a version that's properly transpiled?
Unexpected token: keyword (const) [./node_modules/axios-extensions/node_modules/lru-cache/index.js:4,0][main.legacy.js:7204,0]
Details:
domain: [object Object]
domainThrown: true
Having troubles with setting up caching with axios-extensions:
main.ts
import axios from 'axios';
import { cacheAdapterEnhancer } from 'axios-extensions';
axios.defaults.baseURL = process.env.VUE_APP_API_BASE_URL;
axios.defaults.params = {
client_id: process.env.VUE_APP_CLIENT_ID,
client_secret: process.env.VUE_APP_CLIENT_SECRET
};
axios.defaults.adapter = cacheAdapterEnhancer(axios.defaults.adapter);
Last line throws
Argument of type 'AxiosAdapter | undefined' is not assignable to parameter of type 'AxiosAdapter'.
Type 'undefined' is not assignable to type 'AxiosAdapter'.ts(2345)
And
Service.ts
import axios from 'axios';
import { IResponse } from './models/ResponseModel';
class Foo {
private ACTION_URL: string = '/api/action';
public async Bar(query: string): Promise<IResponse> {
return await axios
.get<IResponse>(this.ACTION_URL, {
cache: false,
})
.then((response) => {
return response.data;
})
.catch((error: any) => {
return error;
});
}
}
Throws
Argument of type '{ cache: boolean; }' is not assignable to parameter of type 'AxiosRequestConfig'.
Object literal may only specify known properties, and 'cache' does not exist in type 'AxiosRequestConfig'
I am getting Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin.
even though I have access-control-allow-origin *
present in the response.
It is working fine without axios-extensions
I'm using request options to force axios-extensions to update cache entries when needed.
This has been working fine with axios 0.18.0 (and 0.18.1) but when I recently updated to 0.19.0 I noticed that forceUpdate does not have effect anymore. Data is still returned from cache.
package.json:
"axios": "^0.19.0",
"axios-extensions": "^3.0.6",
Code:
const requestOptions = {
params: {
_format: 'json',
},
forceUpdate: cacheIsBypassed,
};
const {data} = await cachedAxios.get(requestUrl, requestOptions);
For now the resolution was reverting back to 0.18.1 but for going forward it would be good to make axios-extensions to work fully with axios 0.19 too.
Is there a way to set different ttl's for individual requests?
Seems like setting one maxAge for all items in the cache isn't ideal.
It would be nice if there was a way to do something like the following:
Thoughts?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.