Coder Social home page Coder Social logo

nuxt-ssr-cache's Introduction

nuxt-ssr-cache

NPM version

Cache middleware for nuxt's SSR rendering.

Setup

npm install nuxt-ssr-cache

or

yarn add nuxt-ssr-cache

then inside your nuxt.config.js add cache config:

module.exports = {
  // If you provide a version, it will be stored inside cache.
  // Later when you deploy a new version, old cache will be
  // automatically purged.
  version: pkg.version,

  // ....

  modules: [
      'nuxt-ssr-cache',
  ],
  cache: {
    // if you're serving multiple host names (with differing
    // results) from the same server, set this option to true.
    // (cache keys will be prefixed by your host name)
    // if your server is behind a reverse-proxy, please use
    // express or whatever else that uses 'X-Forwarded-Host'
    // header field to provide req.hostname (actual host name)
    useHostPrefix: false,
    pages: [
      // these are prefixes of pages that need to be cached
      // if you want to cache all pages, just include '/'
      '/page1',
      '/page2',

      // you can also pass a regular expression to test a path
      /^\/page3\/\d+$/,

      // to cache only root route, use a regular expression
      /^\/$/
    ],
    
    key(route, context) {
      // custom function to return cache key, when used previous
      // properties (useHostPrefix, pages) are ignored. return 
      // falsy value to bypass the cache
    },

    store: {
      type: 'memory',

      // maximum number of pages to store in memory
      // if limit is reached, least recently used page
      // is removed.
      max: 100,

      // number of seconds to store this page in cache
      ttl: 60,
    },
  },

  // ...
};

redis store

module.exports = {
  // ....
  cache: {
    // ....
    store: {
      type: 'redis',
      host: 'localhost',
      ttl: 10 * 60,
      configure: [
        // these values are configured
        // on redis upon initialization
        ['maxmemory', '200mb'],
        ['maxmemory-policy', 'allkeys-lru'],
      ],
    },
  },
}

Uses cache-manager-redis under the hood.

memcached store

module.exports = {
  // ....
  cache: {
    // ....
    store: {
      type: 'memcached',
      options: {
        hosts: ['127.0.0.1:11211'],
      },
    },
  },
}

Uses cache-manager-memcached-store under the hood.

multi cache (layered)

module.exports = {
  // ....
  cache: {
    // ....
    store: {
      // multi cache stores pages in all caches
      // later tries to read them in sequential order
      // in this example it first tries to read from memory
      // if not found, it tries to read from redis
      type: 'multi',
      stores: [
        { type: 'memory', /* ... */ },
        { type: 'redis', /* ... */ },
      ],
    },
  },
}

License

MIT

nuxt-ssr-cache's People

Contributors

arash16 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

nuxt-ssr-cache's Issues

about redisCache

sorry i don't know why use redis client and quit:

function redisCache(config) {
  if (config && Array.isArray(config.configure)) {
    const redis = require('redis')
    const client = redis.createClient(config.client)
    config.configure.forEach(function(options) {
      client.config('set', ...options, function(err, result) {
        if (err || result !== 'ok') {
          console.error(err)
        }
      })
    })
    client.quit()
  }

  return cacheManager.caching({
    store: require('cache-manager-redis'),
    retry_strategy() {
      return undefined
    },
    ...config
  })
}

why don't:

function redisCache(config) {
  return cacheManager.caching({
    store: require('cache-manager-redis'),
    retry_strategy() {
      return undefined
    },
    ...config
  })
}

and maybe we don't need multi-cache?

--- update

and why need serialize cache string?

Can this work for client side?

Hi, this is already a good start point, thanks for this middleware.
But I would be even more happy if this could be used to display prerendered/cached pages on the client.
Because at the moment I have one page which displays a lot of data and needs massive api calls. I'm using nuxt-ssr-cache to not hurt the api that much, by caching the site and only reload data / rerender every 30 minutes. This works if the user goes directly to the page (server side rendered).
But if the client goes on front page first and lands through a nuxt-link on that page, the api calls are made again from client side.
Do you know if it is possible to also cache in this case?

Infinite reloading page with dev build progress bars

OS: MacOS
Node: 10.15
Nuxt: 2.6.2

Steps to reproduce:

  • Enable module
  • Run in cli: nuxt
  • Open localhost:3000
    After build finished I have infinite reloads:
    Image of Yaktocat

Module settings:

