Giter Site home page Giter Site logo

inertia-bundle's Introduction

Inertia.js Symfony Adapter

CI StyleCI

This is a Inertia.js server-side adapter based on inertia-laravel, but for Symfony 5 and 6.

Warning

Looking for a new owner

As I don't work with Symfony or PHP anymore, I'm looking for someone who wants to take over the development of this project. As of now the project is unmaintained.

Installation

First, make sure you have the twig, encore and serializer recipe:

composer require twig encore symfony/serializer-pack

Install using Composer:

composer require rompetomp/inertia-bundle
yarn add @inertiajs/inertia

Setup root template

The first step to using Inertia is creating a root template. We recommend using app.html.twig. This template should include your assets, as well as the inertia(page) function

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    {% block stylesheets %}
        {{ encore_entry_link_tags('app') }}
    {% endblock %}
    {{ inertiaHead(page) }}
</head>
<body>
{{ inertia(page) }}
{% block javascripts %}
    {{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

The inertia(page) function is a helper function for creating our base div. It includes a data-page attribute which contains the initial page information. This is what it looks like:

<div id="app" data-page="<?php echo htmlspecialchars(json_encode($page)); ?>"></div>

If you'd like a different root view, you can change it by creating a config/packages/rompetomp_inertia.yaml file and including this config:

rompetomp_inertia:
  root_view: 'name.html.twig'

Set up the frontend adapter

Find a frontend adapter that you wish to use here https://github.com/inertiajs. The README's are using Laravel's Webpack Mix. It's not hard translating this to Webpack Encore, just follow the documentation here: https://symfony.com/doc/current/frontend.html.

Webpack Encore Examples

For Vue:

yarn add @inertiajs/inertia-vue
const Encore = require('@symfony/webpack-encore')
const path = require('path')

if (!Encore.isRuntimeEnvironmentConfigured()) {
  Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev')
}

Encore
  .setOutputPath('public/build/')
  .setPublicPath('/build')
  .enableVueLoader()
  .addAliases({
    vue$: 'vue/dist/vue.runtime.esm.js',
    '@': path.resolve('assets/js')
  })
  .addEntry('app', './assets/js/app.js')
  .splitEntryChunks()
  .cleanupOutputBeforeBuild()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .disableSingleRuntimeChunk()
  .configureBabel(() => {}, {
    useBuiltIns: 'usage',
    corejs: 3
  })
  .enableSassLoader()

module.exports = Encore.getWebpackConfig()
//assets/app.js
import { createInertiaApp } from '@inertiajs/inertia-vue'
import Vue from "vue";

createInertiaApp({
    resolve: name => require(`./Pages/${name}`),
    setup({ el, app, props }) {
        new Vue({
            render: h => h(app, props),
        }).$mount(el)
    },
})

For React:

const Encore = require('@symfony/webpack-encore')
const path = require('path')

if (!Encore.isRuntimeEnvironmentConfigured()) {
  Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev')
}

Encore
  .setOutputPath('public/build/')
  .setPublicPath('/build')
  .enableReactPreset()
  .addAliases({
    '@': path.resolve('assets/js')
  })
  .addEntry('app', './assets/js/app.js')
  .splitEntryChunks()
  .cleanupOutputBeforeBuild()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .disableSingleRuntimeChunk()
  .configureBabel(() => {}, {
    useBuiltIns: 'usage',
    corejs: 3
  })
  .enableSassLoader()

module.exports = Encore.getWebpackConfig()

For Svelte:

const Encore = require('@symfony/webpack-encore')
const path = require('path')

if (!Encore.isRuntimeEnvironmentConfigured()) {
  Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev')
}

Encore
  .setOutputPath('public/build/')
  .setPublicPath('/build')
  .addLoader({
    test: /\.(svelte)$/,
    use: {
      loader: 'svelte-loader',
      options: {
        emitCss: true,
        hotReload: true,
      },
    },
  })
  .addAliases({
    '@': path.resolve('assets/js')
  })
  .addEntry('app', './assets/js/app.js')
  .splitEntryChunks()
  .cleanupOutputBeforeBuild()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .disableSingleRuntimeChunk()
  .configureBabel(() => {}, {
    useBuiltIns: 'usage',
    corejs: 3
  })
  .enableSassLoader()

const config = Encore.getWebpackConfig()
config.resolve.mainFields = ['svelte', 'browser', 'module', 'main']
config.resolve.extensions =  ['.wasm', '.mjs', '.js', '.json', '.jsx', '.vue', '.ts', '.tsx', '.svelte']

module.exports = config

Making Inertia responses

To make an Inertia response, inject the Rompetomp\InertiaBundle\Service\InertiaInterface $inertia typehint in your controller, and use the render function on that Service:

<?php
namespace App\Controller;

use Rompetomp\InertiaBundle\Service\InertiaInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class DashboardController extends AbstractController
{
    public function index(InertiaInterface $inertia)
    {
        return $inertia->render('Dashboard', ['prop' => 'propValue']);
    }
}

Sharing data

To share data with all your components, use $inertia->share($key, $data). This can be done in an EventSubscriber:

<?php

namespace App\EventSubscriber;

use Rompetomp\InertiaBundle\Service\InertiaInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class InertiaSubscriber implements EventSubscriberInterface
{
    /** @var \Rompetomp\InertiaBundle\Service\InertiaInterface */
    protected $inertia;

    /**
     * AppSubscriber constructor.
     *
     * @param \Rompetomp\InertiaBundle\Service\InertiaInterface $inertia
     */
    public function __construct(InertiaInterface $inertia)
    {
        $this->inertia = $inertia;
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::CONTROLLER => 'onControllerEvent',
        ];
    }

    public function onControllerEvent($event)
    {
        $this->inertia->share(
            'Auth::user', 
            [
                'name' => 'Hannes', // Synchronously
                'posts' => function () {
                    return [1 => 'Post'];
                }   
            ]
        );
    }
}

View data

If you want to pass data to your root template, you can do that by passing a third parameter to the render function:

return $inertia->render('Dashboard', ['prop' => 'propValue'], ['title' => 'Page Title']);

You can also pass these with the function viewData, just like you would pass data to the share function:

$this->inertia->viewData('title', 'Page Title');

You can access this data in your layout file under the viewData variable.

Asset versioning

Like in Laravel, you can also pass a version to the Inertia services by calling

$inertia->version($version);

Lazy Prop

It's more efficient to use lazy data evaluation server-side you are using partial reloads.

To use lazy data you need to use Rompetomp\InertiaBundle\Service\Inertia::lazy

Sample usage:

<?php
namespace App\Controller;

use Rompetomp\InertiaBundle\Service\InertiaInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class DashboardController extends AbstractController
{
    public function index(InertiaInterface $inertia)
    {
        return $inertia->render('Dashboard', [
            // using array
            'usingArray' => $inertia->lazy(['SomeClass', 'someMethod']),
            // using string
            'usingString' => $inertia->lazy('SomeClass::someMethod'),
            // using callable
            'usingCallable' => $inertia->lazy(function () { return [...]; }),
        ]);
    }
}

The lazy method can accept callable, array and string When using string or array, the service will try to check if it is existing service in container if not it will just proceed to call the function

Server-side rendering

For frontend configuration just follow the document https://inertiajs.com/server-side-rendering#setting-up-ssr

Setting up Encore / webpack

To run the webpack properly install webpack-node-externals

npm install webpack-node-externals

Next we will create a new file namely webpack.ssr.config.js this is almost the same with your webpack.config.js. Remember that you need to keep this both config.

touch webpack.ssr.mix.js

Here is an example file for webpack.ssr.config.js

const Encore = require('@symfony/webpack-encore')
const webpackNodeExternals = require('webpack-node-externals')
const path = require('path')

if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev')
}

