Giter VIP home page Giter VIP logo

ember-scoped-css's Introduction

ember-scoped-css

ember-scoped-css is a modern addon that allows you to isolate your CSS in a modular way with co-located scoped CSS. This is a build-time-only addon and therefore is fully supported if your app is built with Embroider.

With ember-scoped-css you can write your component styles in a co-located .css file next to your .hbs or .gjs/.gts files. Every selector you write in your styles is automatically scoped to the component. So you can develop your component with styles isolated from the rest of the application and you don't have to worry about CSS selectors collisions or issues with the CSS cascade.

If you want to read more specifics on how this addon achieves isolation with CSS you can read more in the detailed CSS isolation documentation

As selectors are scoped/renamed during the build process. So there is no performance hit when running the app.

The philosophy of ember-scoped-css is to stick as close to CSS and HTML as possible and not introduce new syntax or concepts unless it is absolutely necessary.

You may also find the docs on CSS @layer interesting. This build tool emits CSS in a @layer.

Compatibility

  • V2 addons
  • non-embroider apps
  • embroider apps
You Have ember-scoped-css ember-scoped-css-compat
ember-template-imports@v4 or [email protected]+ 0.19.0 10.0.0
ember-template-imports@v3 or [email protected] or rollup-plugin-glimmer-template-tag <= 0.18.0 <= 9.0.0
classic components <= 0.18.0 <= 8.0.0
ember < 4 <= 0.18.0 <= 8.0.0

Installation for a non-embroider ember app

npm install --save-dev ember-scoped-css ember-scoped-css-compat

Configuration

In your ember-cli-build.js, you can configure the behavior of scoped-css transformations within the app via

const app = new EmberApp(defaults, { 
  /* ... */ 
  'ember-scoped-css': {
    layerName: 'app-styles', // default: 'components', set to false to disable the layer
    additionalRoots: ['routes/'], // default: [], set this to use scoped-css in pods-using apps
  }
});

Note that supporting pods is opt in, because all apps can have their pods root directory configured differently.

Installation for an embroider app

npm install --save-dev ember-scoped-css ember-scoped-css-compat

Setup webpack:

// ember-cli-build.js
module.exports = async function (defaults) {  
  const app = new EmberApp(defaults, { /* ... */ });

  const { Webpack } = require('@embroider/webpack');

  return require('@embroider/compat').compatBuild(app, Webpack, {
    /* ... */
    packagerOptions: {
      webpackConfig: {
        module: {
          rules: [
            // css loaders for your app CSS
            {
              test: /\.css$/,
              use: [
                {
                  loader: require.resolve(
                    'ember-scoped-css/build/app-css-loader'
                  ),
                  options: {
                    layerName: 'the-layer-name', // optional
                  }
                },
              ],
            },
          ],
        },
      },
    },
  });
}

Installation for a V2 Addon

npm install --save-dev ember-scoped-css
  1. If you want to use .gjs/gts components, @embroider/addon-dev provides a plugin for you, addon.gjs().
An older approach

There is a deprecated plugin (superseded by @embroider/addon-dev) that uses ember-template-imports -- to use this follow the instructions from rollup-plugin-glimmer-template-tag addon.

This plugin is not recommended, and is archived.

  1. Add the following to your rollup.config.mjs:
+ import { scopedCssUnplugin } from 'ember-scoped-css/build';

// if you want to have some global styles in your addon then
// put them in the styles folder and change the path to the styles folder
// if there are no global styles then you can remove addon.keepAssets
- addon.keepAssets(['**/*.css']),
+ addon.keepAssets(['**/styles/*.css']),

// add the following to the rollup config
+ scopedCssUnplugin.rollup(),

Note that if you're using rollup-plugin-ts, scopedCssUnpulugin.rollup() must come before typescript(/*...*/)

Configuration

In the rollup config, you may pass options:

scopedCssUnplugin.rollup({ 
  layerName: 'utilities', // default: 'components', set to false to disable the layer
});

Usage

With ember-scoped-css you define styles in .css files that are colocated with your components

{{! src/components/my-component.hbs }}
<div data-test-my-component class='hello-class header'><b>Hello</b>, world!</div>
/* src/components/my-component.css */
.hello-class {
  color: red;
}

/* the :global() pseudo-class is used to define a global class. It mean that header class wont be scoped to that component */
.hello-class:global(.header) {
  font-size: 20px;
}

b {
  color: blue;
}

NOTE: that if you're using pods, css co-located with templates/routes/etc will need to be named styles.css

Passing classes as arguments to a component

