smapiot / piral Goto Github PK
View Code? Open in Web Editor NEWFramework for next generation web apps using micro frontends. :rocket:
Home Page: https://piral.io
License: MIT License
Framework for next generation web apps using micro frontends. :rocket:
Home Page: https://piral.io
License: MIT License
0.7 preview (or 0.6.3) on any OS with any version of NPM.
When scaffolding with an inexisting package (e.g., foobarqxz
) no error is shown and nothing is installed (apparently the NPM installation in the background failed). However, something is scaffolded. Obviously, this cannot work.
pilet new foobarqxz
An error is shown and the scaffolding is aborted.
No error is shown and something has been scaffolded. This something is not capable of running.
Check if the NPM installation was a success. If not, tell the user and abort.
Apparently, such a check already exists internally (it rejects
when errored), but it seems unsufficient for a failed installation. This should be investigated and corrected!
I'm curious what your thoughts are in piral around how dependencies are managed in the core of the app vs in individual pilet bundles. I've been hunting around this year for a solution to help a large modular front end app ship individual pieces independently, and piral overlaps on heaps of requirements I have.
I've got lots of questions I'm keen to find out more details for, but initially I'm curious what the plan would be for checking the presence of dependencies. Do the individual modules/pilets operate with no assumptions about npm packages that may have been bundled by the core app? For example, I'm guessing something like react router is bundled in the core app, can a pilet make any assumptions about what dependencies might already be bundled and in scope?
Does the individual pilet get built in a context where it compiles against a specific version of the core app?
Dependency isolation is nice because it allows individual modules to ship at their own pace, but has the down side that no assumptions can be made, and each module needs to bundle its own dependencies, which will be wasteful.
The general context is I'm hunting for a solution where a team that ships a particular module is free to continuously ship their piece, with minimal bottlenecks and constraints on any other piece, even avoiding a need to redeploy a central app at all.
But with this level of isolation, do you end up with n versions of standard dependencies for n pilets?
Super keen to find out some more about concepts like this, I really haven't come across another project that seems to target this specific problem in this way π
Using https://docs.piral.io
.
The general documentation includes a "Development" section, which, unfortunately, has nothing to do with the expected "development of a Piral instance", but rather with the "development of Piral itself".
We need to introduce some terminology to make clear what is what.
docs.piral.io
The development of Piral itself should maybe be constraint to the GitHub repository, e.g., the README files.
The following strategy could help:
piral-vue
) has documentation as accessible as possible for people coming to NPMRight now it is quite a mix. Not very user friendly.
The documentation is growing and the organization needs to be more strict.
It is desired to handle actions before or after the scaffolding takes place.
(for us it's all about the after hook).
Ideally, it would be possible to execute custom code after scaffolding took place, for example to automatically format files to the desired code-style or maybe to expand template variables in files.
In our case, the package.json is generated by piral. But we want to have a consistent ordering of attributes in the package.json, which is why we use npx sort-package-json
to sort it.
Windows, latest version
Scaffolding locally (from a tgz) does not work when the relative path is like "my-app\my-app.tgz" instead of ".\my-app\my-app.tgz" (independent of slash or backslash).
piral new my-app
)npm pack
)pilet new my-app/my-app.tgz --target test-pilet
)The pilet gets scaffolded from the local tgz just like pilet new ./my-app/my-app.tgz --target test-pilet
does).
It is failing.
Right now only the initial dot is evaluated to be a local pilet. We should, however, also consider ending with .tgz
. In this case we could see if such a path exists locally. Only if it does we go on with a local scaffolding.
Right now we support the top players (React obviously, Angular and Vue), but (except for Hyperapp) we do not support other (established) frameworks.
We should go for integrating a converter to handle Aurelia. Like all other converters it would be opt-in.
Right now I can't think of anything to discuss.
Allow Pilets to use import()
, i.e., code splitting (implicitly this will allow also resources like images to be extracted).
Ideally, the Pilet creator can just use the basic functionality of any bundler to place require
and import
calls. For resources the author may configure an extraction (inlining is simple and will work by default). For modules the import
would implicitly create a split bundle (i.e., another js file as the build outcome).
While handling on the server side (serving the Pilets) is not part of this issue, the Pilet definition that is consumed by Piral is. In Piral we need to handle
Similar to #69, however, for pilets - pilet validate
. Besides some obvious checks in the package.json (mostly main
, devDependencies
, ...) we will check the used version of the Piral instance. If the version can be checked and the currently used version is older than the current one it may yield a warning or negative result.
Especially CI/CD systems may want a simple way to perform some validation on the pilets before publishing them.
I guess not everyone will agree to all the rules we come up here. Therefore, we could introduce a key in the pilets
section of the Piral instance's package.json - maybe called validation
. This key could have all the rules specified similar to, e.g., an eslint.json. Alternatively, an extra file may be used (pilint.json?).
What rules should exist? What could be their values?
Right now I see (up for discussion):
updated
required
(otherwise error), suggest
(otherwise warning), ignore
(not used)suggest
no-self-reference
active
(check is active), ignore
(not used)ignore
no-third-party-dependency
active
(check is active), ignore
(not used)ignore
externals-as-peer-dependencies
active
(check is active), ignore
(not used), maybe only-used
(only the ones that are used in code)ignore
The validity of the package.json / pilet as a whole is implicit and not exposed as a rule. It will always be validated.
The useExtension
mechanism is a powerful one, but since it gives back a React component, it makes sense to supply wrappers for other languages (contained obviously in their packages, e.g., piral-ng
for the Angular wrapper).
Piral is React-first, but tries to be very open for the integration of other libraries / frameworks. This should apply to all APIs that are available in pilets (e.g., also showNotification
etc.).
It seems useful to apply the React createPortal
approach here for efficiency. Thus the original component will stay as React (or already packed / hooked into a React component), but the React component does not create a new render tree, but uses an existing render tree.
Latest v0.7 pre or earlier versions depending on Parcel 1.12.3 or earlier.
Installation of the piral-cli
yields a high security vuln. warning from NPM. Apparently, the vuln. is in Parcel.
See parcel-bundler/parcel#3454 for details.
piral-cli
No warning should appear. piral-cli
should be free of any malicious code.
A dependency of Parcel has been found with vulnerable code - thus as Parcel is a dependency of the piral-cli
we also get a warning.
Wait until the merged fix (parcel-bundler/parcel#3451) is also available in a published version of Parcel. So far the authors stand at "we don't release unless critical", but this is a classic case of "high enough" in my opinion.
The Pilet API should be accessible also in App Shell.
At the moment accessing the API from the App Shell is not that easy and some features of the API might useful/required in the App Shell.
This could be already covered by #38, if createApi
is available. Alternatively (or additionally), the core (i.e., app-shell) could already create an API or itself and pass it back.
The piral-cli
should be capable of creating a new Pilet (just like some yeoman generator can do). Advantage of the piral-cli
would be that it could be sensitive to a given Piral instance (thus already using a specific layout, i.e., specific dev / peer dependencies) coming from a feed or local. Furthermore, while the code itself would need updates, the definitions (e.g., for a standard Piral instance) would be dynamic and always up-to-date.
The Piral CLI should support the integration of custom plugins, which enhance the functionality of the CLI.
Right now adding some commands, e.g., for (E2E) testing, are missing (on purpose). Nevertheless, with a simple extension mechanism new commands (e.g., pilet test
) could be made available.
Plugins could have the following opportunities:
Maybe there are more (or should be less) possibilities?
Right now the Piral instance is checked in a shallow way to obey to the requirements / spec, e.g., if an app
key is present (and valid) in the package.json.
This should go a bit further and the validation should also be exposed in form of a Piral CLI command, such as piral validate
.
If the information, e.g., in files
is invalid it should be pointed out before scaffolding / running pilet debug
or similar is tried. It should complain as soon as possible.
What should be verified? Initial guess:
app
field, pilets
section etc.pilets.files
are valid paths and in case of app
have a valid contentNo new feature - just more detailed documentation.
It would be great if you could extend the documentation regarding PageError, FeedError etc. so that it explains exactly when this type of error occurs.
We as shell application developers want to provide a dedicated component which takes care of error presentation. To be able to test all the error cases which are handled by piral we have to know why and when they occur.
For NotFoundError it is pretty clear - so it would for example just say: βThis error is shown if the user navigates to a route/page which is not registered.β But for the other cases it is not obvious when they occur. At the end it would be enough if you could at least extend the explanations in the components.ts file.
Lazy loading of Pilets (if a Pilet responsible for current page is already given) should be somehow allowed by Piral such that a server-side renderer may already include the Piral responsible for the current page.
In such a case Piral would not wait until all Pilets are loaded, but would start with the current Pilet right away. The rest would be loaded and integrated on the fly. Side-effects of this should be noted and evaluated during implementation.
The pilet scaffolding from non-NPM registry sources such as the local filesystem, git repositories, ... should just work.
Right now having a (private) NPM feed to distribute the Piral instance for development of pilets seems necessary despite just being a development booster / optional. We should allow / support other options nicely to provide possibilities to people without a private NPM feed.
What options should we support?
0.7.0 preview, Linux, MacOS and WSL.
Right now only Piral debug / build get the enhanced shared dependency resolution.
extendSharedDependencies({})()
) in the consoleThe console output when running piral debug
should be the same as in pilet debug
.
The output is different. The added package(s) are not listed during pilet debug
.
While this has no effect on the debugged pilet (currently, it gets the dependencies from the bundler - i.e., direct) it has an impact on the loaded external pilets (i.e., in a scenario when the current pilet should be debugged with other pilets).
Also in the long run this has some impact (-> in the long run pilet debug
should always go for the ("manually specified") indirect dependencies to be consistent with the runtime behavior later).
For the Pilet scaffolding files can be added to the Piral instance and referenced in the package.json file. The files will be copied as part of the scaffolding process to the same destination path as the are located in the Piral instance.
The scaffolding process should support that the destination for the files in the new Pilet can be configured.
This enhancement would add further flexibility in customising the scaffolding of Pilets based on the Piral instance.
For more information, see the CONTRIBUTING
guide.
It should be easily possible to create a progressive web app for a Piral instance via an opt-in plugin in form of another package (e.g., piral-pwa
).
The package must come with possibilities to determine the PWA options / manifest. As such it may not be exclusive to JS/TS code, but potentially also in other forms (some manifest.json / package.json extensions ...).
For Parcel implementation details see parcel-bundler/parcel#301.
Some Parcel plugins are already based on the most used option; Workbox.
Piral instances are SPA-first. As such they are perfectly suited for offline-first scenarios. This may not apply to every Piral instance, and further work (e.g., abstracting away some authentication mechanism) may be required. Nevertheless, some people may desire a very simple way to create a PWA for their Piral instance (and its a great showcase).
Determines a lot on the ability of Parcel to make this work. Maybe the piral-cli
requires some exetnsion mechanism for it. This was planned anyway for the future.
For debugging purposes (e.g., pilet or piral watch mode) an instance of kras
should be included. kras
makes mocking (also with proxying) and HTTP introspection very simple. It should be fully configurable from the user.
Right now Parcel's cache comes with all the good (and bad) consequences. One potential problem is that a cached module (asset) may actually lead to invalid bundling or strange errors - especially in the context of Pilets where we apply quite some magic.
Knowing that Parcel has a cache (folder: .cache
) one may delete it to "see" if its a real issue or just some caching issue. However, not knowing this one can be in a really bad spot - essentially having a system that only recovers / behaves normally when cloned / installed somewhere else again.
On upgrade of a Pilet some dependencies may change. As a result the asset tree from Parcel may need to know that (potentially previously included dependencies) are now virtual modules. This can have all sorts of side-effects.
When should we clear the cache? The following seems obvious:
When using the documentation shown at https://docs.piral.io/documentation#your-first-piral-application the reader expects a step-by-step instruction on how to start creating a own Piral Instance. Instead, the reader needs to follow a link at the top which will be not recognized, because the headline is telling about a "Pilet Guideline".
For starters, it must be intuitive to start with Piral, by showing the big picture with real-life samples.
The onboarding of developers must be very easy by offering well-structured step-by-step guides with real-life samples in order to have fun starting with Piral and all of its components.
We should use parcel-codegen more actively, e.g., to generate the documentation within Parcel instead of before.
Right now we have separate commands to solve documentation and they are flawed. We want also to make better use of the Markdown files, e.g., by using a custom Markdown middleware (already pre-processing to HTML).
Right now already attached pilets are evaluated together with the dynamically loaded pilets. Thus the attached pilets need to wait unnecessary long to be integrated.
In general pilets should be evaluated as fast as possible. Maybe different strategies should be selectable, where asyncβ βββββ(already introduced) is just one short way.
Right now no possibility of explicitly configuring the functionality coming from piral-ext
, e.g., the fetch
behavior, exists.
With no configuration (besides the default one taken by the Piral framework) commands such as fetch
, query
, etc. are limited. If they need additional settings such as headers that need to be send, then these commands are more or less useless.
Potentially, these additions can be determined anyway (and thus fully configured if wanted).
Right now (a global installation of) the Piral CLI with parameters passed as CLI arguments needs to be used for scaffolding a new pilet or a new Piral instance. Both cases should be simplified to provide a really simple way of scaffolding a pilet or Piral instance.
Ideally, NPM itself can be used for this process:
npm init pilet
would create a new pilet, while
npm init piral-instance
would create a new Piral instance.
In a bit older NPM versions npx
could be used:
npx create-pilet
npx create-piral-instance
All of these commands would not work with CLI parameters, but with command prompts. As such they provide an easy and interactive way of scaffolding.
This idea has been discussed with a bit of background in #61. In particular, the current way of requiring a global installation is not super user friendly. The Piral CLI continues to work locally, however, for scaffolding this is (due to chicken-egg...) not working out.
Essentially, these two new commands represent two initializer packages, that just wrap the Piral CLI with a questionnaire run as command line prompts.
Right now the dev dependencies to be transported into a pilet have to be specified explicitly. This can lead to a skew (or duplication) between a dev dependency in the Piral instance and a dev dependency in pilets.
We should have a simple way to tell Piral which dev dependencies to just take from the Piral instance and apply in the pilets (same version).
Keeping versions in sync can be quite bad. Especially since the devDependencies
of the pilets
sections are not validated by NPM. Thus invalid versions may appear.
Right now a possibility is to just specify a dev dependency as true
. Such a dependency would be resolved against the given version from the dependencies of the Piral instance first.
If no such dependency as been found a warning should be output and the latest
version should be used.
Enhance the Piral CLI to also integrate with the local Pilet Feed service.
Sometimes during development of a Piral instance it makes sense to have a local feed service running. The sample feed service is great and useful for this purpose, but it needs to be started separately (despite it being published now as an NPM package that allows execution, e.g., via npx
).
If piral-cli-feed
would exist it could allow:
piral feed
to run the server standalone (same as sample-pilet-service
does today)piral debug --feed
to run the feed in kras as a middleware; essentially not needing to mock the feedMight be a useful extension, but it should have a lower priority, since the public feed service is available by now and offers more features than the local feed service.
For example if, e.g., recharts
is used in a pilet with bundle splitting (i.e., not in the main bundle) and (previously not used) shared dependencies are used within that dependency (e.g., react-dom
) then the URL cannot be resolved.
The build should succeed.
Build fails with, e.g., /react-dom.vm: ENOENT: no such file or directory, open '/react-dom.vm'
.
URL for shared dependency resolution fails in transient bundles.
Workaround: Right now the workaround is to introduce the shared dependency explicitly in the main bundle, e.g., via import 'react-dom';
. This, however, should not be necessary.
Currently, piral-core
(and dependents such as piral
) use tslib
. As such it should be a no-brainer to also expose tslib
as a shared dependency. This can significantly reduce bundle size.
tslib
contains all runtime helpers of TypeScript. These helpers are auto-generated by default, but can be referenced implicitly using the "importHelpers": true
setting in the compilerOptions
of the tsconfig.
Naturally, the tsconfig of a Piral instance / pilets should contain this flag and use the then shared dependency to reduce output sizes.
Maybe other shared runtime libs (e.g., for async code the regenerator package comes to my mind) should be shared similarly. Right now, however, it is unclear how its exposed. Potentially, this should therefore be left to the specific Piral instance to decide.
For some scenarios and supporting older browser version, Server-Side rendering could bring benefits.
Certain older browser might not fully support all features provided by Piral. Another benefit could be improving the overall performance e.g. by adding caching for the rendered pages.
One downside of using the Server-Side rendering is that the backend will require more resources for rendering and exposing the content.
The current logo is just a placeholder. Before going really "public" (i.e., declaring this thing - API - stable and doing more blog posts etc.) we want to come up with something really good.
Does anyone have some good idea? Is someone interested in "sponsoring" / "contributing" a cool logo design? Any kind of concepts or sketches would be much appreciated.
We are currently working towards a first "official" release (incl. a nice landing page). Yes, we already have some NPM packages out there, however, we only wanted to refine our CI process / workflow and protect the package names. At this point in time nothing should be considered stable or officially supported.
Update: The Pilet API is stable, the way of creating a Piral instance is quite stable, too. Everything is officially supported.
We hope that we can publish a first release candidate (RC) end of November 2019. This release will also include a feed with sample pilets to easily play around.
Update: A first RC (0.9) was released. With the upcoming 0.10 we publish the second RC.
In the end of Q4 2019 we should be ready to offer some premium support on top of the standard open-source development if desired.
Update: If you need enhanced support then just contact us. We are here to help.
piral-core/cli: 0.7.0-pre.637
I have the following section in the createInstance() call:
getDependencies: extendSharedDependencies({
'my-piral-instance': require('./exports'),
}),
If a pilet is published normally to the feed service, this dependency is correctly provided from the shell to the pilet. But when using pilet debug
, it will give the pilet a different instance of this dependency (the referenced by the main
attribute in the package.json of the shell.
pilet debug
. It will show false.There should only be one instance exported to the pilet. The pilet should not get a different import than what the shell exports at runtime.
The pilet got served a separate version of the export, which is not the same as the one the shell uses.
Right now the documentation page available at docs.piral.io does not contain any information regarding the (current) version (reflecting the target pf the documentation). The version should be added and always be present.
Ideally, there should be a follow up story to bring in a version selector for different versions.
Versions are relevant to find the right information for the currently used software.
Right now we support the top players (React obviously, Angular and Vue), but (except for Hyperapp) we do not support other (established) frameworks.
We should go for integrating a converter to handle Mithril.js. Like all other converters it would be opt-in.
Right now I can't think of anything to discuss.
For more information, see the CONTRIBUTING
guide.
Easy Way
Detailed Way
Next Steps
It is not clear, what the reader needs to do in order to run the Piral instance.
Here, a big picture and step-by-step guide will help a lot.
The start with creating an own Piral instance must be as simple as possible.
A detailed step-by-step guide must be provided and every step needs to be decribed also for NodeJS and/or React beginners.
The piral-cli
should be capable of updating an existing Pilet (created using the scaffolding #3). The upgrade mechanism would specifically update the scripts and template files + any new peer dependencies / versions.
The central dependencies / tooling should be fully determined by the used Piral instance. As such it makes sense to be able to easily upgrade a Pilet in all areas where it did not try to be unique, but rather took some defaults coming from that Piral instance.
The current API uses generics, which makes it hard to deal with it on multiple layers. Especially from a pilet POV its a huge pain.
We need a better way - that originates the API already from piral-core
and only extends it all the way.
Right now the app shell should / would alias the "final" API and use it in generics. As such MyAppShellApi
becomes a thing. It should not be. It will prevent cross Piral solutions (pilets) independent of any pattern libraries or other dependencies - just because the APIs are named differently (but share the same core or are even 100% the same).
This will have an impact on how pilet API extensions are written. The intention here is not to make things more complicated or worse, but to simplify.
By using declaration merging we are on the road to have a single Piral instance only (which makes sense - multiple instances are academic anyway).
A public feed for Pilets should be available, so that developers who are starting to evaluate and use Piral, have a possibility to host their Pilets for development and testing purposes.
The standard approach for Piral is that Pilets are loaded from a so-called feed service. When starting with Piral such a feed service is usually not available. To ease the process of evaluating Piral and get started with the first Pilets, a publicly accessible feed service should be available, where developers can perform a simple registration and publish their Pilets into their own dedicated feed.
Some restrictions to the number of Pilets or number of Requests should apply, to keep the efforts of hosting and operating the public feed in a certain limit.
We should attach the actions somehow to the created PiralInstance. Thus the actions can be accessed like PiralInstance.foo or similar for the different actions. Potentially, the actions should all be contained in PiralInstance.actions
Right now its not so easy to fire the actions without the useAction/s hook/s. Its also not directly possible to create an API. Thus using the actions (from non-rendering code) is more difficult than it should be.
Enhance the current piral-core createInstance
method to allow input of a preexisting history object.
When piralizing an existing app it comes in handy to configure which history object the piral instance uses. This allows "programmatical" routing - meaning the ability to push changes to the history object.
The preexisting history object is probably created with
import { createBrowserHistory } from 'history';
method.
It seems we have 2 options to achieve this
createInstance
callUnder the hood piral needs to take care that the history object gets forwarded to the router instance which is really used at runtime. This means probably also switching from using BrowserRouter
to Router
component of react-router
lib.
Regarding the 2nd solution we could adjust the options for setupState
to be able to input the history object there. Piral should then look inside the state if a history object exists.
Give a sign if you have further questions.
Right now we have piral-auth
a plugin that establishes a user object. However, it does not directly deal with authorization / authentication. We should provide some premade solutions (in form of plugins) for dealing with popular providers. Microsoft's Active Directory is a quite popular way - we should support it directly.
Microsoft released several libraries for dealing with AD authentication from JS. The latest version is called MSAL.js.
Potentially, just wrapping the plugin and exposing it via some actions. An integration with, e.g., fetch
or urql
would be superb (e.g., indirectly via events) to ensure a valid token is provided if necessary.
Configuration etc. should just be taken / extended from the original. What other solutions (generic OAuth 2? or already OIDC?) should be available in form of plugins?
We should simplify most of the register...
APIs in the pilet API.
Right now APIs such as registerTile
have a name parameter (usually the first parameter) which is only used internally (will be part of the ID in the component registry) and for calling the unregister...
counterpart.
The following APIs would be a match here:
registerTile
(and related such as registerTileX
)registerMenu
(and registerMenuX
)registerSearchProvider
All other APIs (e.g., registerModal
) use the name actively in some other regularly used API, e.g., for showModal
, linking against the page, etc.
Observing that the unregister...
counterpart is almost never used we could also provide an alternative ("overload") of these functions that omits the first parameter. In such cases Piral should just auto-create a name that is by definition unique.
Obviously, using this overload there is no chance of calling unregister...
.
Currently the Piral Ext library serves several functions like translations or backend connectivity. It might be useful to split the library into smaller libraries, so that developers have some flexibility to chose and tailor to the required features.
When somebody wants to use piral-core
due to a migration of an existing application the full piral-ext
may be too much to consume. Nevertheless, a part of it (e.g., fetch
) may still be interesting.
We could go for the following parts:
piral-fetch
piral-translate
piral-urql
For more information, see the CONTRIBUTING
guide.
As discussed, we need a way to extend shared dependencies (keeping the ones that the piral-cli adds via the externals
field in the package.json)
We usually want to keep use the externals
dependency mechanism rather than explicitly exporting them. But in some cases, an explicit export is the only way (for example when exporting from the shell).
For packages using a browser
field in the package.json
the resolution for "externals" are not performed correctly (actually, the content in browser
is ignored, however, the underlying bundler (Parcel) does not ignore it - leading to a mismatch).
Example (taken from Styled Components):
{
"browser": {
"./dist/styled-components.esm.js": "./dist/styled-components.browser.esm.js",
"./dist/styled-components.cjs.js": "./dist/styled-components.browser.cjs.js"
},
// ...
}
browser
should have preference over other paths (e.g., ESM).
import 'styled-components';
)The bundle size should be quite minimal (e.g., close to 1k).
The bundle size is > 60k, where styled components is fully integrated.
We should fix the Piral CLI regarding the module resolution. Potentially, the algorithm from Parcel should be either re-used or reverse engineered ("copied") to prevent such bugs in the future.
Piral 0.8.0
calling registerPage with a component returned by react-redux's connect()
method, I'm getting an exception:
No converter for component of type "function ConnectFunction(props) {...
app.registerPage()
I expect this to work with react-redux components.
An exception is thrown
At this point, you are trying to use component.type:
But component.type is not a string, but instead is a function for this connected component.
A custom field should be added to the package.json file. The new field should be optional and transport meta data related to the given pilet. There should not be any content restriction applied to this field.
Via the feed service, clients can be informed about custom attributes of the available pilets.
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.