export const cache = {
  useHostPrefix: true,
  store: {
    type: 'memory',
    // maximum number of pages to store in memory
    max: 100,
    // number of seconds to store this page in cache
    ttl: 3600,
  },
  pages: [
    '/',
  ],
}

Memcached support?

Do you have plans to add memcached? I have tried to add it but it doesnt seem to work. I would love to see that implemented.

Why created, serverprefetch still getting called ?

nuxt.config.js
`
modules: [
'nuxt-ssr-cache',
'@nuxtjs/device',
'@nuxtjs/style-resources',
'@nuxtjs/apollo',
'vue-social-sharing/nuxt'
],

cache: {
    useHostPrefix: false,
    pages: [
        // these are prefixes of pages that need to be cached
        // if you want to cache all pages, just include '/'
        '/',
    ],

    store: {
        type: 'memory',
        max: 100,
        // number of seconds to store this page in cache
        ttl: 60,
    },
}, 

`
And the console still print the logs from created and so on
image

feature request: new store type 'none'

It will be good for local development. Developer can generate part of nuxt config like this:

cache: {
useHostPrefix: true,
pages: [
'/',
],
store: {
type: isDevelopment?'none':'memory',
max: 100,
ttl: 60,
},
},

as described in module that you used https://www.npmjs.com/package/cache-manager

Development environment
You may disable real caching but still get all the callback functionality working by setting none store.

Missing documentation

From the looks of it this looks like a very interesting package - so good job so far!

I do, however, think this documentation needs some more information on what it caches exactly. Does it cache my axios / async data calls? How do I purge the cache? What are the expected load time difference?

Cache only working per path and not considering host domain

Great work with this module! I have one problem though as it's only caching on path and not including host when it's adding and checking for cache.

On our localized site (localized with i18n) the result is that we get the same page (same localized content) on all domains (.se, .uk, .no) when the path i the same (e.g. the start page).

Anyway to solve this?

How to determine dynamic version

Hey,
I know I can set version: pkg.version,but it is inside nuxt.config. So to change it I have to reload app. In my case Im using dynamic pages from API Backend. I know current version in API,
Api update version of pages every time someone add page etc.

So question is:
Can I somehow set version dynamically, and without reloading nuxt app on server?
Like some server middleware?

Feature Request: Add support of mongodb store

Hi,

Firstly, I'd like to thank you so much for your extremely helpful module.

In my case, the site I am working on has large number of pages, so, in-memory solutions would not be suitable for me.

If this supports mongodb cache store, this will be a very big adding I believe.

Handling dynamic routes and clearing cache programmatically

Hello,

First of all, kudos for the module, it's being really helpful for me so far.

I have a couple of questions that I have not been able to resolve through the documentation.

How to handle dynamic routes?

I've a few pages that I'd like to cache while avoiding others, for my use case I've a dynamic set of routes with this structure:

/en/page-1
/en/page-2
/en/page-3
...

And I would like to cache them while avoiding to cache a route like /en/account. If I could make an API call to declare the list of pages to cache that would be great.

Clearing cache programmatically

Sometimes I'd like to force a cache clear when I change content from my CMS. I could do it using a cache system which I could alter from elsewhere like redis, but I'd like to avoid installing new services and keep the in memory cache. Is it feasible?

Thanks

ERROR Cannot redefine property: styles

Hi!
I faced with error "ERROR Cannot redefine property: styles" when nuxt-ssr-cache module is included in nuxt.config.js.
Maybe I doing something wrong, cuz this problem has not issues.

nuxt version 2.15.8.
nuxt-ssr-cache version 1.5.2.

My config:

cache: {
      version: pkg.version,
      pages: [
        /^\/$/,
        '/main'
      ],
      key(route, context) {
        const hostname = context.req.headers.host || context.req.headers['x-forwarded-host'];
        if (!hostname) {
          return null;
        }
        return hostname ? path.join(hostname, route) : route;
      },
      store: {
        type: 'memory',
        max: 100,
        ttl: 600
      }
 },

Issue with using nuxt-ssr-cache with Nuxt.JS universal-storage-module

Hi!

I would love to use this package but i have discovered a conflict.

If i use this package with Nuxt.js offical storage package universal-storage-module i receive the following error $storage is undefined and cannot use their package. It seems that there is a conflict between the two packages.

Is there a way to fix this.

Have a nice day:)

Regards Joel

Feature Request - A hook to clear cache based on a key

I would like to clear a page from cache at some point in the application life.

Suppose I have a cached questions page. If a new question gets created, I'd like a way to remove the page form the cache, so the newly posted question gets added to the new cache.