Encore
    .setOutputPath('public/build-ssr/')
    .setPublicPath('/build-ssr')
    .enableVueLoader(() => {}, { version: 3 })
    .addEntry('ssr', './assets/ssr.js')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .enableSassLoader()

const config = Encore.getWebpackConfig();
config.target = 'node';
config.externals = [webpackNodeExternals()];

module.exports = config

Enabling SSR

To enable the ssr you need to add a configuration for your package config/packages/rompetomp_inertia.yaml file

rompetomp_inertia:
  ssr:
    enabled: true
    url: 'http://127.0.0.1:13714/render'

Building your application

You now have two build processes you need to run—one for your client-side bundle, and another for your server-side bundle:

encore build
encore build -- -c ./webpack.ssr.config.js

The build folder for the ssr will be located in public/build-ssr/ssr.js. You can change the path by changing the output path (setOutputPath) in your ./webpack.ssr.config.js

Running the Node.js Service

To run the ssr service you will need to run it via node.

node public/build-ssr/ssr.js

This will be available in http://127.0.0.1:13714 where this is the path we need to put in our ssr.url

Projects using this bundle

inertia-bundle's People

Contributors

aleksblendwerk avatar amprosius avatar andrelec1 avatar archistico avatar cydrickn avatar fluffydiscord avatar innocenzi avatar mercuryseries avatar romaixn avatar rompetomp avatar tofandel avatar ugurkorkmaz avatar xynnn avatar zed-magdy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