There is a scoped-class helper that you can use to pass a class name as an argument to a component. The helper takes a class name and returns a scoped class name. scoped-class helper is replaced at build time so there is no performance hit when running the app.

{{! src/components/my-component.hbs }}
<OtherComponent @internalClass={{scoped-class 'hello-class'}} />
<OtherComponent @internalClass={{(scoped-class 'hello-class')}} />
<OtherComponent
  @internalClass={{concat (scoped-class 'hello-class') ' other-class'}}
/>

In gjs/gts/<template>, the above would look like:

import { scopedClass } from 'ember-scoped-css';

<template>
  <OtherComponent @internalClass={{scopedClass 'hello-class'}} />
  <OtherComponent @internalClass={{(scopedClass 'hello-class')}} />
  <OtherComponent
    @internalClass={{concat (scopedClass 'hello-class') ' other-class'}}
  />
</template>

Testing

As classes are renamed during the build process you can't directly verify if classes are present in your tests. To solve this problem you can use the scopedClass function from the ember-scoped-css/test-support module. The function takes the class names and path to the CSS file where are the classes defined and returns the scoped class names.

The path to the CSS file is always relative to the V2 addon root no matter where the test is located.

import { scopedClass } from 'ember-scoped-css/test-support';

test('MyComponent has hello-class', async function (assert) {
  assert.expect(1);

  await render(hbs`
    <MyComponent />
  `);

  const rewrittenClass = scopedClass(
    'hello-class',
    '<module-name>/components/my-component'
  );

  assert.dom('[data-test-my-component]').hasClass(rewrittenClass);
});

Linting

ember-scoped-css exports a ember-template-lint plugin with one rule scoped-class-helper. This lint rule is intended to help you prevent improper use of the scoped-class helper which might not be immediately obvious during regular development. You can read more information in the lint rules documentation

Steps for adding the rule to the project

  1. Add ember-scoped-css plugin to .template-lintrc.js
'use strict';