The cache will be removed either from memory or redis, depending on configuration.

idea: rewrite as a nuxt module

Hi @arash16.

I really like what you did for implementing renderer-level caching for Nuxt. Nice job 👍

Nuxt.js is designed to be extended via modules. While providing API for using programmatically I highly recommend implementing it as a module.

Read more: https://nuxtjs.org/guide/modules

Change should not be really hard. The module exports a function which has access to all internals:

import { cacheRenderer } from './middleware'

export default function (options) {
  cacheRenderer(this.nuxt, options)
}

Happy hacking :)

Only homepage caching

Hello
I want to cache only my home page that is "/"

How can I do this?

    pages: [
      // you can also pass a regular expression to test a path
      '/'
    ],

The code above is caching all pages.

caching not working

I tried to use it but it is not working, the component which wraps a axios call still calls the webservice. I also tried to cache all pages by using just '/'. any idea what could be the issue? Thank you

nuxt config:

const isProduction = (process.env.NODE_ENV === 'production')  
const host = (isProduction) ? 'api.xxx.com' : 'localhost'
const scheme = (isProduction) ? 'https' : 'http'
const baseUrl = (isProduction) ? '${scheme}://${host}/rest-api' : '${scheme}://${host}:8080/vrmandat-rest-api'
export default {
  env: {
    api: {
      host: host,
      scheme: scheme,
      baseUrl: baseUrl
  },
  payment: {
  stripe: {
       mode: 'test',
       test: {
         publicKey: 'pk_test_xxx'
       },
       live: {
         publicKey: ''
      }
     }
   }
  },
  mode: 'universal', 
 head: { 
   title: process.env.npm_package_name || '', 
    meta: [ 
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 
    ], 
    link: [ 
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 
    ],
    script: [ 
      { src: 'https://www.paypalobjects.com/api/checkout.js' } 
    ] 
  }, 
  css: [ 
    '@/assets/styles/global.scss'
   ], 
  plugins: [
    '~/plugins/axios.js',
    '~/plugins/validate.js',
    '~/plugins/mq.js',
    '~/plugins/global.js',
    '~/plugins/print.js'
  ],
  buildModules: [
  ],
  modules: [
    'bootstrap-vue/nuxt',
    '@nuxtjs/axios',
    '~/modules/nuxt-auth/lib/module/index.js',
    'nuxt-i18n',
    'nuxt-stripe-module',
    'nuxt-ssr-cache',
  ],
  axios: {
    baseURL: '${baseUrl}/',
    https: (isProduction) ? true : false,
    progress: true,
    debug: false
  },
  /*
  ** Build configuration
  */
  build: {
    // Add exception
    transpile: [
      'vee-validate/dist/rules'
    ],
    /*
    ** You can extend webpack config here
    */
    extend (config, ctx) {
    }
  },
  /*
  ** Router configuration
   */
  router: {
    middleware: [
      'auth',
      'auth-refresh'
    ]
  },
  /*
  ** Auth configuration
   */
  auth: {
    plugins: [
      '~/plugins/auth.js'
    ],
    redirect: {
      login: '/auth/login',
      logout: '/',
      home: '/',
      callback: '/auth/callback'
    },
    strategies: {
      local: {
        _scheme: 'local',
        name: 'local',
        endpoints: {
          login: {
            url: `${baseUrl}/auth/getAccessToken`,
            method: 'post',
            propertyName: false
          },
          refresh: {
            url: '${baseUrl}/auth/refreshAccessToken',
            method: 'post',
            propertyName: false
          },
          logout: {
            url: `${baseUrl}/auth/logout`,
            method: 'post',
            propertyName: false
          },
          user: {
            url: '${baseUrl}/auth/user',
            method: 'get',
            propertyName: false
          }
        },
        tokenRequired: true,
        tokenType: 'Bearer',
        tokenName: 'Authorization',
        globalToken: true,
        accessTokenKey: 'access_token',
        refreshTokenKey: 'refresh_token',
        automaticRefresh: true,
        expiration: {
          factor: 0.9,
          timeFormat: 'seconds'
        }
      }
    }
  },
  /*
  ** i18n configuration
   */
  i18n: {
    locales: [
      {
        code: 'en',
        name: 'English',
        file: 'en.json'
      },
      {
        code: 'de',
        name: 'Deutsch',
        file: 'de.json'
      },
      {
        code: 'fr',
        name: 'Français',
        file: 'fr.json'
      },
      {
        code: 'it',
        name: 'Italiano',
        file: 'it.json'
      }
    ],
    lazy: true,
    defaultLocale: 'de',
    langDir: 'locales/'
  },
  stripe: {
    version: 'v3',
    publishableKey: 'pk_test_xxx'
  },
  
  cache: {
	    // if you're serving multiple host names (with differing
	    // results) from the same server, set this option to true.
	    // (cache keys will be prefixed by your host name)
	    // if your server is behind a reverse-proxy, please use
	    // express or whatever else that uses 'X-Forwarded-Host'
	    // header field to provide req.hostname (actual host name)
	    useHostPrefix: false,
	    pages: [
	      // these are prefixes of pages that need to be cached
	      // if you want to cache all pages, just include '/'
	      '/charts/alter',
	    ],

	    store: {
	      type: 'memory',

	      // maximum number of pages to store in memory
	      // if limit is reached, least recently used page
	      // is removed.
	      max: 100,

	      // number of seconds to store this page in cache
	      ttl: 60,
	    },
	  },
}