inertia-bundle's Issues

Required new props in config

Hello, with the last update (2.3.1) It's required to have that is a config file :

rompetomp_inertia:
  ssr:
    enabled: false
    url: 'http://127.0.0.1:13714/render'

Otherwise it throws an error. Maybe do a check to make it optional? Or add it in a recipe.

Returning doctrine entities

I think I'm missing something regarding sending a collection of entities to be rendered by Inertia. I have the following action:

    /**
     * @Route("/item/", name="item_list")
     */
    public function itemList(InertiaInterface $inertia, ItemRepository $repo)
    {
        return $inertia->render('ItemList', [
            'items' => $repo->findAll(),
        ]);
    }

Which works great when doing a hard browser refresh:

<div id="app" data-page="{&quot;component&quot;:&quot;ItemList&quot;,&quot;props&quot;:{&quot;items&quot;:[{&quot;id&quot;:1,&quot;name&quot;:&quot;Bike&quot;,&quot;price&quot;:&quot;150.00&quot;},{&quot;id&quot;:2,&quot;name&quot;:&quot;Car&quot;,&quot;price&quot;:&quot;2500.00&quot;},...}]},&quot;url&quot;:&quot;\/item\/&quot;,&quot;version&quot;:null}"></div>

But returns empty objects in the xhr response:

{"component":"ItemList","props":{"items":[{},{},{},{},{},{},{},{},{},{}]},"url":"\/item\/","version":null}

What am I missing?!

View data implementation

Hi again, I ran into a case where I could use some view data only for Twig, without passing it to the front-end.

public function render($component, $props = []): Response

Maybe just allowing a third argument, $viewData, would be enough? Then you would just add it to the compact method on the render.

$response->setContent($this->engine->render($this->rootView, compact('page', 'viewData')));

This way, the page variable will still be accessible to the Twig extension without any change, and we could use some other data for Twig.

It wouldn't cover all cases though. We could allow to do the same as shared data: allow adding view data in an event subscriber, and then merge the render method's viewData argument with the sharedViewData.

$viewData = array_merge($this->sharedViewData, $viewData);
$response->setContent($this->engine->render($this->rootView, compact('page', 'viewData')));

How to add Remember Me feature?

I've been desperately trying to make the "Remember Me' feature work with Inertia Bundle, but no luck so far. Any clue on how to have the REMEMBERME cookie set after login?

Argument #1 ($url) must be of type string, null given

Rompetomp\InertiaBundle\Service\Inertia::setSsrUrl(): Argument #1 ($url) must be of type string, null given, called in /app/var/cache/dev/ContainerLcyDPuy/App _KernelDevDebugContainer.php on line 1664

Happens when executing cache:clear (so right after installation). It is fixed by adding an empty string as url in the rompetomp_inertia.yaml config file.

Multiple root views

Hi,
We would like to use two root views in a project, as we have 2 separate frontend applications that need a different set of assets. As far as I can see, we can configure only a single root_view. Is it possible to have multiple?

Thanks for your work on the bundle!

Encore configuration examples for the available adapters

First of all, thanks for providing a way to use Inertia.js with Symfony, I really want to take it for a test drive.

And this might be out of scope for your project but I still wanted to give it a shot. This project's README says:

Find a frontend adapter that you wish to use here https://github.com/inertiajs. The README's are using Laravel's Webpack Mix. It's not hard translating this to Webpack Encore, just follow the documentation

Actually it's hard for me, as I haven't worked with Encore or Mix before and only have basic Webpack knowledge. So maybe you could provide example configurations for one or more of the available adapters.

Throws exception when navigating to a page via browser address bar

When using the Link component pages navigate correctly but when you input a page via the address bar I get the following exception:

12:36:38.709 critical | Uncaught PHP Exception Twig\Error\RuntimeError: "An exception has been thrown during the rendering of a template ("Response body is empty.")." at /Users/xxx/Sites/symfony/app/templates/app.html.twig line 5

It looks like it's dying at:

{{ inertiaHead(page) }}

Any ideas what's up, or any additional info you need?

Impossible to get an empty JSON object in the generated response

Hi Hannes,

not sure if you are up for this lengthy and rather inconvenient issue - as you said you were not using the project any more - but I'll try my luck anyway.

TLDR: see the PR, I guess... #20

I am currently using the bundle in a project where the Vue side of things expects an empty errors object to determine that there were no errors.

In the current state of the bundle, it seems impossible to get an empty object - in my case it's just a new ArrayObject() - all the way through to the JSON string that is finally returned by the render method of Inertia and I always end up with this warning in the console:

[Vue warn]: Invalid prop: type check failed for prop "errors". Expected Object, got Array

If my findings 🕵🏻‍♂️ are correct, this is due to two things:

I. When Symfony's Serializer is found in the serialize method of Inertia (as in my case), the objects in $page eventually make their way through AbstractObjectNormalizer which will return an empty array for empty objects - unless the key preserve_empty_objects is present in the context.

Relevant excerpt from AbstractObjectNormalizer:

    $data = [];

    [...]

    if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) {
        return new \ArrayObject();
    }

    return $data;

So my first proposal would be to add preserve_empty_objects to the context that is passed from the serialize method in Inertia, like so:

    if (null !== $this->serializer) {
        $json = $this->serializer->serialize($page, 'json', array_merge([
            'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
            AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function () { return null; },
            AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
        ], $context));
    } else {
        $json = json_encode($page);
    }

