stampit-org / stamp Goto Github PK
View Code? Open in Web Editor NEWStamps - better OOP model
Home Page: https://stampit.js.org
License: MIT License
Stamps - better OOP model
Home Page: https://stampit.js.org
License: MIT License
fp-constructor is implemented wrong. Probably my fault.
.constructor
should be an instance method (on the instance's delegate prototype), not a static property. It should refer to the specific stamp which created the instance. (See type representatives).of()
should be a static prop (on the stamp). It should also refer to the stamp. (See .of() method)In other words, it should create the stamp equivalent to the following factory:
const createUser = ({
userName = 'Anonymous',
avatar = 'anon.png'
} = {}) => Object.assign(
Object.create({
constructor: createUser
}), {
userName,
avatar
}
);
createUser.of = createUser;
// testing .of and .constructor:
const empty = ({ constructor } = {}) => constructor.of ?
constructor.of() :
undefined
;
const foo = createUser({ userName: 'Empty', avatar: 'me.png' });
console.log(
empty(foo), // { avatar: "anon.png", userName: "Anonymous" }
foo.constructor === createUser.of, // true
createUser.of === createUser // true
);
.constructor
and .of()
can be used as a tool to create Functors and Monads, to derive operations for algebraic datatypes, etc...
Incidentally, .constructor
and .of()
go hand-in-hand. If you want one, you almost certainly want both.
Current implementation of @stamp/core/mergeOne()
does not fully check if its first parameter dst
is undefined
. If the second parameter src
is an object, dst
is never checked and assumed to be an array/object and thus a TypeError: Cannot read property 'foo' of undefined
is thrown.
stamp/packages/collision/index.js
line 33This issue is triggered by unit tests and standardiseDescriptor()
(in packages/it/index.js
lines:41, 47 and 56
This issue relates with #52
Greetings, stamp people.
I've been messing around with stamps for a relatively small project and it has been a fantastic development experience.
That said, I'm interested on how to implement a different behavior for method collision.
As far as I understand, collision defer
calls the colliding methods sequentially. What I would like to be able to accomplish is to pipe the return value of each colliding method to the next.
For example, if I wish to have a 'stamp-wide' toJSON()
serializer, I would like each of the composing stamps to have a toJSON()
method. If the toJSON()
methods were expecting a previous value, that would considerably simplify the complexity of such a feature.
Something like:
const Stamp1 = stampit({
props: {
name: "stamp1"
},
methods: {
toJSON(previous) {
const _json = { name: this.name };
return !previous ? _json : {...previous, _json};
}
});
Does this make sense? Is there an already established way to accomplish something like this?
Thanks in advance.
In article Fun with Stamps. Episode 20. Stampit v4 it is mentioned:
stampit
is mainly for browsers. It has both node and browser builds. Whereas@stamp/it
is node CommonJS only.
This is mentioned nowhere in any of the README.md
of the @stamp
monorepo.
Furthermore, current package.json
requires a node engine >= 6.4.0
and .travis.yml
setup test against node version 6, 7, 8, 10 and 12, but NodeJS Releases page no longer lists version 6 and 7.
By defining the minimal supported NodeJS version, we define the proper ES version to expect and can clean/simplify/harden the code base in several places (get rid of __proto__
, rely on Symbol
, Reflect
and Proxy
primitives, spread and rest operators, etc.)
NodeJS version | Supported ES version |
---|---|
6.4.0 | ES2015 |
7.5.0 | ES2016 |
8.6.0 | ES2016 (and parts of ES217) |
9.11.2 | ES217 |
10.3.0 | ES2018 |
Also, since odd numbered releases of NodeJS are not considered long-lived, .travis.yml
might be better limited to testing only even numbered version plus:
>= 7.5.0
in package.json
)Branch | Build failing π¨ |
---|---|
Dependency | lerna |
Current Version | 2.0.0-beta.37 |
Type | devDependency |
This version is covered by your current version range and after updating it in your project the build failed.
As lerna is βonlyβ a devDependency of this project it might not break production or downstream projects, but βonlyβ your build or test tools β preventing new deploys or publishes.
I recommend you give this issue a high priority. Iβm sure you can resolve this πͺ
π¦ π Initial Yarn support and more!
We'll make yarn the default once we feel that it's more stable.
$ lerna bootstrap --npm-client=yarn
{ "npmClient": "yarn" }
This will run yarn
in each package can create temp yarn.lock
files while developing so you'll want to .gitignore/.npmignore
these files. Ex: .gitignore
: packages/*/yarn.lock
Very important fix for Babel that we used in the last release. This prevents a timing issue when publishing where a module will try to download a package that isn't published yet because it is published before it's own dependency is published itself. We used to get many issues from users on non-public npm about "babel-types" not being found.
Useful to get output for child processes immediately if using lerna run
with a watch command
$ lerna run watch --stream
#620 Feature: skip-temp-tag. (@noherczeg)
This will not create a temporary dist-tag called lerna-temp
when publishing. Useful if your third party proxy doesn't support dist-tags.
$ lerna publish --skip-temp-tag
Defaults to running npm run test
and npm run env
{ "commands": { "bootstrap": { "includeFilteredDependencies": true } } }
{ "commands": { "run": { "sort": false } } }
$ lerna publish --registry https://my-private-registry
lerna diff
. (@evocateur)sync-exec
(@wtgtybhertgeghgtwtg)The new version differs by 18 commits .
7202ecc
2.0.0-beta.38
163e22b
Add support for pluggable npm clients (#605)
62a8f2a
Support --skip-temp-tag when publishing (#620)
2f3b195
Add a --stream option to the run command (#608)
3ace547
Add versions to lerna ls. Fixes #603. (#624)
b595884
On import, infer targetDir base from packages glob list (#480)
d246667
lerna publish
checks for changes since most recent tag in the current branch (#475)
744d036
README grammar: it's β its (#609) [skip ci]
5a4039a
Always run test and env scripts (#587)
d7c1fe9
Durable includeFilteredDependencies
config via lerna.json (#598)
3021dc3
Support sort
option in lerna.json (#596)
2e67ed2
Fix midair collision (#604)
6b5fb91
Explicit registry flag feature (#599)
8790656
Publish npm packages in topological order. (#595)
925c0a1
Fix --ignore flag when globs are expanded to an array (#601)
There are 18 commits in total. See the full diff.
There is a collection of frequently asked questions and of course you may always ask my humans.
Your Greenkeeper Bot π΄
In the stampit API, under Static property descriptors, the examples shows:
let MyStamp = stampit() // empty stamp creates empty objects
MyStamp.name === 'Stamp' // every stamp default name is "Stamp"
MyStamp = MyStamp.staticDeepProperties({
name: { value: 'MyStamp' } // this meta data will be applied just after composition, i.e. immediately
})
MyStamp.name === 'MyStamp'
I think it should be
let MyStamp = stampit() // empty stamp creates empty objects
MyStamp.name === 'Stamp' // every stamp default name is "Stamp"
MyStamp = MyStamp.staticPropertyDescriptors({
name: { value: 'MyStamp' } // this meta data will be applied just after composition, i.e. immediately
})
MyStamp.name === 'MyStamp'
The same goes for the examples under Other ways to add static property descriptors
Project currently relies on jshint
for linting. eslint
seems to offer more rules, configuration options, and support for ESnext (and TypeScript)
Integration within editor (at least with Visual Studio Code) is also superior with better identification of which rule triggers, optional 'auto fix' options.
Last, eslint
integrates with Prettier
for a single "fix lint errors and format code" action.
@stamp/XX
packages changed from my version 0.2.5 to latest 1.0.1, but I can't find what changed or where to look for this. Checked for CHANGLELOG.md
in this repo and the Github release section.
I am really curious what could be done about this to make the whole experience less painful. I am still rather a rookie user of the Flow so it's possible I am just missing some obvious path. Let's consider some very basic stamp like this.
const SomeStamp = stampit.compose({
methods: {
usefulMethod(input: string): string {
return input.toLowercase()
}
}
})
It would be really lovely to be able to extract type definition out of this so I can do the following and know that passed object is really an instance of that stamp and I can safely use its method without checking manually.
function(proxy: SomeStamp) {
proxy.usefulMethod('BOOM')
}
Right now to achieve something like that I have to essentially duplicate all signatures to a separate interface type.
interface SomeStamp {
usefulMethod(input: string): string
}
However, that's rather tedious and not that useful in the end. I am aware that C++ has header files that are kinda similar, but there is at least a compiler that can scream when something is out of sync.
Any ideas what about can be done about this? I am certain it would bring stamps to another level of existence. It gets even more powerful with composing a multiple number of small stamps together.
I've made a few stamps where I inject other stamps as properties using .props({...}) for dependency injection purposes (testing/mocking).
I'd like to use init-properties stamp to automatically initialize some props that are stamps and have their arguments automatically namespaced (π ), but in some cases in my code, I am injecting stamps as .props() where those stamps are not intended to be initialized during initialization of the parent stamp. e.g.
const MyStamp = require('./MyStamp');
const asyncStampFactory = stampit
.init(function(fetchRemoteStampConfiguration, numStamps) {
this.fetchRemoteStampConfiguration = fetchRemoteStampConfiguration;
this.numStamps = numStamps;
})
.props({ myStamp })
.methods({
makeSomestamps() {
const stamps = [];
for(var x = 0; x < numStamps x += 1) {
const stampConfig = await this.fetchRemoteStampConfiguration(x)
stamps[x] = this.myStamp(stampConfig);
}
return stamps;
}
});
return asyncStampFactory;
Note that I won't be making a single stamp from the myStamp prop, but an array of them -- and I can't create the array of stamps on stamp initialization as the point of this stamp is to do some async fetching and return instances of the myStamp per result that was fetched.
init-properties seems to conflict with this.
I'm wondering if there might be a justification here for being able to configure the init-property stamp not to automatically initialize ALL stamp properties, but only some?
My instinct was to move myStamp to .configuration() and then use this.config.myStamp() in the loop to create instances instead, and only put stamps in .props() that should be auto initialized with values that are available during stamp initialization.
Would appreciate any insights :)
Current implementation of @stamp/core/assign()
handles (only) two parameters but several instances in the code base invoke it with three parameters:
stamp/packages/collision/index.js
line 71stamp/packages/it/index.js
lines 38, 44 and 53This issue relates with #52
I'm not sure it is a bug, but actually I don't know how to workaround this issue.
I've the following code:
const stampit = require('@stamp/it')
const {argOverProp} = require('@stamp/arg-over-prop')
const Course = stampit()
.compose(argOverProp({
_id: null,
}))
console.log(
Course() // works
)
console.log(
Course(undefined) // works
)
console.log(
Course(null) // throws Error
)
TypeError: Cannot read property '_id' of null
at Object.initializer (node_modules/@stamp/arg-over-prop/index.js:12:51)
at Stamp (node_modules/@stamp/compose/index.js:34:41)
at Suite.Object.<anonymous>.describe (lib/test_argoverprop/aop.test.js:29:3)
at Object.<anonymous> (lib/test_argoverprop/aop.test.js:27:1)
Any help is welcome.
Howdy,
I'm running into some problems with stubbing methods on stamps that have been privatized. In particular, because referencing the injected method always references the PROXY function, there's no way to access the stubs properties. It'd be nice to be able to reference the stubs on their location since they are not private-ized and it's counter intuitive to not be able to access properties on the public methods. Not sure the best way to go about achieving that goal. For now I am storing my stubs in a local closure, then referencing them from their local closure variables. It'd be nicer to be able to reference the functions directly on the object. Open to any alternative composition patterns too, if there's a better way to accomplish this.
const Configure = require('@stamp/configure');
const stampit = require('@stamp/it');
const sinon = require('sinon');
const exampleStamp = stampit.compose(Configure)
.methods({ myMethod() { return true; } });
const exampleStampInstance = exampleStamp();
const exampleMethodResult = exampleStampInstance.myMethod();
console.log(exampleMethodResult); // true
const myStub = sinon.fake.returns(false);
const stubbedStamp = exampleStamp.methods({ myMethod: myStub });
const stubbedStampInstance = stubbedStamp();
const stubbedMethodResult = stubbedStampInstance.myMethod();
console.log(stubbedMethodResult); // false -- fake was called as expected (good!)
console.log('myStub.called', myStub.called); // true -- the stub logged the call
console.log('stubbedStampInstance.myMethod.called', stubbedStampInstance.myMethod.called); // undefined (should be true, not intuitive at all)
Current implementation of @stamp/core/mergeOne()
when hitting an src
property with the value undefined
simply return
s (thus returning a final merged result of undefined
. Code should be altered so that it continue
s the loop and compute the correct merged result.
stamp/packages/collision/index.js
line 30This issue relates with #52
At the moment there is no way to use collisionProtectAnyMethod
as default and then allow a method to be overridden as special case. For me it makes more sense to encourage collision free stamps, but be able to use defer or replace a method when it makes sense.
I have a stamp with a method that uses get
style function:
methods: {
/**
* @returns {string}
*/
get label() {
return this.state.label.target
},
...
I also have a few methods that I am trying to set up for defer
style collision so that they are all called (instead of the last one).
It appears that when I use these two features together, I get an error that this.state
is undefined. Using console.trace I am lead believe that this line of code is "calling" my getter:
https://github.com/stampit-org/stamp/blob/master/packages/collision/index.js#L146
Should the npm package be deprecated?
If yes, the following files should include the reason why it is deprecated.
packages/named/index.js
packages/named/README.md
Line 100 in 90558ea
I am seeing some differences in results when implementing stamps using these packages. This is causing me some confusion and even concern about the "right way" to do things.
It appears that the behaviour of props
differs between these packages when creating a new instance of a stamp which has accessors in props.
I'll provide an example in code:
// import stampit from '@stamp/it' // works
import stampit from 'stampit' // fails
// storing the value in global scope is bad - but that's not the point
let storedValueOne: string | undefined
const HasStringAccessorOne = stampit({
props: {
get one(): string | undefined {
return storedValueOne
},
set one(value: string | undefined) {
if (!['undefined', 'string'].includes(typeof value)) return
storedValueOne = value
},
},
init(opts) {
this.one = opts.one
},
})
const HasStringAccessorTwo = stampit({
props: {
two: undefined as string | undefined
},
init(opts) {
this.two = opts.two
},
})
const TestAccessors = stampit(
HasStringAccessorOne,
HasStringAccessorTwo,
{ name: 'TestAccessors' },
)
const instance1 = TestAccessors({ one: 'mango', two: 'apple'})
console.log('=== instance1 instantiated ===')
console.log()
// check instance1
console.log('instance1.one (accessor)')
console.log(' - expected: mango')
console.log(` - got: ${instance1.one}`)
console.log(` - result: ${instance1.one === 'mango' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
console.log('instance1.two (value)')
console.log(' - expected: apple')
console.log(` - got: ${instance1.two}`)
console.log(` - result: ${instance1.two === 'apple' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
const instance2 = TestAccessors({ one: 'banana', two: 'pear'})
console.log('=== instance2 instantiated ===')
console.log()
// check instance2
console.log('instance2.one (accessor)')
console.log(' - expected: banana')
console.log(` - got: ${instance2.one}`)
console.log(` - result: ${instance2.one === 'banana' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
console.log('instance2.two (value)')
console.log(' - expected: pear')
console.log(` - got: ${instance2.two}`)
console.log(` - result: ${instance2.two === 'pear' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
// check instance1 again
console.log('instance1.one (accessor)')
console.log(' - expected: mango')
console.log(` - got: ${instance1.one}`)
console.log(` - result: ${instance1.one === 'mango' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
console.log('instance1.two (value)')
console.log(' - expected: apple')
console.log(` - got: ${instance1.two}`)
console.log(` - result: ${instance1.two === 'apple' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
Result when using import stampit from '@stamp/it
:
=== instance1 instantiated ===
instance1.one (accessor)
- expected: mango
- got: mango
- result: π’ pass
instance1.two (value)
- expected: apple
- got: apple
- result: π’ pass
=== instance2 instantiated ===
instance2.one (accessor)
- expected: banana
- got: banana
- result: π’ pass
instance2.two (value)
- expected: pear
- got: pear
- result: π’ pass
instance1.one (accessor)
- expected: mango
- got: mango
- result: π’ pass
instance1.two (value)
- expected: apple
- got: apple
- result: π’ pass
Result when using import stampit from 'stampit'
:
=== instance1 instantiated ===
instance1.one (accessor)
- expected: mango
- got: mango
- result: π’ pass
instance1.two (value)
- expected: apple
- got: apple
- result: π’ pass
=== instance2 instantiated ===
instance2.one (accessor)
- expected: banana
- got: banana
- result: π’ pass
instance2.two (value)
- expected: pear
- got: pear
- result: π’ pass
instance1.one (accessor)
- expected: mango
- got: banana
- result: π΄ fail
instance1.two (value)
- expected: apple
- got: apple
- result: π’ pass
So it seems that stampit
package deals with merging of accessors on props in a different way to @stamp/it
, and I'm not sure which is correct. My expectation (as per the code above) would be that they should behave like standard props and not be copied into the new instance.
Note: you can ignore that I placed let storedValueOne
in the global scope. This behaviour persists when the accessor stamp is built using a function. The only way to work around this behaviour is to place the stored value in another prop like _one
, or by using a weak map, but the point here is the divergence in behaviour between the packages.
Here is some code using a "stamp builder" which encloses the stored value, which still fails when using stampit
package:
import stampit from 'stampit'
const hasStringAccessor = (name: string) => {
let storedValue: string | undefined
return stampit({
props: {
get [name](): string | undefined {
return storedValue
},
set [name](value: string | undefined) {
if (!['undefined', 'string'].includes(typeof value)) return
storedValue = value
},
},
init(opts) {
this[name] = opts[name]
},
})
}
const TestAccessors = stampit(
hasStringAccessor('one'),
{ name: 'TestAccessors' },
)
const instance1 = TestAccessors({ one: 'mango'})
console.log('=== instance1 instantiated ===')
console.log()
// check instance1
console.log('instance1.one (accessor)')
console.log(' - expected: mango')
console.log(` - got: ${instance1.one}`)
console.log(` - result: ${instance1.one === 'mango' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
const instance2 = TestAccessors({ one: 'banana'})
console.log('=== instance2 instantiated ===')
console.log()
// check instance2
console.log('instance2.one (accessor)')
console.log(' - expected: banana')
console.log(` - got: ${instance2.one}`)
console.log(` - result: ${instance2.one === 'banana' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
// check instance1 again
console.log('instance1.one (accessor)')
console.log(' - expected: mango')
console.log(` - got: ${instance1.one}`)
console.log(` - result: ${instance1.one === 'mango' ? 'π’ pass' : 'π΄ fail'}`)
console.log()
Branch | Build failing π¨ |
---|---|
Dependency | lerna |
Current Version | 2.0.0-beta.34 |
Type | devDependency |
This version is covered by your current version range and after updating it in your project the build failed.
As lerna is βonlyβ a devDependency of this project it might not break production or downstream projects, but βonlyβ your build or test tools β preventing new deploys or publishes.
I recommend you give this issue a high priority. Iβm sure you can resolve this πͺ
The new version differs by 48 commits .
e3fc0e9
2.0.0-beta.35
05ceaaa
fix version [skip ci]
b35c05f
update changelog [skip ci] (#561)
77b77fa
Bump cross-env
. (#554)
560348c
Updating README: clean
respects --include-filtered-dependencies (#558) [skip ci]
5996375
redo labels [skip ci] (#560)
4b6f0aa
Drop isarray
. (#559)
bcaf1aa
Fix broken hoisting tests (#557)
6e9bf13
Merge pull request #507 from gigabo/hoist
0b016de
Spawn child process for rimraf (#547)
bc93b96
Bump signal-exit
. (#549)
84b81ab
Use real Object.assign now that we're off of 0.10/0.12
3a61c69
Don't prune the repo root when hoisting (#67)
4eaf88e
Kill some duplicate methods
f7b8b3e
Automatic hoisting of common dependencies to repo root (#36)
There are 48 commits in total. See the full diff.
There is a collection of frequently asked questions and of course you may always ask my humans.
Your Greenkeeper Bot π΄
Hi,
We encountered a conflict using @stamp/it library with @stamp/arg-over-prop, the latest version of @stamp/is currently 1.0.0 should be installed, but version 0.1.2 is installed instead.
Now that this is no longer RFC I've moved this issue from #356 in stampit to here.
The new types will differentiate a generic Stamp
from a Stamp
that has a defined descriptor as you see in the screenshot. I've not been able to decide on a name for that differentiating type. I could do with some opinions. Here is my short list in no particular order:
ComposedStamp
InferableStamp
DefinedStamp
Tasks
In order to offer users proper Typescript support, two options are available:
.d.ts
files for each published package.js
source code to Typescript (with incremental build and automatic generation of source maps and .d.ts
filesFirst option might be faster but implies to maintain .js
source and .d.ts
in sync as code base evolve.
Second option would take longer to achieve but should be easier to maintain in the long run. Caveats is that source code no longer is JavaScript but Typescript. Final code being transpiled (using Typescript or Babel) thus making fine control of the code (slightly) less possible. On the other hand, source Typescript code can benefit from the latest ESnext syntax while still producing JavaScript code compatible with the minimal supported version(es5, es2015, etc.)
Hello, consider this code:
const stampit = require('@stamp/it');
const Operations = stampit({
props: { operations: [] }
});
// the test
it('stampit does not clone array props', () => {
const ops = new Operations();
ops.operations.push('op 1');
expect(ops.operations.length).toBe(1);
const ops2 = new Operations();
// this test fails, we receive ['op 1'] instead of []
expect(ops2.operations).toEqual([]);
});
As you see in the code example, Stampit didn't clone the array when the second instance was created.
Instead, it shared the reference from the first instance.
This is an unusual behaviour that should be fixed in my opinions.
PS:
This little hack greens the test above:
const Operations = stampit({
init() { this.operations = [] }
});
rerun the unit test above, now it passes.
I try to use the new stamp/it module which works got so far, but I found another problem with collision detection :(
DefaultStamp, used for every new stamp which doesn't compose with another already existing stamp:
import stampit from '@stamp/it'
import compose from '@stamp/compose'
import Collision from '@stamp/collision';
let ProtectMethods = Collision.collisionProtectAnyMethod();
let DefaultStamp = compose(stampit(), ProtectMethods)
export default DefaultStamp;
test
test('using the same stamp in other stamps is ok', t => {
let Named = DefaultStamp.deepProps({
name:undefined
}).methods({
getName() {
return this.name;
},
})
let base = Named.deepProps({
name:'base'
}).create();
t.truthy(base.getName() === 'base')
let Robot = Named.methods({
getRobotLaws() {
return 'be nice'
}
})
let Animal = Named.methods({
getAnimalLaws() {
return 'king of jungle'
}
})
let Robotimal = Robot.compose(Animal);
})
which fails with:
DefaultStamp βΊ using the same stamp in other stamps is ok
Error: Collision of method `getName` is forbidden
module.exports.compose.composers (node_modules/@stamp/collision/index.js:128:19)
Function.compose (node_modules/@stamp/compose/index.js:153:27)
Function.stampit (node_modules/@stamp/it/index.js:83:18)
Function._compose [as compose] (node_modules/@stamp/compose/index.js:66:34)
Test.t [as fn] (src/services/DefaultStamp.test.js:83:27)
I see why it happens, in the last line I use two stamps which both have getName
. I can solve this using Named
stamp in Robotimal
and not the other stamps.
My Problem here is that at the moment when I write Robot
and not thinking about Robotimal
I need to use Named
so Robot
is fully functional. Months later when I realize I need a Robotimal
I need to refactor all stamps I use for the new stamp.
Is it possible to identity check the stamp getName
came from and see that it is the same stamp (Named
) and don't declare it as collision? Because it doesn't override the existing behavior at all.
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.