package.json:

{
  "name": "app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "test": "jest"
  },
  "dependencies": {
    "@nuxtjs/auth": "^4.8.4",
    "@nuxtjs/axios": "^5.6.0",
    "bootstrap": "^4.3.1",
    "bootstrap-vue": "2.1.0",
    "highcharts": "^7.2.1",
    "highcharts-3d": "^0.1.7",
    "highcharts-vue": "^1.3.5",
    "libphonenumber-js": "^1.7.23",
    "moment": "2.24.0",
    "nuxt": "2.10.2",
    "nuxt-i18n": "^6.0.2",
    "nuxt-ssr-cache": "^1.5.1",
    "nuxt-stripe-module": "^2.0.0",
    "popper.js": "^1.15.0",
    "v-click-outside": "^2.1.3",
    "vee-validate": "^3.0.3",
    "vue-html-to-paper": "^1.1.1",
    "vue-mq": "^1.0.1",
    "vue-multiselect": "^2.1.6",
    "vue-router": "^3.1.3",
    "vue-script2": "^2.1.0"
  },
  "devDependencies": {
    "@vue/test-utils": "^1.0.0-beta.27",
    "babel-jest": "^24.1.0",
    "jest": "^24.1.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^7.3.1",
    "vue-jest": "^4.0.0-0"
  }
}

[question] Are there plans to add Cache Headers usage

e.g. to support if-modified-since or if-not-match?

In order to do this, the cached page, would also need to store an etag value and/or a last-modified time...
Then a request can be compared to the stored values and an appropriate 304 response could be returned...

This would then allow CDN's to cache pages, but have clients still check if the content is updated.

Nuxt 3 Fatal Error

I'm seeing this warning when using memcached config in the cache. Not sure whats causing this.

Nuxt 3 Fatal Error

My current cache nuxt.config.js

  cache: {
    useHostPrefix: true,
    store: {
      type: 'multi',
      stores: [
        {
          type: 'memory',
          max: 100,
          ttl: 1800
        },
        {
          type: 'memcached',
          max: 1000,
          ttl: 3600,
          options: {
            hosts: ['127.0.0.1:11211'],
            maxValueSize: 5242880
          }
        }
      ]
    },
    pages: ['/']
  },

When i remove memcached, i dont see this warning at all

Cache is not working

Hi,

We are running Nuxt app with default(recommenced) server. When using and configured nuxt-ssr-cache as defined in readme, it caches nothing with following configurations.

Am I missing something?