module.exports = {
	plugins: [
+    'ember-scoped-css/src/template-lint/plugin'
  ],
  1. Add scoped-class-helper rule to .template-lintrc.js
'use strict';

module.exports = {
	plugins: [
    'ember-scoped-css/src/template-lint/plugin'
  ],
  rules: {
+    'scoped-class-helper': 'error',
  }

License

This project is licensed under the MIT License.

ember-scoped-css's People

Contributors

candunaj avatar ember-tomster avatar github-actions[bot] avatar jakebixby avatar mansona avatar mattmcmanus avatar nullvoxpopuli avatar renovate[bot] avatar

Stargazers

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

Watchers

 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

Forkers

nullvoxpopuli

ember-scoped-css's Issues

Figure how to get rid of v1 addon / go full native JS

Today, ember-scoped-css is a v1 addon (with some v2esque things)
The advantage of the v1 addon is that it auto-installs itself, configures babel, and dumps all the CSS generated by it in to the vendor.css.

To migrate to a v2 addon, we'd want some structure like this:

  • ember-scoped-css
    • runtime/
      • allows importing of the scopedClass helper
    • buildtime/
      • requires adding a babel plugin to your babel config
      • requires adding a webpack/unplugin plugin to put the generated CSS in the app's dist

new package: ember-scoped-css-compat

  • uses ember-scoped-css to do v1 things like auto-installing

Will not compile css assets for gts files in app/components

given the CSS file app/components/foo.css

hbs and gjs files will compile appropriately. gts files will compile the component portion but not the css portion.

  • app/components/foo.gts
  • app/components/foo.gjs
  • app/components/foo.hbs

I tested this with both an embroider-strict and a classic-build app.

Add <style> tag support *within* each <template>

Update 3:

  • no scoped (this is default, so to specify is redundant)
  • :deep() for punching under the component boundary (such as when styling an external library)
  • global CSS would not be in components, ideally, but could be left "as is" via global attribute.
const Other = <template>
  <style>
    .button {
      color: red;
    }
  </style>
  <button class='button'>non export</button>
</template>;

<template>
  <style>
    .button {
      color: green;
    }
  </style>
  <Other/>

  <button class='button'>Default export</button>
</template>

Update 2: I don't think it's a good idea to support <style> as a vanilla tag in gjs/gts -- we don't have the bandwidth to implement support for that across the 10+ syntax parsers that the community needs to support.

Example for Update 2

Example, each component's style has its own scope.

const Other = <template>
  <style scoped>
    .button {
      color: red;
    }
  </style>
  <button class='button'>non export</button>
</template>;

<template>
  <style scoped>
    .button {
      color: green;
    }
  </style>
  <Other />
  <button class='button'>Default export</button>
</template>

Update: per discussion below, in-<template> styles now have the scoped attribute.
We'd want to include a lint for folks to enable.

  • forbid-global-style-tag or something like that -- that way folks can still do global styles in a component if they really want to

Removed after update 2 (see above)

Additionally, related to #34
these features should be able to work together:

<style>
  .button {
    color: red;
  }
</style>

const Other = <template>
  <style scoped>
    .button {
      color: blue;
    }
  </style>
  <button class='button'>non export</button>
</template>;

const Another = <template>
  <style scoped>
    .button {
      color: green;
    }
  <button class='button'>non export</button>
</template>;

<template>
  <Other />
  <Another />
  <button class='button'>Default export</button>
</template>

Here, precedence should be that:

  • Other is blue
  • Another is green
  • The button in the default export is red

There should be configurable ignore css glob

There is a hardcoded extension to ignore in scoped-css-preprocessor.js.

It should be configurable in a config.

Background

As we wanted to support ember-scoped-css side by side with ember-css-modules we moved all css files to *.module.css. And setup an extension for ember-css-modules to module.css. This extension needs to be ignored otherwise the scoped-css-preprocessor will check if co-located components exist for those files and that can be time-consuming.

Add <style> tag for module-scoped CSS in gjs/gts files

Example, all components share a style

<style>
  .button {
    color: red;
  }
</style>

const Other = <template>
  <button class='button'>non export</button>
</template>

<template>
  <Other />
  <button class='button'>Default export</button>
</template>

Updating Template before CSS causes template to not pick up scoped class name

I've observed some funky behavior within the new addon, specifically around creating elements with a style class before the class exists, the template never receives the scoped class definition, even after the class has been created and the template has been changed. Additional classes don't necessarily trigger a scoped class name update, only in certain cases where the element has some specific changes to it.

  1. Create a template file containing a div with a single style
// hbs
<div class="foo">foo</div>
  1. Create an accompanying css file with the style for the div
// css
.foo {
 	color: red;
 }
  1. Observe the rendered HTML does not have a scoped class name for foo
// html output
<div class="foo">foo</div>
  1. Update the template file with a second div with a second single style
// hbs
<div class="foo">foo</div>
<div class="bar">bar</div>
  1. Observe the rendered HTML after build has applied the scoped class name for foo
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar</div>
  1. Update the CSS file with a style definition for bar
// css
.foo {
 	color: red;
 }
 .bar {
 	color: green;
 }
  1. Observe the rendered HTML after build to see there has been no change to the scoped class definitions.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar</div>
  1. Save the template file with no changes
// hbs
<div class="foo">foo</div>
<div class="bar">bar</div>
  1. Observe the rendered HTML after build to see there has been no change to the scoped class definitions.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar</div>
  1. Add a single space character after "bar" and save the template file
// hbs
<div class="foo">foo</div>
<div class="bar">bar </div>
  1. Observe the rendered HTML after build to see the first definition is scoped, but there has been no change to the second scoped class definition.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar </div>
  1. Add a div with no styles after the "bar" div and save the template file
// hbs
<div class="foo">foo</div>
<div class="bar">bar </div>
<div>baz</div>
  1. Observe the rendered HTML after build to see there has been no change to the scoped class definitions.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar </div>
<div>baz</div>
  1. Add a "baz" style to the latest div and save the template file
// hbs
<div class="foo">foo</div>
<div class="bar">bar </div>
<div class="baz">baz</div>
  1. Observe the rendered HTML after build to see there has been no change to the scoped class definitions.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar </div>
<div class="baz">baz</div>
  1. Update the CSS file with a style definition for bar
// css
.foo {
 	color: red;
 }
 .bar {
 	color: green;
 }
.baz {
	color: blue;
}
  1. Observe the rendered HTML after build to see there has been no change to the scoped class definitions.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar </div>
<div class="baz">baz</div>

18. Add a "foo" style to the last div and save the template file
```hbs
// hbs
<div class="foo">foo</div>
<div class="bar">bar </div>
<div class="baz foo">baz</div>
  1. Observe the rendered HTML after build to see there has been no change to the second scoped class definition, but the third has updated.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar">bar </div>
<div class="baz_ee3b6cad0 foo_ee3b6cad0">baz </div>
  1. Add a second "bar" style to the second div and save the template file
// hbs
<div class="foo">foo</div>
<div class="bar bar">bar </div>
<div class="baz foo">baz</div>
  1. Observe the rendered HTML after build to see there has been no change to the scoped class definitions.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar bar">bar </div>
<div class="baz_ee3b6cad0 foo_ee3b6cad0">baz </div>
  1. Add a fourth div with a "bar" style and save the template file
// hbs
<div class="foo">foo</div>
<div class="bar bar">bar </div>
<div class="baz foo">baz</div>
<div class="bar">biz</div>
  1. Observe the rendered HTML after build to see there has been no change to the second scoped class definitions, but the new fourth div has generated a scoped definition.
// html output
<div class="foo_ee3b6cad0">foo</div>
<div class="bar bar">bar </div>
<div class="baz_ee3b6cad0 foo_ee3b6cad0">baz </div>
<div class="bar_ee3b6cad0">biz</div>

The second div with bar never updates, even though a newer div with the same style does. Definitely appears to be some sort of caching issue, hopefully these steps help!

CSS preprocessor is not active if the app is named differently from the package.json#name field

I ran in to this as a laziness when copying the classic-app in #174
and without changing code, you can set name in ember-cli-build.js:

module.exports = function (defaults) {
  const app = new EmberApp(defaults, {
    name: 'classic-app',

and the modulePrefix in config/environment.js:

module.exports = function (environment) {
  const ENV = {
    modulePrefix: 'classic-app',

this works with all features of ember-cli, but the css preprocessor is expecting the name to match the package.json#name field (and likely other file filtering as well).

I'd consider this a bug -- but the work-around is easy enough for now (rename all your imports of the custom name to match the package.json#name)

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • wyvox/action v1
  • wyvox/action v1
  • wyvox/action v1
  • wyvox/action v1
.github/workflows/fixture-tests.yml
  • wyvox/action v1
.github/workflows/plan-release.yml
  • actions/checkout v4
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • peter-evans/create-pull-request v6
.github/workflows/publish.yml
  • actions/checkout v4
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
npm
ember-scoped-css-compat/package.json
  • @babel/eslint-parser ^7.24.5
  • @babel/plugin-proposal-decorators ^7.23.0
  • @ember/optional-features ^2.0.0
  • @ember/string ^3.0.1
  • @ember/test-helpers ^3.2.0
  • @embroider/test-setup ^4.0.0
  • @glimmer/component ^1.1.2
  • @glimmer/tracking ^1.1.2
  • @nullvoxpopuli/eslint-configs ^4.0.0
  • broccoli-asset-rev ^3.0.0
  • concurrently ^8.2.2
  • ember-auto-import ^2.7.3
  • ember-cli ~5.10.0
  • ember-cli-dependency-checker ^3.3.1
  • ember-cli-inject-live-reload ^2.1.0
  • ember-cli-sri ^2.1.1
  • ember-cli-terser ^4.0.2
  • ember-load-initializers ^2.1.2
  • ember-page-title ^8.2.3
  • ember-qunit ^8.0.2
  • ember-resolver ^12.0.0
  • ember-source ~5.10.0
  • ember-source-channel-url ^3.0.0
  • ember-template-lint ^6.0.0
  • ember-try ^3.0.0
  • eslint ^8.50.0
  • eslint-plugin-ember ^12.1.0
  • eslint-plugin-n ^17.7.0
  • eslint-plugin-qunit ^8.1.1
  • loader.js ^4.7.0
  • prettier ^3.2.5
  • qunit ^2.20.0
  • qunit-dom ^3.0.0
  • webpack ^5.91.0
ember-scoped-css/package.json
  • @babel/eslint-parser ^7.24.5
  • @nullvoxpopuli/eslint-configs ^4.0.0
  • @tsconfig/ember ^3.0.7
  • @tsconfig/strictest ^2.0.5
  • @types/blueimp-md5 ^2.18.2
  • @typescript-eslint/eslint-plugin ^7.10.0
  • @typescript-eslint/parser ^7.10.0
  • concurrently ^8.2.2
  • ember-template-lint ^6.0.0
  • esbuild ^0.23.0
  • esbuild-plugin-vitest-cleaner ^0.5.1
  • eslint ^8.50.0
  • prettier ^3.2.5
  • typescript ^5.2.2
  • vitest ^1.6.0
  • webpack ^5.91.0
package.json
  • concurrently ^8.2.2
  • prettier ^3.2.5
  • release-plan ^0.9.0
  • node 22.4.1
  • pnpm 9.5.0
test-apps/classic-app/package.json
test-apps/embroider-app/package.json
test-apps/pods-classic-app/package.json
test-apps/pods-embroider-app/package.json
test-apps/v2-addon-ts/package.json
test-apps/v2-addon/package.json

  • Check this box to trigger a request for Renovate to run again on this repository

CSS layer should be configurable in a config

ember-scoped-css puts all CSS files inside a layer with a hardcoded name. While it was expected for the first application where we used the ember-scoped-css, it should be configurable in config.

Requirement

It should be configurable if the transformed CSS is wrapped in a CSS layer.
The name of the CSS layer should be configurable.

Scoping keyframes

Currently, CSS keyframes aren't scoped.

Requirement

keyframes in CSS files should be scoped with the suffix the same way as classes are renamed with suffix

The following CSS

@keyframe blink { ... }

will turn to

@keyframe blink_e1235345 { ... }

Enhance embroider support.

This will require that apps have an explicit babel config, or to set their babel plugins in their ember-cli-build.js

There have now been two PRs in which the effort to have "embroider compatibility" is far too much effort.

This due to how the embroider support is done via altering the wire-format of compiled templates.
We can't do this reliably, and this repo's upgrades are already held back as a result.
Here is one such example of the opcodes changing:

We need to be using transforms from: https://github.com/emberjs/babel-plugin-ember-template-compilation/#common-options that would allow us to use the existing template-transform code from the classic app side of things and get rid of all the current wire-format mangling.
The wire format is not public API to us, and we can't ever expect that it's stable.

Incompatible with `@embroider/addon-dev@v4` - transforms appear to not be working

Discovered here: #107
during a yolo-upgrade of all embroider deps to latest, the tests fail with this error:

not ok 1 Chrome 117.0 - [82 ms] - Integration | Component | Alert from v2-addon: it has scoped class
  ---
  actual: >
    message 
  expected: >
    message_e4b9579df
  stack: >

Which is resolved when downgrading @embroider/addon-dev to v3.2.0.

This needs investigation, but here is the changelog to begin investigation:

When using decorator-transforms, the rollup unplugin errors on "invalid syntax"

My babel.config.js:

module.exports = function (api) {
	api.cache(true);

	return {
		presets: [['@babel/preset-typescript']],
		plugins: [
			['@babel/plugin-transform-typescript', { allowDeclareFields: true }],
			'ember-template-imports/src/babel-plugin',
			'@embroider/addon-dev/template-colocation-plugin',
			['module:decorator-transforms', { runtime: { import: 'decorator-transforms/runtime' } }],
			'ember-concurrency/lib/babel-plugin-transform-ember-concurrency-async-tasks',
		],
	};
};

Rollup:

import { Addon } from '@embroider/addon-dev/rollup';
import { babel } from '@rollup/plugin-babel';
import json from '@rollup/plugin-json';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { defineConfig } from 'rollup';
import { glimmerTemplateTag } from 'rollup-plugin-glimmer-template-tag';

import { scopedCssUnplugin } from 'ember-scoped-css/build';

const addon = new Addon({
	srcDir: 'src',
	destDir: 'dist',
});

export default defineConfig({
	output: addon.output(),
	plugins: [
		json(),
		addon.publicEntrypoints([ /* ... */ ]),
		addon.publicAssets('public', { include: ['**/*.svg', '**/*.png'] }),
		addon.appReexports( /* ... */ ),
		babel({
			babelHelpers: 'bundled',
			extensions: ['.js', '.ts', '.gjs', '.gts'],
		}),
		nodeResolve({ extensions: ['.js', '.ts', '.gjs', '.gts'] }),
		addon.dependencies(),
		glimmerTemplateTag(),

		addon.hbs(),
		addon.keepAssets(['**/styles/*.css']),

		scopedCssUnplugin.rollup({ layerName: 'luna' }),

		addon.clean(),
	],
});

Make the computed suffixes more stable

Right now we use the full file path to generate the suffixes that are added to class names i.e. /Users/superman/git/best-company/frontend/button-component.css but that means that suffixes won't be stable across different computers.

We should use the project-relative path (which could just mean path from the closest package.json 🤔) and that way the suffixes would be more stable 👍

Add to the Glimmer / <template> tutorial

Once

Are implemented, this would provide an excellent one-stop for styles for the ember ecosystem for a variety of use cases, and would rival the capabilities of Svelte.
This would work well with a new "how to do CSS" in https://tutorial.glimdown.com
(this section isn't implemented in any form right now due to ember not having any way to do CSS)

Additionally, once implemented, we can RFC adoption to the app blueprint because of aforementioned variety of use cases.

Open Source this Addon

What does that look like?

  • Auditboard Security Review
  • npm publishing process?
  • License? Code of Conduct?
  • ?

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.