(Sidenote: if the Serializer is not found and json_encode is used instead, all is fine so far. But that's unlikely in a Symfony project...)

All right, one down.

II. Now at the very end of the serialize method, this happens:

    return json_decode($json, true) ?? [];

The second parameter is true, meaning JSON objects will be returned as associative arrays (as the PHP docs tell us), so this will once again turn all our carefully preserved objects in $json to arrays.

I don't think this was intended and you mainly set it to true to be able to return an array instead of the odd stdClass object that json_decode gives us when this is not set.

I think we can fix this by just replacing it with:

    return (array) json_decode($json, false);

Now I am finally getting my desired empty object in the final response sent by Symfony.

The downside is: this might break things for users of your bundle who are (unknowingly) relying on this behavior. But I would assume it was never your intention to actually cast or manipulate any objects or types.

What do you think?

Also maybe @innocenzi has some thoughts on this as well, as he added the Serializer feature.

No HMR on Symfony/Vuejs, use LiveReaload

I don't know if this is the best place to tell this ...
but
With the Symfony => Inertia => vuejs we loose the HMR
( you can't use the webpack-encore server because you need the symfony render ... )

Today using a HMR is obvious for dev, but with this stack you can't ...
So after some researche we can use a old cousin of HMR: LiveReload

3 Step to get ready :

  • npm install --save-dev webpack-livereload-plugin

  • in webpack.config.js add :

        if (!Encore.isProduction()) {
            Encore.addPlugin(new LiveReloadPlugin());
        }
  • in your main twig template add :
        {% if app.debug %}
            <script src="http://localhost:35729/livereload.js"></script>
        {% endif %}

Auto-update the Debug Toolbar on Inertia page load

Is it possible for this bundle to send the Symfony-Debug-Toolbar-Replace header (as described here: https://symfony.com/blog/new-in-symfony-4-1-self-updating-debug-toolbar) to replace the Symfony debug toolbar's "current page" with the request that responded to the Inertia.js page request (i.e. when clicking on an inertia powered link)?

I didn't see mention of this in the docs at all (and nothing came up when searching here on github).

Currently just the XHR section of the toolbar updates when navigating via inertia links.

Thanks for your work on this bundle!

Inertia.post sends parameters in body as json

I'm trying to use inertia to post to the login form but the form is sent in the body as json, which is not being understood by the symfony/security-bundle

Is there a way to convert the post request to use a normal HTTP payload instead, or to convert the received json to the $request->request automatically?

Inertia version is null

Hello, thanks for this nice bundle.

I have a little problem with settings version for Inertia. When i set in my subscriber version, i get always refresh.
for example if i set this as my version
$this->inertia->version('123')

i would except to not get refresh on every request. After a little debugging i found, that in InertiaListener.php theres a condition for checking versions. (i am not be dev) After dump, i can see version is null.

var_dump($request->headers->get('X-Inertia-Version')); var_dump($this->inertia->getVersion());
Results in:
/srv/app/vendor/rompetomp/inertia-bundle/EventListener/InertiaListener.php:38:string '123' (length=3) /srv/app/vendor/rompetomp/inertia-bundle/EventListener/InertiaListener.php:39:null

Lacking SSR-mode

Hi there!

I have been experimenting with this bundle but just realized that it is missing one of the key features of Inertia: the server-side rendering mode.

Is it on a roadmap? In the works?

If not, would you eventually accept a PR for it?

is_callable check in prop evaluation may try to execute something that's not callable

Since #42 landed I noticed a bug in this bundle.

When rendering certain data I ended up getting an ArgumentCountError, and in this specific case it was because PHP's system global function was trying to be called with 0 arguments. This seemed odd because we don't call system anywhere in our code.

It seems that this is due to the is_callable check that was added in #42. In our case, we had a value that was System for an item in an array (not a parent level prop, but an array of data that we were passing to Inertia).

is_callable will accept a string as the first parameter, and if that string is a global function name (which system is, https://www.php.net/system) is_callable will return true and then try to execute that function in the call_user_func that follows (https://github.com/rompetomp/inertia-bundle/blob/master/Service/Inertia.php#L160) which is probably not the intent here.

https://3v4l.org/I9QKY

The previous behavior of checking if the prop was a Closure didn't have this issue. I suspect anyone that has basic string props that happen to match a global PHP function name will also run into this issue of inertia trying to execute PHP functions when the prop itself was just a simple string.

PHP 8.3 - Symfony 7

  • rompetomp/inertia-bundle[0.1.0, ..., 0.5.0] require php ^7.1 -> your php version (8.3.0) does not satisfy that requirement.
  • ....

and so on.
Is it possible to update for symfony 7?

Default serializer, output nested entity

I have a entity that contains another entity...
I load the entity througth doctrine and passe it to the inertia render.

$transaction = $this->getDoctrine()->getRepository(Transaction::class)->find($id);
return $inertia->render('transaction/show', [
            'entity' => $transaction
        ]);

But when i inspect the entity with the vue-devtool a found some properties in it ( seem from doctrine ? ) !
image

So...
I think we are all ok with the fact we didn't want __cloner__, __initializer__ and __isInitialized__ ...

But, its my jobs to sananitize my object before sending it to inertia ( and the sf serializer ) ?
I have to use a real serializer at this point ?
Ok we can concidere this like a bug in this vendor and we need to finding a walkaround (PR) ?

Shared properties

Hi, I'm quite confused about shared properties in this bundle. They seem to not be added in the response?

public function render($component, $props = []): Response
{
$request = $this->requestStack->getCurrentRequest();
$url = $request->getRequestUri();
$only = array_filter(explode(',', $request->headers->get('X-Inertia-Partial-Data')));
$props = ($only && $request->headers->get('X-Inertia-Partial-Component') === $component)
? self::array_only($props, $only) : $props;
array_walk_recursive($props, function (&$prop) {
if ($prop instanceof \Closure) {
$prop = $prop();
}
});
$version = $this->version;
$page = compact('component', 'props', 'url', 'version');
if ($request->headers->get('X-Inertia')) {
return new JsonResponse($page, 200, [
'Vary' => 'Accept',
'X-Inertia' => true,
]);
}
$response = new Response();
$response->setContent($this->engine->render($this->rootView, compact('page')));
return $response;
}

I think adding $props = array_merge($this->sharedProps, $props); at the start of the function would handle it correctly.

PHP 8.0 compatibility

I would like to add compatibility for 8.0 in composer.json.

That means there also have to be tests on 8.0 though...

Currently PHPUnit 6.5 is used in the CI script which doesn't support 8.0, I propose that can be bumped up to the latest greatest 9.x - might need some minor changes in the tests but that should be trivial.

Also I couldn't find any info on whether Travis CI supports 8.0. Are you okay with replacing the current usage of Travis CI with an equivalent GitHub action?

If that sounds like a good approach to you, I can prepare a PR. Also I haven't really tried it on 8.0 yet but I doubt the actual code needs any changes. Will see...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.