Code Monkey home page Code Monkey logo

Comments (48)

freddy38510 avatar freddy38510 commented on June 4, 2024

You should enable gzip or brotli on your server. Without compression, the total network bytes is really big, especially in your case.

There is a lot of problems the way you handle your css.

The only thing i can do is to encourage you to understand:

  • what is a render blocking resource
  • what is the critical rendering path

When you begin to be comfortable with these concepts, you can start to optimize your page speed performance.

Varvy is a good resource to accomplish that.

If you don't want to jump into that, just try to use only one stylesheet in addition to the vendor stylesheet.

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

Yeah i will study those concempts and i'll make them mine. By the way thank you for all you have done. You are a cool guy, it has been a pleasure to meet you!

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

I found that some css was managed from js so it would be added once js comes.
Also i found that i forgot to reset the extractCSS: false in my quasar.conf.js.

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Late to the party, trying to catch up with new findings: the FOUC was indeed caused by critical CSS not being available when needed.
Forcing extractCSS: false solved the problem because the whole CSS gets inlined, which is not optimal.

Seems like the critical example into the README, at least in our case, could not work unless you provide it with the dist CSS files.
That property is marked as optional on critical README, but it's not explained which is the default if it's not set (if any).

To fix the FOUC problem we added css: [`${distDir}/css/*`] to critical.generate options. Dunno if the glob can be narrowed further depending on the processed file, nor if this narrowing would have any sensible impact on the generation process performance or generated html.

Given that extractCss option default value is true when building for production, it could be a good idea to check if the problem apply to all Quasar projects and, if yes, update the README example to include the css option.
@freddy38510 Have you hit this problem in you projects too?

I'm also wondering why you used fse.outputFile(file, html) to overwrite the file currently processed by critical, instead of target option. I added target: file and seems everything works fine, but maybe I'm missing some edge cases.

Our final configuration:

      afterGenerate: async (files, distDir) => {
        await Promise.all(
          files.map(async (file) => {
            await critical.generate({
              inline: true,
              src: file,
              target: file, // <-- new
              base: distDir,
              css: [`${distDir.split(path.sep).join(path.posix.sep)}/css/*`], // <-- new
              ignore: {
                atrule: ["@font-face"],
                decl: (node, value) => /url\(/.test(value),
              },
            });
          })
        );
     }

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

PS: when running critical on our project afterGenerate hook, we get a Node error.

(node:28844) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 exit listeners added to [process]. Use emitter.setMaxListeners() to increase limit

Adding require('events').EventEmitter.defaultMaxListeners = 0; on the top of quasar.conf.js remove the warning, but 11 is the number of pages generated in our project, so this could be some kind of memory leak where listeners aren't released correctly.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

@IlCallo
I tried Critical with the option provided in the README on a default Quasar project, and the critical CSS was correctly generated. I didn't see any FOUC.

Critical is using Penthouse which is using Puppeteer to render the page in Chromium (headless by default).

The stylesheets are extracted from the rendered html.

Penthouse has blockJSRequests:true as a default, so if your CSS is injected by a script, it could not be included in the generated critical CSS.

Did you try Critical with this option ?

penthouse: {
  blockJSRequests: false
}

To go further we could try to see how the page is rendered in Penthouse using the screenshots and renderWaitTime options.

Concerning the memory leak issue i found this information in this example that could explain the issue:

By default penthouse opens urls called in parallel in different tabs in the same browser, for performance reasons. If you need to run many pages however your machine will at some point (~ 10 pages) start running out of resources (causing crashes, errors and/or slowdown).

The example suggest to setup a queue. I would like to keep the example in the README as minimal as possible.

What do you think about that ?

Edit: Oh, and you're totally right about the target option. I just didn't think to use it.

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

I would say the extracted CSS from lazy-loaded pages is required by the app JS chunk, and that could be the reason for the FOUC, even if I would say the SSR step should have already added that import into the index head tag...

Unluckily blockJSRequests: false did not help.

Adding an automatic queue machanism could be an interesting idea but I think it belongs to critical and penthouse to do so,
I read there were some discussion about concurrency anyway. I'll just wait for stuff to move in that area for now, since our websites are small and won't cause fatal errors but only warnings

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

I would be happy to reproduce this issue then see if the stylesheets are presents into the head tag of generated html by the extension.

Then, i would like to see how these pages are rendered in Puppeteer.

This surely help us to understand what is going on with CSS.

Could you give me some hints to reproduce the issue ?

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Don't think I will be able to do so this week, as I'm working on other aspects of SSG and shall catch up with some Quasar core stuff too, but will come back to these CSS problems when I manage to find some time

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Could you reopen the issue, in the meantime?

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Sure i can.

Don't bother you to do that. I was just asking for some hints about use case of CSS that could cause FOUC.

I will try at my side many ways of handling CSS in a default project.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

@IlCallo, @vandelpavel
Could you try to upgrade the extension and invoke it to enable inlining of critical CSS via prompts ?

As you can see, i added this feature in this commit. The README is updated too.

I switched to Critters to inline critical CSS. It should avoid memory leaking because it doesn't involve browser.

Does it solve the FOUC issue for you guys ?

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Nope, still present.
Maybe due to our project components structure, I guess we have to investigate this on our end.

(node:28844) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 exit listeners added to [process]. Use emitter.setMaxListeners() to increase limit

But this error went away, so still a win :)


