wasp-lang / wasp Goto Github PK
View Code? Open in Web Editor NEWThe fastest way to develop full-stack web apps with React & Node.js.
Home Page: https://wasp-lang.dev
License: MIT License
The fastest way to develop full-stack web apps with React & Node.js.
Home Page: https://wasp-lang.dev
License: MIT License
Right now, all the FileDrafts that Generator returns are written to the disk in wasp/src/Generator.hs, writeFileDrafts.
However, it is likely that many of these files are already present on the disk with the same contents!
So how do we make sure that we write only the files that are missing or need to be updated, while also removing any files that are not part of the generated project any more?
Why optimize this -> it reduces time needed to generate the project (might be important for bigger projects). It also means that nodemon
and CRA's watch will trigger less times.
Idea is that we could write down, in a file somewhere in .wasp/ dir, list of all the files that Generator says should be on the disk + their checksums. Then, each time we can check the diff between that and the current list of FileDrafts that we want to write, and decide which files need to be written and which need to be deleted. Finally, we would update that list.
I am not sure though if checksum is enough to check that two files are the same? If it is hash then there is a small chance of them being different although checksum is the same -> I am not sure how to go about that.
Right now, errors are coming directly from client/server, and often it is not easy to connect them with the Wasp source code, since generated code should be opaque to the user, but in this sense it is not.
We need to find a way to give errors more meaning, connect them to the Wasp code (both .wasp and ext/ code).
This will not be an easy task, since it is questionable how much control we have over them, how can we intercept them and how much do we understand what they are about.
To do this, we need to somehow intercept errors and then attach more information to them. We can choose to display both new, attached information and old information, or just new information.
How can we intercept them?
One way would be to intercept them on stdout of client and server processes. This does not seem great, since we depend a lot on how they are being outputed, formatted, and so on, there is a lot of text processing here that does not conform to any strict standard and could easily be broken.
Another way, better way, would be intercept them programmatically, where we have more control and hopefully there are more conventions as to how they are organized and formatted. We could inject top-lvl error handlers in generated code that would do this transformation. Or, we could monkey-patch them into the generated code. There are still certain errors we will not be able to catch this way, like errors coming from tools like standardjs -> basically static analysis tools / linters.
Ho do we transform/enrich errors?
We should probably use some kind of source mapping, so if error happens at certain place in generated code, we can connect it to the source code. I am not sure what will this take, but it should be possible.
I think finding a solution that will 100% catch all errors in a uniform way will not be possible. Instead, it probably makes most sense to focus on most prominent / easiest to tackle errors, and then we build from there, expanding the solution to capture more and more errors as we encounter them, organically.
Obviously I merely scratched the surface of the problem, so more though is required here for sure, and hopefully we might even come up with smarter approach. We should also research how others are doing this.
Since Docusaurus is a complete solution for documentation (website + docs + blog) we should start moving everything there. A great thing is also that we get React for free, so we can finally simplify our website's code.
At the end when everything is moved over we should also delete website/ and blog/ repos, and also make sure web is hosted on wasp-lang.dev domain.
Flux is concept that we are most likely going to build in into the language itself, and we should make sure we have good grasp of it. While I already know the basics from practice, I want to read more resources about it to make sure I am not missing on anything.
I should start from here: https://facebook.github.io/flux/ -> there is a nice video also.
In this specific case the problem is with Vim temp files (e.g. someFile.js~) - Upon some file change, filewatch triggers recompiling which registers the temp file but by the time it tries to copy it is already gone - thus wasp tries to copy an inexistent file which crashes wasp.
Possible solutions:
Approach 2) is probably easy enough to do and a good foundation for the future (.wasp/gitignore).
Once new project is generated, new Wasper might feel stranded -> what next?
Good idea might be to immediately provide them with directions.
We could do that by outputing them as a result of running wasp new <project-name>
.
We could:
wasp start
.Right now, Wasp enables either writing Wasp code or importing custom js code. However, there is no mechanism to manage npm packages and therefore include any third-party code.
We want to allow user to install and then use any npm package. For example, if in snippet of js code, written by user as part of their .wasp, needs to use lodash, user can specify lodash as one of packages and then import it and use it.
This should be mostly similar to how it is done in package.json (or should we actually use special package.json for this?), so that they just list the packages and versions and we take care of the rest. Maybe similar to how Stack (for Haskell) takes care of this and generates Cabal files?
One important thing to take into consideration is that Wasp already uses a bunch of packages in the generated code: react, redux, react-redux, lodash, ... . So, what if user wants to add lodash again, for some reason (e.g. they don't know it is already used)? What if it is different version than what Wasp is using? Do we tell them "Wasp already brings lodash, you can't do this, but you can import one that Wasp provides, and that one is version x.y.z"? Or do we somehow install both versions, and then provide user with their version when they need it, while Wasp uses its own version?
There are different approaches to explore here, it is not straightforward and it requires some careful consideration.
To reproduce: do some change in Wasp app, Wasp will recompile and generate new files, which will trigger nodemon and CRA, and when they are done browser tab with Wasp app will go blank.
It seems the cause of this is that when someting runs in parallel with CRA, CRA creates this empty tab -> probably because it finishes too soon and does not catch some of the changes? But actually it certainly does catch them because app is in correct state, it is just that browser tab got blank and needs reloading. In our case what is running in parallel is writing files -> CRA most likely triggers on very first file we write, but then we write a couple more files and those are problematic.
This problem has been described here actuallyi: facebook/create-react-app#5809 .
I am not sure how we can fix this -> one way would be to make sure CRA does not trigger until we are done with writing all the files, but we don't have that level of control over CRA. We could stop it and start it again, but that seems wasteful and possibly slow. Maybe, same as for nodemon, we can set a delay via CLI parameters? That could solve this problem for some time. Or we could somehow make sure to trigger it one more time, once we write last file?
It might actually help if we understand better what is exactly causing the problem -> maybe we can make a PR on CRA and solve it not only for us but also for others who are running stuff in parallel with CRA and having this problem.
We want to be able to specify button label for EntityForm submit button.
Probably most relevant piece of code to start from is here: https://github.com/wasp-lang/wasp/blob/master/waspc/src/Wasp/EntityForm.hs#L54 -> here it should be added as Submit button option.
There are really three parts to this change:
We should also remove, from generated code, option to modify this label, which is currently there -> it is not needed anymore if we do this.
While this issue requires changes to the whole system, I believe it is good "starter" issue as it can be mostly done by copying how it is done for other EntityForm options, is conceptually simple, all is clear regarding requirements, and it teaches you about the whole waspc.
We want deployment to be managed, as much as possible, by Wasp.
For now, since we have only frontend, we could go with smth simple, for example just deploying to gh-pages.
This is still relatively open and first we should figure out the options, before we start implementing anything.
I marked this as a bug, although we made it on purpose, just to convey the severity of this issue. Maybe we could also add "production" label, for things that need to be done in order to run Wasp apps in production.
Or smth like that, that will for now spin up prisma studio.
How to do it:
cli/
. Check out other commands there to get an idea, e.g. wasp db migrate-up
.prisma studio
. Keep in mind that in our case, prisma studio
has to be run from server/
, while schema is in db/
, so schema has to explicitly be defined when running prisma studio
with --schema ../db/schema.prisma
.If app has not auth
defined, server crashes.
This is due to import auth from './auth/index.js' in
index.js-> if there is no
auth`, this import fails. It should be conditional.
WSON = WaSp Object Notation :D.
Idea is to refactor Parser so that we don't have to write so much boilerplate code for each new piece of language.
Most declarations in Wasp look like smth smth configObject. This config object is something I think we could abstract, to avoid repetition. This is first step toward generalizing all of the declarations, in the future.
One approach could be to just define very reusable parsers, one of them being parser that takes list of keys, corresponding parsers for each of the keys, and then parses object with those keys and uses appropriate value parsers. So we would just combine parsers like this easily.
Second approach could be to define more general construct in Wasp, something called WSON (or some other name), which would be this type of JSON like object. We would have parser (recursive) for it, and that is it, we would reuse that. This parser would obviously be less strict, so we would need to do more validation afterwards (semantic analysis?), but that should be better for language design.
All these pose questions of: how do we map them to Wasp AST?
Relevant file: https://github.com/wasp-lang/wasp/blob/master/waspc/app/Main.hs .
Currently, we take two paths as arguments -> wasp file path and out directory path. We need them to be absolute to be able to work with them, so if they are not absolute, we just concat pwd in front of them. But, that does not work if they contain "../"! We would like to make it work in that case also.
Somehow that doesn't work, we should fix it.
Example of a command we run from CLI: wasp db migrate-save "Added user"
-> it will just list available db commands, I guess it doesn't match it to any command?
See code here: https://github.com/wasp-lang/wasp/blob/master/waspc/examples/todoApp/todoApp.wasp#L82
------ todoApp.wasp
action signUp {
fn: import { signUp } from "@ext/actions.js",
entities: [User]
}
------ ext/actions.js
export const signUp = async (args, context) => {
await createNewUser({ email: args.email, password: args.password })
}
As stated in the title, this is the case where we inject User to the action function but then it doesn't use it directly, because it uses createNewUser()
function provided by Wasp auth module. Wasper could have omitted entities: [User]
declaration from the action declaration and everything would compile, but the problem is that then Wasp wouldn't be aware that this action changes User entity (creates new one) so then it can't update the query caches accordingly.
So the way it is now Wasper has to remember to declare that the action is affecting certain entity although it doesn't use it directly - this is extra mental work for Wasper and could result in errors.
We should think what is the solution we want to achieve here. Maybe supporting composability of the actions would be a solution - e.g. if signUp
action uses createNewUser
(e.g. it could inject it as well) action which changes User
, then we can know that signUp
action also changes user, although it is not explicitly declared.
By giving high lvl overview, similar like they do it for React (https://reactjs.org/docs/codebase-overview.html), we can make it much easier for contributors to start contributing. We should explain basic stuff like Parser, Generator, AST/Wasp, FileDraft, CLI vs Library, ... .
This should probably go to README here or maybe special document.
https://github.com/wasp-lang/wasp/community -> here is a checklist of things to do to, they seem reasonable / useful so let's do them!
Whenever wasp start
is run, we run npm install
after compilation to make sure that all the npm packages are installed. This take care of cases like: npm packages were never installed (wasp start was run for the first time) or change in the code resulted with package being added/removed and it needs to be installed.
Why would we even want to avoid this? Well, because npm install
can be fairly slow, so not running it when not needed makes wasp start
much faster to start.
How could this be done? One idea is to check which packages are currently installed, and based on that figure out if anything needs to be updated or not. For example, after each "npm install", wasp could write down which packages were installed, in some file in .wasp/. Then, on the next wasp start
, after the compilation where new package.json has been generated, it could check if there are any new packages compared to those that it remembered. If there are any, run npm install
, otherwise, don't. I just wrote above that we Wasp could write down which packages were installed, but that sounds like duplicating what package.json already does -> it would probably be the easiest to instead just put aside current package.json before compilation, and then compare it with the newly generated package.json -> if there is any difference, run npm install
.
On the implementation side, this should be the responsibility of Generator. And if we wanted to make it agnostic of the way Generator generates code, we could do it like this: Generator, after it generated the project, returns data containing info about the current installation (it could contain whole package.json as a string). Wasp takes this data and stores it in a file in .wasp/ dir. Then, on the next run, it will read that data from a file .wasp/ dir and feed it to the Generator, which will use it to make decisions about the generation of code (will it run npm install or not and similar). This way we can keep the logic encapsulated in Generator while still storing information to the disk and delegating that storing to IO code outside of the Generator.
We should implement syntax highlighting for popular editors!
Right now, if Wasper wants to specify an npm dependency (via dependencies
declaration) that Wasp is already using in generated code (for example react
or react-query
), they will get an error message saying that Wasp is already using this package and that they have to specify exactly the same version as Wasp is using.
Next step is to allow Wasper to specify any version they like, and this should be implemented internally via npm aliases
, where wasp would use npm alias to refer to its version of package in different way than Wasper. So for example if Wasper declared "react": "^16.12.0"
but Wasp is using "react": "^15.0.0"
, then Wasp should change the name of its dependency via npm aliases to be smth like react-wasp
instead of just react
. It might still be smart to "warn" Wasper that they are choosing different version than Wasp and that they might want to instead use the same version, to avoid possibly redundant dependencies.
Implementation of this should probably start from Generator.PackageJsonGenerator
module.
Much of this is also discussed in possibly more details in issue #11 , where current implementation was designed and future implementations were discussed.
Not really a bug, but a deficiency that we are aware of and want to improve.
In the node server that Wasp is generating, we currently just set CORS with app.use(cors())
, which allows all CORS requests. This is not restrictive enough, it should allow only requests from the domain of the client I believe. Should we also configure something else? I am not actually what it takes to say that CORS is well configured, that is the first thing that we should investigate, and then we can make a plan for implementing that.
If you are reading this and have a good idea of how CORS should be configured, do share the thoughts!
https://www.npmjs.com/package/helmet -> sets some reasonable default HTTPS response headers.
Right now, we successfully hosted our todoApp example, generated in Wasp, at examples.wasp-lang.dev.
However, if we would want to host it at examples.wasp-lang.dev/todoApp (for example), we could not, because with current configuration it can only be served from the root directory of the domain.
We need to figure out what is needed to get this working, and add support for this in Wasp!
Right now, if auth
feature is used, all actions and queries have auth
middleware in front of them.
But, this is problematic in some situations, for example if you have signUp
action that calls createNewUser
, it will get rejected with 401 if JWT token is wrong. Which makes sense, but we actually want signUp
to succed in situation like this, because how are we going to fix that JWT token otherwise? So, what we really want is for signUp
to not require authentication, which makes sense, but, we don't have that right now as an option.
This should probably work smth like this:
action signUp {
fn: import { signUp } from "@ext/actions.js",
entities: [User],
auth: false
}
and in this case auth
middleware should not be applied on server on this action.
I am marking this as enhancement because that is what it is, but also as bug because it actually limits user severly in situation like this and manifests as kind of a bug.
Draft:
auth :: Bool
field.auth
property.auth
middleware on operations that have auth
specified as False
.Wasp already brings some npm dependencies with it, and we can't really ignore them, at least for now.
However, although affected by them, there is no easy way for Wasper to find out what they are (except for looking into .wasp/out/ dir).
What we could do is have command wasp deps list
that would list all of these Wasp dependencies together with their versions, and could also list dependencies added by Wasper via dependencies
declaration next to them while already listing them.
Draft:
wasp deps list
in cli/
.In waspc/data/Generator/templates/server/package.json, dev dependencies are hardcoded, while non-dev dependencies are not (they are instead inserted as depsChunk
. We want to achieve the same thing with dev dependencies: they should be inserted, not hardcoded.
Draft:
waspNpmDevDeps
list (take a look at waspNpmDeps
) that contains dev dependencies listed in package.json template.genPackageJson
, add devDepsChunk
to the Aeson object (next to depsChunk
). This part of code should look smth like this after the change: , "depsChunk" .= toPackageJsonDependenciesString (resolvedWaspDeps ++ resolvedUserDeps)
, "devDepsChunk" .= toPackageJsonDependenciesString waspNpmDevDeps
devDepsChunk
, same as it uses depsChunk
.That should be it! Confirm that everything works well by running TodoApp example
cd waspc/examples/todoApp
../../run wasp start
Example: auth middleware. For certain reasons, we might return 401. However, only thing we see in terminal is that 401 was returned. Not that it came from auth middleware, or why it happened. Why should both say where it happened (in auth middleware) and what caused it (for example, there was no such user in the database). This will make debugging much easier!
We should do this for all the functions that we generate, and also enable Waspers to use such mechanism in their code.
This is referring to the TodoApp example in wasp/waspc/examples. We should make it nicer, by extracting fuctional react components from inside other react components to components that are next to them.
When node server, running with npm start
(nodemon) crashes, it says
Server: [nodemon] app crashed - waiting for file changes before starting...
but it does not end/exit, waiting for something to change to try again.
Problem is, while nodemon does print the error message to terminal, we don't forward it for some reason (it should come just after the message above). This means user will see something is wrong but will not know what, making it very hard / impossible to recover from problem.
How to reproduce it: run wasp start
but delete .wasp/out/server/node_modules in the middle of it running, node server will say that app crashed. To see the error, don't run wasp start
but instead run npm start
from .wasp/out/server.
Question is, is node server sending the error message, and if yes, why are we not showing it? Maybe nodemon/node is not printing the error message because it knows it is not in terminal? Or it is but it is not neither stdout nor stderr but it is writing directly to /dev/tty (I read that is usually how output gets by stdout/stderr).
Wasp doesn't yet have native support for Windows, explained here: #47 (comment) .
But, you can use it nicely via WSL (Windows Subsystem for Linux)!
Once you set up Ubuntu on WSL, just follow Linux instructions for installing Wasp. If you need further help, reach out to us on Discord - we have some community members using WSL that might be able to help you.
Note: If you are using WSL2, make sure that your Wasp project is not on Windows file system, but instead on Linux file system. Otherwise, Wasp won't be able to detect file changes, due to the issue in WSL2.
I believe this is something we will figure out with time and can't be sure yet, but I would like us to start thinking about it.
How much should we put in language, and how much in standard library?
Right now, we are putting everything in language, but that might quickly grow it to complexity where it will be pretty hard to manage!
So the idea is that we recognize some basic concepts that language itself should implement, that serve as basis for everything else, and then move the rest to standard library. However, this is not typical general language, instead we want Wasp to have very good understanding of what is what + we want to generate code from it that is nice, meaning we can't afford to lose some of the information that we might lose if we move some stuff to std lib instead of core language (or can we?).
Naive try:
I believe that we are certainly going to move in this direction at some point, but let's leave it for later when we will have more information on how we are designing the language and similar, and we can add more thought in this issue in the meantime.
If Wasper wants to, for example, add Redux to their app, they will want to wrap the top-lvl React component into Provider for Redux. In that case, they will need some kind of mechanism to do that -> it could be a JS function that receives top lvl component and returns the same component but wrapped into whatever they like -> basically a decorator.
We want Wasp to have support for adding a favicon. This should be property of app
declaration:
app MyApp {
title: "My super app!",
favicon: "@ext/favicon.ico"
Should favicon really go into ext/
or not (and go somewhere next to .wasp), is debatable. Also, what are the requirements on favicon? Does it have to have certain dimensions? What about specifying different favicon of different qualities, as can be normal for some cases? What about some additional properties?
It feels like this should be researched more -> understanding the essence of favicon, and then we can go from there.
Right now, EntityForm (entity-form) is generated with onCreate prop that takes a function which it executes once form is submitted. This gives flexibility, allowing user to specify what happens once form is submitted, however we would like to support, by default, the default case, which is just creating new instance of that entity and adding it to the main collection for that entity.
We could, in generated code, do it so that if prop is not given, this default operation is done. For this, we need to connect the form to redux. This is ok, but what is not ideal is that even when user is using the prop, form is still needlessly connected to redux.
Better might be that we remove prop (from generated code) and that is it, there is no customizing. Later, we add property in Wasp code, to specify different action if you want, and maybe some time in the future we add prop to generated code, in some smart way. I think I like this the best.
If we go with this second approach, this should be pretty easy, just adding new property to EntityForm in Wasp and removing React prop in generated code + connecting generated component to Redux.
Stemming from #63, we want to implement first version of "entities as resources" solution.
This means following:
For 1., we need to be careful when injecting prisma.task
that we don't mess up any references to this
in javascript.
How will invalidation work? We could have, in generated code, a "singleton" that serves as registry of "this operation is tied up to this entity/resource", and each query would register to it in its module, and then when action is performed, we just invalidate their caches (via react-query) by learning from this register who needs to be invalidated.
We want to implement Entity on level of Wasp.
It should have following primitive types as possible values of its fields:
We will almost certainly want to also add Object and Array at some point and so much other things, possibly also more primitive types, but that is out of scope of this specific issue.
Entity should generate appropriate database logic (Prisma schema), and it will also serve as stepping stone for other features (entity based forms/UI, smarter state management, ...).
It should either print version each time Usage is printed, or we should add a command that prints version.
At some point, we will probably want to add first-class support for local state. Queries and actions right now manage remote state (server state), but they don't manage local state.
Right now, solution would be that Wasper uses Redux or some other solution, or maybe even React context as some kind of local state. However, it would probably be nicer to provide support on Wasp level!
I like how GraphQL does it -> they introduce local schema for managing local state, so at the end server state and local state become somewhat blended, you don't know and don't care which state you are working with, it is all just state.
It would be cool to do smth similar in Wasp, and we can probably achieve it by introducing local operations (queries and actions), which means you could write queries and actions that operate on client, not on server, and then use them together with the "normal", server queries/actions, without thinking about it too much. I am not yet sure about the details here, but I feel the direction is promising.
This is not urgent, we should wait until people start building Wasp apps big enough that they need local state and then we can look into how to implement this.
This comment reflects current state of the issue. Check comments below for discussion.
There are three stages, and we plan to implement them in order, from basic towards most advanced:
Use case: I implemented getTask(id)
query and when I invoked it from client with an inexistent id (not in db), server sent 500 response along with Prisma error msg.
This should not have happened, we don't want to expose the internals. We had a generic message prepared to be used but it wasn't - we need to investigate why.
Right now, if we are doing sign up (via createNewUser) and email already exists, 500 is returned, which is internal server error. Instead, we should return more descriptive message, and it certainly shouldn't be 500. I am not sure what is the best practice, is it ok security-wise to say that email is already used up, so we should figure that out.
We should check out the generated code for createNewUser and make sure to handle Prisma errors there and throw appropriate HttpErrors.
Right now Wasp is pretty hard to try out, due to couple of reasons:
stack
is downloading and installing all of its dependencies, and I am not sure how easy it is in general to install stack
and if people want to do it at all.)We should figure out what are the most efficient steps to take to make wasp easier to try out, so people can play with it! It might thought make sense to postpone this for later, when there is more to play with (more features).
The problem is that user can now run migrate
commands before the code was generated for the first time and prisma CLI installed.
In that case everything just freezes. A use-case that I had was when I downloaded our todo example files and tried to run migrate-save
before anything else.
Possible solution: db commands should run code generation and npm install before doing their stuff. Or they could check if it was ever done (e.g. we can save that info in .wasproot file) and then do it only if it is needed.
Waspc (Wasp compiler) currently has one compile option (and more will be coming), which is external code directory path
.
Some background:
This is path to a directory that contains "external" code, like .js, .css and similar. .wasp imports and uses this code, so that why it is important for compiler to know where it is located. Compiler right now basically copies that whole directory to the generated code. In the future, we will most likely remove this concept of special directory for such code and will just have normal imports, allowing code to be anywhere and copying it accordingly, but this is how it works for now.
TODO:
We want to allow specifying this path as argument to waspc CLI. Something like waspc input.wasp out/ --ext-dir ../foo/my-ext-code
(new part is --ext-dir ../foo/my-ext-code
).
File to start from (and possibly only file to be edited) is https://github.com/wasp-lang/wasp/blob/master/waspc/app/Main.hs .
Currently we don't do any checks, but we should - length, special characters and everything else that determines the strength of the password.
Hello folks!
I'm trying to build the project on my machine with steps described in readme but with no success:
bash-5.0$ stack build
Building all executables for `waspc' once. After a successful build of all of them, only specified executables will be rebuilt.
waspc> configure (lib + exe)
Configuring waspc-0.1.0.0...
waspc> build (lib + exe)
Preprocessing library for waspc-0.1.0.0..
Building library for waspc-0.1.0.0..
Preprocessing executable 'wasp' for waspc-0.1.0.0..
Building executable 'wasp' for waspc-0.1.0.0..
[ 2 of 11] Compiling Common
<command line>: can't load framework: Cocoa (not found)
-- While building package waspc-0.1.0.0 (scroll up to its section to see the error) using:
/Users/tim/.stack/setup-exe-cache/x86_64-osx/Cabal-simple_mPHDZzAJ_3.0.1.0_ghc-8.8.4 --builddir=.stack-work/dist/x86_64-osx/Cabal-3.0.1.0 build lib:waspc exe:wasp --ghc-options " -fdiagnostics-color=always"
Process exited with code: ExitFailure 1
I guess this may be because Apple got rid of system Cocoa.framework
which affects a lot of software. There are no release notes describing the change but I've seen that before.
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.