import webpack from 'webpack'
import cheerio from 'cheerio'
const nodeExternals = require('webpack-node-externals')
export default {
  mode: 'universal',
  server: {
    port: 80, // default: 3000
    host: '0.0.0.0' // default: localhost
  },
  loading: false,
  router: {
    middleware: ['redirect'],
    extendRoutes(routes, resolve) {
      routes.push({
        name: 'by_date',
        path: '/live/:date',
        component: resolve(__dirname, 'pages/live/index.vue')
      })
    }
  },
  /*
   ** Headers of the page
   */
  head: {
    title: 'title here',
    meta: [
      { charset: 'utf-8' },
      {
        name: 'viewport',
        content: 'width=device-width, initial-scale=1, viewport-fit=cover'
      },
      {
        hid: 'description',
        name: 'description',
        content:
          'description here'
      }
    ],
    link: [
      {
        rel: 'icon',
        type: 'image/x-icon',
        href: 'https://www.xyz.xyz/favicon.ico'
      }
    ],
    script: [
      {
        src: 'https://code.jquery.com/jquery-3.3.1.slim.min.js',
        type: 'text/javascript'
      },
      {
        src:
          'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js',
        type: 'text/javascript'
      }
    ]
  },
  icon: {
    iconSrc: 'https://www.xyz.xyz/favicon.ico',
    iconFileName: 'https://www.xyz.xyz/favicon.ico'
  },
  /*
   ** Global CSS
   */
  css: ['~/assets/scss/index.scss'],

  /*
   ** Plugins to load before mounting the App
   */
  plugins: [
    '~plugins/bootstrap.js',
    { src: '~/plugins/vue-slick', ssr: false }
  ],

  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/pwa',
    '@nuxtjs/dotenv',
    'cookie-universal-nuxt',
    ['@nuxtjs/google-tag-manager', { id: '', layer: 'dataLayer' }],
    [
      'nuxt-fontawesome',
      {
        imports: [
          {
            set: '@fortawesome/free-solid-svg-icons',
            icons: [
              'faCamera',
              'faExternalLinkAlt',
              'faBars',
              'faComment',
              'faCheck',
              'faArrowRight',
              'faChevronLeft',
              'faChevronRight',
              'faPlay',
              'faAlignLeft',
              'faTable',
              'faTh',
              'faFire',
              'faNewspaper'
            ]
          }
        ]
      }
    ],
    'nuxt-ssr-cache'
  ],
  cache: {
    useHostPrefix: false,
    pages: [
      '/'
    ],

    key(route, context) {
      // custom function to return cache key, when used previous
      // properties (useHostPrefix, pages) are ignored. return
      // falsy value to bypass the cache
    },

    store: {
      type: 'memory',

      // maximum number of pages to store in memory
      // if limit is reached, least recently used page
      // is removed.
      max: 100,

      // number of seconds to store this page in cache
      ttl: 600
    }
  },
  hooks: {
    // This hook is called before rendering the html to the browser
    'render:route': (url, result) => {
      if (url.startsWith('/embed/')) {
        this.$ = cheerio.load(result.html)
        this.$('#__nuxt')
          .removeAttr('data-server-rendered')
          .removeAttr('id')
        this.$(`head script`).remove()
        this.$(`body script[src^="/_nuxt/"][src$="js"]`).remove()
        this.$(`head link[href^="/_nuxt/"][href$="js"]`).remove()
        result.html = this.$.html()
      }
    }
  },
  /*
   ** Axios module configuration
   */
  axios: {
    // See https://github.com/nuxt-community/axios-module#options
  },

  /*
   ** Build configuration
   */
  build: {
    extractCSS: true,
    vendor: ['jquery', 'bootstrap'],
    plugins: [
      new webpack.ProvidePlugin({
        $: 'jquery'
      })
    ],
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {
      // Run ESLint on save
      if (ctx.isDev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/,
          options: {
            fix: true
          }
        })
      }
      if (ctx.isServer) {
        config.externals = [
          nodeExternals({
            whitelist: [/^vue-slick/]
          })
        ]
      }
    }
  }
}

ttl in memcached

Hi! Thank you for awesome module!

I try to use memcached as a store. But I can't find a way to put ttl to cache options.

function renderSetCache(){ return renderRoute(route, context) .then(function(result) { if (!result.error) { cache.setAsync(cacheKey, serialize(result)); } return result; }); }

In your code from middleware.js you don't put options with ttl in set method of the cache.

node-cache-manager-memcached-store package wait for this signature:

/**

  • Set a value for a given key.
  • @method set
  • @param {String} key - The cache key
  • @param {String} value - The value to set
  • @param {Object} [options] - The options (optional)
  • @param {Object} options.ttl - The ttl value. Default is 2592000 seconds
  • @param {Function} [cb] - A callback that returns a potential error, otherwise null
    */
    MemcachedClient.prototype.set = function (key, value, options, cb) {
    var opt = {
    ttl: 2592000
    }

Can you fix this or better will be to make a pull request?

Restart nuxt

When restarting nuxt, the cache is cleared, this is my mistake or so it is conceived. Is there an option when the cache is permanent

p.s. My error :)

Feature: cache key control

How about to add some function/module in nuxt config, to have ability control cache key and global version without rebuild app.

For examples:

  1. Caching by user, cookie session key as additional cache key
  2. Caching by app view settings stored in cookies (mobile/desktop, custom layouts, i18n etc)
  3. Flush cache if some global data change time point from redis or etc, is grater than cache time

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.