Just a suggestion: it's usually better to centralize the configuration, so you should use the prompt result to initialize a property into ssg configuration object, instead of using it directly via api.prompts. When using the latter way, the configuration will be scattered in multiple files and the user could get confused if he wants to disable the feature later on.

Unluckily there is no clean way to do this, as there are no helpers to dynamically change quasar.conf.js.
I did this using regex into https://github.com/quasarframework/app-extension-typescript/blob/dev/extension/src/install.js if you wanna try.

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Another way, if you think the CSS inline is a thing everybody will need and enable, is to just enable it by default and ask people to disable it manually if they don't want it.
Previous version required to add some code and configuration, this version only require to add a property to false, much easier for everyone :)

Could be useful to give the user the ability to override Critters configuration too (I don't know if it will be ever needed, never used it)

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

@IlCallo,

Let me explain what i found:

When extractCSS option is set to true, styles included via an import code is extracted. This is good for production build, because we don't want all the Quasar styles inlined. Styles from <style> tags in our own SFC components are extracted too.

Now, think about async components. In this case, the extracted CSS is injected at client side. This is why you needed to provide all css assets to Critical, like you said in a previous comment.
It could be a good solution, but then we have extra requests at client side to inject the extracted stylesheets. There is also some duplicated CSS in this case.

Ok, so what is a good solution ? Anwser is: extract styles included via an import code, and inline styles from <style> tags in SFC components. These styles should be inlined at server-side (so at build-time), and not injected at client-side.

This is what i accomplished by playing with webpack rules and vue-style-loader. Styles are inlined at server side with an data-vue-ssr-id attribute. This way we avoid duplicate style injection on hydration.

The only downside of this behavior, is that the styles we are talking about should be injected in quasar.server-manifest.json file. Especially for async components. This is needed to be able to inline them at server side. I can't tell if it increases time to generate static pages in some cases when there is a lot of styles to inline and the quasar.server-manifest.json file grows a lot.

Right now, i'm thinking about a good way to provide the ability to enable/disable this feature. It could be an option inlineSFCStyle at quasar-conf-file.js. Or we could set an object to extractCSS option.

extractCSS: {
  normal: true, // styles from import code
  vue: false // styles from SFC components
}

In this example, internally we should keep extractCSS to boolean, because this is how Quasar is using this option.

// index.js
api.extendQuasarConf((conf, api) => {
  if (conf.build.extractCSS === void 0) {
    conf.build.extractCSS = true // default to true in production
    conf.build.__extractCSS = {
      normal: true,
      vue: true
    }
  } else if (typeof conf.build.extractCSS === 'object') {
    conf.build.__extractCSS = conf.build.extractCSS

    conf.build.extractCSS = true // should be a boolean
  } else {
    conf.build.__extractCSS = {
      normal: conf.build.extractCSS,
      vue: conf.build.extractCSS
    }
  }
}

// now we can use conf.build.__extractCSS for our webpack rules

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

And is quasar.server-manifest.json an automatic generated file? Or we should mark all files/globs to include for each async component? That's not clear from the explanation

I'd avoid to override extractCSS property as it's not much future proof. Adding your own property into the ssg object seems the best

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

quasar.server-manifest.json is an automatic generated file by the webpack plugin vue-server-renderer/server-plugin.

The name of this file is set here in Quasar.

This is the server bundle that will be passed to createBundleRenderer.

To inject styles to the server bundle, there is an option onlyLocals in css-loader that should be set to false for styles that are coming from SFC components. This option is set to true in Quasar for the webpack server config when extractCSS is true.

About extractCSS i think you are right.

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

I got sucked into some work projects, I'm slowly resuming my open source effort in these weeks.
Currently struggling to make one of our loaders work with SSR (and so this package SSG), will come back around here probably next months, sorry for stopping responding all of a sudden 😅

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Don't be sorry, i'm currently facing the same situation. You already helped me a lot in this project.

Could you tell me more about these loaders when you have some free time ? Is it related to inlined styles ?

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Nope, there were 2 distinct errors, related to Quasar SSR build which apparently share webpack entries between the client and server configuration generating a conflict, and about some of the code not being thought to run on node.

You can see them explained in the faq here:
https://github.com/dreamonkey/responsive-image-loader

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Seems like we finally managed to make everything work with your SSG AE 🥳
We're using Critical right now, but plan to work on checking out the new version based on Critters you proposed.
Do you plan to put the AE on NPM at some point, to be able to use versioning properly?

To make it work on both Linux and Windows, we had to change some stuff using join to get the right separators.

  • build > distDir becomes path.join('dist', 'ssg')
  • ssg > afterGenerate > critical > css becomes [`${distDir.split(path.sep).join(path.posix.sep)}/css/*`] as apparently glob entries won't work with window path separator (as distDir have using path.join) and obviously it won't work for mixed path separators

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Thanks you @IlCallo for the separator tips. I will try to push a commit about this in the following days. Same thing goes for publishing it on NPM. You're right, i think it's time to do it.

I'm glad to hear that you made your AE working with this one. I saw the commit you made, good catch about deferred scripts and onload event.

These last weeks i miss a lot of time to work on this project, and thus showing the progress on critical css thing. I shouln't have more time to be able to do it until march, as i'm working hard on an other project.

from quasar-app-extension-ssg.

IlCallo avatar IlCallo commented on June 4, 2024

Yeah, also though about asking you to add a "do not defer these scripts" option, but we noticed is an SSR thing outside of this package scope, so we just fixed it at our end

No problem, we'll probably be in the same situation until February end :)
If not, we can still propose some PRs or fork the repo, realigning it when you come back

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Exactly, i already faced this "issue" with vue-lazy-hydration.

If it can help you, there is an issue on vuejs about this. And a PR that is not yet merged.

Feel free to propose some PRs, i will take time to review and merge them.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

I was just reading again what you said about path separators. Previously, i removed the section about critical in the readme. So i don't need to make any commits about this. But, i will keep in mind that glob entries should use posix paths.

I hope to have time to refactor a little the changes i made localy for inlined styles. Right know this is working, but i'm not happy about the way i made this.

I should reconsider the provided options to inlined styles from SFC components and those included via import code. Also, i was forced to rewrite auto-import loader from Quasar. I would like to avoid that as most as possible.

Ps: Tomorrow i will publish this package to NPM ;)

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

I dug into @freddy38510 's code and i almost figured out how the critical code is managed. Tell me if i'm wrong:

  • Inside the index.js you create a generator obj
  • The main part comes when you call await generator.generateAll() where for each route you:
    • render the page
    • call inlineCriticalCss and process critters on it
    • minify
    • and finally create a index file for every page

I need to be sure on how critters works on ssr. Did you already know how to implement it?

I'v run yarn add -D critters-webpack-plugin
Included it in my quasar conf const Critters = require('critters-webpack-plugin');
But then i don't really know how i need to proceed to inject the plugin and how to set the configuration to work in ssr.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

The steps you described are exact. The generation of static html pages is done after Quasar build your app in ssr mode.

However, critters-webpack-plugin only works for html files generated by html-webpack-plugin. Which is not the case for the html files we are talking about. This is why i forked Critters to be able to run it as a standalone.

But, as is, this will not fix all FOUC issues in case of async components with their own styles. I explained that in this post.

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

I just noticed that criters docs says It also means Critters inlines all CSS rules used by your document, rather than only those needed for above-the-fold content.
Have you modified the fork to be able to just inline the above the folder section or are you inlining all the style?

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

After a little discussion we think that the proper way to inline critical paths in ssr is:

  • run the build
  • in the after build hook you need to render the html page and give it to critical (or critters depends if it only inlines the above the fold portion)
  • then critical would process the page and generate a new html file

Here is where the concept splits in half:

SSG

  • you can directly use these files as you are already doing in this AE

SSR

Now thing becomes less crystal clear for me to somehow allows to use both SSG and SSR or just develop a mechanism that works only for SSR.
At the end you have all those final html file with critical style inlined that you don't really need for SSR.
You just need to extract the the style portion (using target option), save it inside an object with all the references o all the pages and inject it inside the page so you can benefit of SSR but also remove some FOUC problems.

For components with third party content
I guess that you can decide to process the page with some dummy content to fill it or just inline everything.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

In the fork i didn't change anything related to Critters behaviors about CSS.

You could always disable inlining critical CSS with Critters by setting ssg.criticalCss option to false.
Then use afterGenerate hook like this for example:

// quasar.conf.js

const critical = require('critical')

module.exports = function (ctx) {
  return {
    // ...

    ssg: {
      afterGenerate: async (files, distDir) => {

        await Promise.all(
          files.map(async (file) => {
            const { html } = await critical.generate({
              inline: true,
              src: file,
              target: file,
              base: distDir,
              css: [`${distDir.split(path.sep).join(path.posix.sep)}/css/*`], // include all your css files
              ignore: {
                atrule: ["@font-face"],
                decl: (node, value) => /url\(/.test(value),
              },
            });
          })
        );
      };
    },

    // ...
  }
}

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

Yes we know that, i just wanted to point (if I'm not wrong) that critters differs from critical for inline all the css and not just the above the fold.
If you just want to inline all the style you could use extractCSS:false inside the quasar.conf.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Setting extractCSS to false inline all the Quasar style, not just the one used by the page.

Let me try to explain what is the current situation about CSS, Quasar, and SSR.

  • In SSR mode the content of pages is generated by bundleRenderer from vue-server-renderer package. bundleRenderer has a feature for automatic critical CSS. But, this feature inline critical css only styles coming from inside *.vue single file components (called SFC) with tag <style>.

  • Quasar imports directly its own style from a non-vue-file.

  • Styles coming from async components can't be processed by critical or critters as they are not available when needed. This is what causes FOUC issue, even if we are using these tools.

  • The way how Quasar is managing webpack rules, avoid the ability to both extract css from Quasar style into a separate CSS file AND inline styles from your own single file components (*.vue files).

This last point is the feature i want to add in future commits. Then, users will be able to inline or extract styles from SFC, and inline or extract styles directly imported (included the one from Quasar). This will be particularly useful for async components with their own styles (not the one from Quasar) that critical or critters can't handle properly. This way bundleRenderer could makes its automatic critical CSS magic trick without duplicated styles.

For extracted CSS into a separate CSS file, i will let users choose between the implemented feature using critters or their own way by using afterGenerate() hook.

PS: I found an article that compare critters and critical. There is not a lot of difference between them concerning the size of the critical CSS. I don't know exactly why, critters is even doing better than critical on this point.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

@vandelpavel I was reading again your post. For the SSR part, did you mean to pre-generate critical css at build-time, then inline this critical css during the server side rendering in response to each request from the client ?

from quasar-app-extension-ssg.

vandelpavel avatar vandelpavel commented on June 4, 2024

Yes, we need to generate the html files like you do inside this extension just to be able to run critters. Then being in SSR we don't need those files anymore. We just need the css to inline while the rest is loaded like normal.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

Hi,

Now I face the same issue after using this SSG extension. The page loads with text and icon but CSSs are loaded after page loading.

Any solution?

Thanks
@freddy38510

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Hi @bloodchen,

Are you using Quasar with Vite or Webpack ?

If you could provide a reproducible repository that would be great.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

It's webpack. Unfortunately I cannot provide the repo since it's a private project. I am happy to debug if you can provide some direction.

Thanks

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

The style that loads too late, is it the style from Quasar or your own style ?
And where does it come from ? Is it a Vue SFC component ? Is it an asynchronous component ?

Which ssg extension options have you enabled ?

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

The style that loads too late, is it the style from Quasar or your own style ? And where does it come from ? Is it a Vue SFC component ? Is it an asynchronous component ?

they are all standard quasar components. For example:

<q-form
@submit="submitSearch"
:style="{
marginTop: isMobile ? '32px' : '40px',
}"
>


<q-btn
icon="search"
class="bg-primary tc-2"
size="lg"
@click="submitSearch"
no-caps
/>



I can see the button has "Search" text on it then change to the search icon.

Which ssg extension options have you enabled ?
all default options.

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

Oh okay I see. I thought all styles were affected. I assume you are using webFont icons, that's why you have FOUC on icons.

You need to switch to svg icons, this is the best option for SSG.

If you were using Quasar with Vite, you could automatically import svg icons, which is very useful. But unfortunately I haven't implemented this feature for Webpack yet.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

This is really difficult to reproduce your issue blindly.

When you say "all styles are affected" does it include the style from the vendor.css file too ?

  • From your example, the class tc-2 is not a Quasar one. Where does it come exactly ? From an external stylesheet ? Inside a Vue SFC component ?

  • <q-form
    :style="{
    marginTop: isMobile ? '32px' : '40px',
    }"
    >

    isMobile would be always false when your app is generated at build-time. So you should see a change from 40px to 32px when first-loading the page on a mobile.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

This is really difficult to reproduce your issue blindly.

When you say "all styles are affected" does it include the style from the vendor.css file too ?

  • From your example, the class tc-2 is not a Quasar one. Where does it come exactly ? From an external stylesheet ? Inside a Vue SFC component ?

  • <q-form
    :style="{
    marginTop: isMobile ? '32px' : '40px',
    }"
    >

    isMobile would be always false when your app is generated at build-time. So you should see a change from 40px to 32px when first-loading the page on a mobile.

Hi, I have managed to produce a lite version of the project. Please check out here https://github.com/bloodchen/ssgtest

After clone, you just run "yarn" "quasar ssg dev" . Then you can see the page with the problems.

Hope you can nail down the problem.

Thanks

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

I just took a look, there is no real FOUC. Your issues are more about content jumping due to the use of Quasar Platform API and some misused classes.

You should keep in mind that the platform is unknown at build time. That's why it's better to stick to css with breakpoints.

I will create PR on your repository to show you what issues I was talking about.

Edit: PR is ready.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

Great. I have merged the PR and it works now ~~ . Thanks for the help.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

btw: any idea why setting header/toolbar other than default height will cause the jumping on page load? Any way to set a higher toolbar without this side effect?

from quasar-app-extension-ssg.

freddy38510 avatar freddy38510 commented on June 4, 2024

This is because at client-side the height of the QHeader component is calculated then dynamically added as "padding-top" to the QPageContainer component.

When using SSR or SSG you can hint of the height (in pixels) of the QHeader thanks to the height-hint prop.

<q-header style="height: 110px" height-hint="110">

Edit: PR created to demonstrate this.

from quasar-app-extension-ssg.

bloodchen avatar bloodchen commented on June 4, 2024

from quasar-app-extension-ssg.

Related Issues (20)

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.