GithubHelp home page GithubHelp logo

adopted-ember-addons / ember-cli-ifa Goto Github PK

View Code? Open in Web Editor NEW
54.0 5.0 53.0 2.88 MB

Ember CLI addon for injecting fingerprinted asset map file into Ember app

License: MIT License

JavaScript 87.75% HTML 7.73% Handlebars 4.51%
ember-cli ember ember-addon

ember-cli-ifa's Introduction

Build Status Code Climate

Ember-cli-ifa

Inject fingerprinted assetMap.json file into your app and provide initializer, service, and helper to dynamically reference fingerprinted assets.

When to use this addon?

  1. If you have dynamic asset file names returned from API and/or you build paths based on several properties.
  2. If you can't put your asset file names into css or to have path as static in your .js files.
  3. If you build your image/asset paths in a dynamic way, eg.
imagePath: computed(function() {
  return this.get('assetMap').resolve(`${this.get('image')}.png`);
})

Compatibility

  • Ember.js v3.12 or above
  • Ember CLI v2.13 or above
  • Node.js v10 or above

Installation

ember install ember-cli-ifa

Configuration

Enable addon in environment.js for specific environment.

module.exports = function(environment) {
  var ENV = {
    ...
    ifa: {
      enabled: true,
      inline: false,
    }
    ...
  };

In case you use s3 and manifest module for ember-cli-deploy, update their configurations in config/deploy.js to include json as a valid file.

module.exports = function(environment) {
  var ENV = {
    ...
    s3: {
      filePattern: function(context, pluginHelper) {
        let filePattern = pluginHelper.readConfigDefault('filePattern');
        return filePattern.replace('}', ',json}');
      },
      ...
    },
    manifest: {
      filePattern: function(context, pluginHelper) {
        let filePattern = pluginHelper.readConfigDefault('filePattern');
        return filePattern.replace('}', ',json}');
      },
      ...
    },
    ...
  };

Configure fingerprinting in ember-cli-build.js. Refer to the documentation of ember-cli for asset-compilation

fingerprint: {
  enabled: true, // set to true only in required environments
  generateAssetMap: true,
  fingerprintAssetMap: true
}

Note that if you use fastboot, this addon is automatically forced into inline: true mode. This is necessary, as otherwise fastboot could not easily access that data.

Usage

asset-map helper

If name is tomster-under-construction:

<img src={{asset-map (concat "assets/" name ".png")}} />

then it will generate something like assets/tomster-under-construction-da524c8bc9283f759ae640b68db81f24.png based on assetMap.json.

asset-map service

import Component from 'ember-component';
import service from 'ember-service/inject';

export default Component.extend({
  assetMap: service('asset-map'),

  key: null, // key passed as 'tomster-under-construction'

  // result will be assets/tomster-under-construction-da524c8bc9283f759ae640b68db81f24.png
  image: computed('key', function() {
    return this.get('assetMap').resolve(`assets/${this.get('key')}.png`);
  })
});

Storing assets in a sub-directory

If prepend option is added in fingerprint configuration block, it will be prepended into generated asset path in the index.html.

// ember-cli-build.js
// ...
var app = new EmberApp(defaults, {
  fingerprint: {
    prepend: '/blog/'
  }
});

/blog will be prepended to the assetMap file path in the index.html.

inline option

If inline: true is specified in the config, contents of assetMap file will be inline into index.html.

This might save one request to assetMap.json, but will increase overall size of index.html file, so use carefully.

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

ember-cli-ifa's People

Contributors

abhilashlr avatar alexlafroscia avatar alonski avatar bnitobzh avatar dependabot-preview[bot] avatar dependabot[bot] avatar drewtempelmeyer avatar headquarters avatar jrjohnson avatar krallin avatar mansona avatar nullvoxpopuli avatar ruslanzavacky avatar turbo87 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

Watchers

 avatar  avatar  avatar  avatar  avatar

ember-cli-ifa's Issues

assetmap url when prepend is empty

fingerprint: {
      enabled: true
      fingerprintAssetMap: true,
      generateAssetMap: true,
      prepend: ''
}
rootUrl: '/',
ifa: {
      enabled: true,
      inline: false
}

If I open my app under a path which is not the root path, e.g. localhost:4200/de/intern/users fetching of the asset map file fails because it uses the empty prepend option and tries to fetch it relative the current path localhost:4200/de/intern/assets/assetMap-533afc92713400107780e8d5d67b0d89.json

I solved it by adding

  if ('/' !== assetMapFile.charAt(0)) {
    assetMapFile = `/${assetMapFile}`;
  }

to the initializer. But I am not sure if this will have any negative effects for other users?

Error on 0.6.0, after update

ember.debug.js:19750 TypeError: Cannot read property 'getAttribute' of null
    at Object.initialize (asset-map.js:16)
    at ember.debug.js:5556
    at ember.debug.js:5585
    at Object.visit [as default] (ember.debug.js:54608)
    at DAG.topsort (ember.debug.js:2578)
    at Class._runInitializer (ember.debug.js:5584)
    at Class.runInitializers (ember.debug.js:5545)
    at Class._bootSync (ember.debug.js:4771)
    at Class.domReady (ember.debug.js:4660)
    at Backburner.run (ember.debug.js:294)
➜  git:(greenkeeper/ember-cli-ifa-0.6.0) ✗ ember -v
ember-cli: 2.15.0
node: 8.5.0
os: darwin x64

package.js

  "devDependencies": {
    "babel-preset-env": "1.6.0",
    "babelify": "7.3.0",
    "blueimp-md5": "^2.10.0",
    "body-parser": "^1.14.1",
    "braintree": "2.3.0",
    "broccoli-asset-rev": "^2.2.0",
    "chai-jquery": "^2.0.0",
    "chalk": "^2.0.0",
    "color": "^2.0.0",
    "connect-restreamer": "1.0.3",
    "d3-array": "1.2.1",
    "d3-collection": "1.0.4",
    "d3-scale": "1.0.4",
    "dotenv": "^4.0.0",
    "ember-ajax": "^3.0.0",
    "ember-basic-dropdown": "0.33.6",
    "ember-browserify": "1.2.0",
    "ember-buffered-proxy": "^0.7.0",
    "ember-changeset": "1.3.0",
    "ember-cli": "2.15.0",
    "ember-cli-app-version": "^3.0.0",
    "ember-cli-autoprefixer": "0.8.1",
    "ember-cli-babel": "^6.7.1",
    "ember-cli-bugsnag": "~1.3.0",
    "ember-cli-chai": "0.4.3",
    "ember-cli-chartist": "^1.0.0",
    "ember-cli-clipboard": "0.8.0",
    "ember-cli-content-security-policy": "1.0.0",
    "ember-cli-dependency-checker": "^2.0.1",
    "ember-cli-deploy": "1.0.0",
    "ember-cli-deploy-build": "1.0.0",
    "ember-cli-deploy-display-revisions": "1.0.0",
    "ember-cli-deploy-gzip": "1.0.0",
    "ember-cli-deploy-notifications": "0.2.0",
    "ember-cli-deploy-revision-data": "1.0.0",
    "ember-cli-deploy-s3": "1.1.0",
    "ember-cli-deploy-s3-index": "1.0.0",
    "ember-cli-deprecation-workflow": "0.2.3",
    "ember-cli-eslint": "4.2.1",
    "ember-cli-htmlbars": "^2.0.3",
    "ember-cli-htmlbars-inline-precompile": "^1.0.0",
    "ember-cli-ifa": "^0.6.0",
    "ember-cli-inject-live-reload": "^1.4.0",
    "ember-cli-is-component": "0.5.0",
    "ember-cli-less": "1.5.5",
    "ember-cli-mirage": "0.3.4",
    "ember-cli-mocha": "0.14.4",
    "ember-cli-moment-shim": "3.5.0",
    "ember-cli-page-object": "0.9.0",
    "ember-cli-release": "0.2.9",
    "ember-cli-sri": "^2.1.0",
    "ember-cli-test-loader": "2.2.0",
    "ember-cli-uglify": "^2.0.0",
    "ember-composable-helpers": "^2.0.1",
    "ember-concurrency": "0.8.7",
    "ember-concurrency-test-waiter": "0.2.0",
    "ember-cookies": "0.0.13",
    "ember-data": "2.7.0",
    "ember-disable-proxy-controllers": "^1.0.1",
    "ember-exam": "0.8.0",
    "ember-export-application-global": "^2.0.0",
    "ember-font-awesome": "4.0.0-alpha.10",
    "ember-group-by": "0.0.4",
    "ember-i18n": "5.0.2",
    "ember-in-viewport": "2.1.1",
    "ember-inflector": "^2.0.0",
    "ember-load-initializers": "^1.0.0",
    "ember-moment": "7.4.1",
    "ember-notify": "5.2.0",
    "ember-power-calendar": "0.5.0",
    "ember-power-select-with-create": "0.4.3",
    "ember-radio-button": "1.1.1",
    "ember-resolver": "^4.1.0",
    "ember-route-action-helper": "2.0.6",
    "ember-route-history": "0.1.3",
    "ember-simple-auth": "1.4.0",
    "ember-tether": "1.0.0-beta.0",
    "ember-truth-helpers": "1.3.0",
    "ember-windoc": "0.2.0",
    "eslint-plugin-ember-best-practices": "^1.0.0",
    "faker": "3.0.1",
    "flatten-hash": "^0.0.4",
    "git-repo-info": "1.4.1",
    "glob": "^7.1.1",
    "loader.js": "^4.0.1",
    "lodash": "^3.9.3",
    "moment": "2.18.1",
    "moment-timezone": "0.5.13",
    "onesky": "^0.1.6",
    "rsvp": "^4.6.1",
    "yargs": "9.0.1"
  },
  "ember-addon": {
    "paths": [
      "lib/google-fonts",
      "lib/intercom",
      "lib/locales-cleanup"
    ]
  },
  "greenkeeper": {
    "ignore": [
      "ember-cli",
      "ember-cli-page-object",
      "ember-data",
      "faker",
      "lodash"
    ]
  },
  "dependencies": {}
}

bower.js

{
  "name": "",
  "dependencies": {
    "chartist-plugin-tooltip": "^0.0.15",
    "chartist": "~0.9.8",
    "ember-cli-moment-shim": "0.0.3",
    "ember-cli-shims": "0.1.1",
    "ember": "2.7.0",
    "jquery": "^2.1.4",
    "lodash": "~3.7.0",
    "moment-duration-format": "^1.3.0",
    "moment-range": "1.0.9",
    "nouislider": "~7.0.10",
    "platform.js": "platform#~1.3.0",
    "sinonjs": "1.14.1",
    "tb-factory": "1.2.1"
  }
}

Any clues ?

index.html

Does this need to be inserted manually? and if so how does the hash get in the index.html?

<script>var __assetMapFilename__ = "/blog/assets/assetMap-<hash>.json";</script>

Does fingerprintAssetMap have to be true for inline?

Hi,

Currently the README makes it look like having fingerprintAssetMap set to true is required to have the add-on work. However, as I read through the code, if we have inline mode on, do we really need to fingerprint the asset map? In inline mode, the asset map file is read from the directory that contains all the built files so its content should always be up-to-date and we don't have to worry about loading an obsolete file that is stored in some cache?

Cannot import RSVP

Hi there, thanks for your great work on this problem!

I took a stab at adding integrating IFA into our app, but ran into an issue where RSVP could not be found to import for the initializer. I haven't dug into the issue enough yet to know if its coming from outdated Ember (1.13.12) or NPM2 nested modules, but I was wondering if there would be a problem importing Ember and using Ember.RSVP in the case that this is due to a different package layout in Ember 1.13?

Use dependabot to keep dependencies up-to-date

It would be great to use a service like https://dependabot.com/ to keep the dependencies of this project up-to-date. Unfortunately only @RuslanZavacky will be able to enable it since it's not part of a GitHub org.

@RuslanZavacky could you go to https://github.com/apps/dependabot-preview/installations/new and give it access to this repo? the rest can probably be handled with a config file in the repo. 🙏

alternatively, we could also move the project into the https://github.com/adopted-ember-addons org.

Option for async loading of asset map

What do you think about adding an option to allow the asset map json file to be loaded without blocking the app from loading?

The usecase being that dynamic assets are only used on one infrequently accessed page.

So to load the app, and have it be blocked, whilst a request is made for a massive assets map isn't great.

Perhaps something like:

beforeModel() {
  return this.assetMap.load();
}

Error: ENOENT: no such file or directory, open '/...broccoli_merge_trees/assets/assetMap.js'

I'm running into an issue using this addon with Fastboot, whereby I'm getting the following error:

Error: ENOENT: no such file or directory, open '/var/folders/5t/y4lxls052bn3ty70r4n7t5b00000gn/T/broccoli-36766MWDqPCH7NGuv/out-1103-broccoli_merge_trees/assets/assetMap.js'
    at Object.openSync (fs.js:440:3)
    at Object.readFileSync (fs.js:342:35)
    at /Users/adam/Projects/tutors/frontend/node_modules/fastboot/src/ember-app.js:189:27
    at Array.forEach (<anonymous>)
    at EmberApp.loadAppFiles (/Users/adam/Projects/tutors/frontend/node_modules/fastboot/src/ember-app.js:187:21)
    at EmberApp.retrieveSandboxedApp (/Users/adam/Projects/tutors/frontend/node_modules/fastboot/src/ember-app.js:235:10)
    at new EmberApp (/Users/adam/Projects/tutors/frontend/node_modules/fastboot/src/ember-app.js:61:21)
    at FastBoot._buildEmberApp (/Users/adam/Projects/tutors/frontend/node_modules/fastboot/src/index.js:114:17)
    at new FastBoot (/Users/adam/Projects/tutors/frontend/node_modules/fastboot/src/index.js:52:10)
    at /Users/adam/Projects/tutors/frontend/node_modules/ember-cli-fastboot/index.js:335:29
    at Layer.handle [as handle_request] (/Users/adam/Projects/tutors/frontend/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/adam/Projects/tutors/frontend/node_modules/express/lib/router/index.js:317:13)
    at /Users/adam/Projects/tutors/frontend/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/Users/adam/Projects/tutors/frontend/node_modules/express/lib/router/index.js:335:12)
    at next (/Users/adam/Projects/tutors/frontend/node_modules/express/lib/router/index.js:275:10)
    at /Users/adam/Projects/tutors/frontend/node_modules/ember-cli/lib/tasks/server/middleware/broccoli-watcher/index.js:54:11
    at watcher.then.errorHandler.buildError (/Users/adam/Projects/tutors/frontend/node_modules/broccoli-middleware/lib/watcher-middleware.js:35:7)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

In environment.js, we have:

ifa: {
      enabled: false,
      inline: true
    }

And in ember-cli-build.js, we have:

fingerprint: {
      prepend: 'https://cdn.tutorspot.co.uk/',
      extensions: [...],
      exclude: [...],
      generateAssetMap: true,
      fingerprintAssetMap: true
    },

This error is only showing up in Fastboot. Everything works fine running locally with Fastboot disabled.

Any guidance would be greatly appreciated!

Default prepend value...

Thanks for creating this addon. I'm wondering why the default value for prepend (in services/asset-map) is a slash? I'm trying to use this with fingerprinting in test and production, but not in development and what I'm seeing is that asset paths are always resolved with a leading slash in development because there is no asset map (so no override of the prepend value). Is it possible to configure the default value?

Thanks!

Setting ifa enabled:false breaks application

Setting ifa enabled to false causes the app to not be able to boot.

// environment.js
ifa: {
  enabled: false,
},
// ember-cli-build.js
fingerprint: {
  enabled: true, // or false; doesn't matter
  generateAssetMap: true
}

Get the following error in the browser when booting the application:

Uncaught (in promise) SyntaxError: Unexpected token _ in JSON at position 0

image

If I'm reading the code correctly, this is happening because:

Your .dependabot/config.yml contained invalid details

Dependabot encountered the following error when parsing your .dependabot/config.yml:

Automerging is not enabled for this account. You can enable it from the [account settings](https://app.dependabot.com/accounts/MelSumner/settings) screen in your Dependabot dashboard.

Please update the config file to conform with Dependabot's specification using our docs and online validator.

Building with --environment=production fails with ENOENT

Release v0.7.0 appears to work correctly. But master fails with:

Error: ENOENT: no such file or directory, open 'C:\Users\Joan\AppData\Local\Temp\broccoli-22940Vf5lUYYq91HV\out-362-zopfli_filter\tests\index.html'
    at Object.openSync (fs.js:451:3)
    at Object.readFileSync (fs.js:351:35)
    at replacePlaceholder (C:\dev\data-server\frontend\node_modules\ember-cli-ifa\index.js:10:23)
    at Class.postBuild (C:\dev\data-server\frontend\node_modules\ember-cli-ifa\index.js:80:5)
    at Class.superWrapper [as postBuild] (C:\dev\data-server\frontend\node_modules\core-object\lib\assign-properties.js:34:20)
    at addonPromises.project.addons.reduce (C:\dev\data-server\frontend\node_modules\ember-cli\lib\models\builder.js:187:28)
    at Array.reduce (<anonymous>:null:null)
    at Builder.processAddonBuildSteps (C:\dev\data-server\frontend\node_modules\ember-cli\lib\models\builder.js:184:43)
    at tryCatcher (C:\dev\data-server\frontend\node_modules\rsvp\dist\rsvp.js:323:19)
    at invokeCallback (C:\dev\data-server\frontend\node_modules\rsvp\dist\rsvp.js:495:31)
    at publish (C:\dev\data-server\frontend\node_modules\rsvp\dist\rsvp.js:481:7)
    at flush (C:\dev\data-server\frontend\node_modules\rsvp\dist\rsvp.js:2402:5)
    at process.internalTickCallback (internal/process/next_tick.js:70:11)

I'd guess the reason is this line. It relies on the existence of tests\index.html which is not the case for production builds.

Include asset map at build time

Currently the asset map is loaded via an additional XHR request, that even defers the app readiness. Have you thought about including the map at build time to save the additional roundtrip to the server?

I guess part of the problem is that the asset map is available only after the app has been built, but it should be probably not hard to insert the map directly into the index.html. This increases the payload of the index.html, but as the asset map is going to be loaded anyways, I see no drawbacks there, while still saving the additional request.

Nice addon btw, needed sth like this some time ago, thought about making this by myself when there is time, but apparently you were faster! 👏

Build error when trying to add to a project

Hey, thanks for creating this, I think it’ll solve a long-standing problem I’ve had with dynamically-determined assets in travis-web testing environments. Sadly when I tried to incorporate it as in this PR, I get this build error:

Cannot read property 'project' of undefined
TypeError: Cannot read property 'project' of undefined
    at Class.postBuild (/home/travis/build/travis-ci/travis-web/node_modules/ember-cli-ifa/index.js:34:31)
    at Class.superWrapper [as postBuild] (/home/travis/build/travis-ci/travis-web/node_modules/ember-cli/node_modules/core-object/lib/assign-properties.js:34:20)
    at addonPromises.project.addons.map.addon (/home/travis/build/travis-ci/travis-web/node_modules/ember-cli/lib/models/builder.js:139:34)
    at Array.map (native)
    at Builder.processAddonBuildSteps (/home/travis/build/travis-ci/travis-web/node_modules/ember-cli/lib/models/builder.js:137:43)
    at tryCatch (/home/travis/build/travis-ci/travis-web/node_modules/rsvp/dist/rsvp.js:539:12)
    at invokeCallback (/home/travis/build/travis-ci/travis-web/node_modules/rsvp/dist/rsvp.js:554:13)
    at publish (/home/travis/build/travis-ci/travis-web/node_modules/rsvp/dist/rsvp.js:522:7)
    at flush (/home/travis/build/travis-ci/travis-web/node_modules/rsvp/dist/rsvp.js:2414:5)
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickCallback (internal/process/next_tick.js:104:9)

Looking at the file in question doesn’t explain much:

    const ifaConfig = this.app.project.config(env).ifa;

I’d expect this.app, an EmberApp, to always be present, I couldn’t find any Ember CLI changes that would explain this.

The application in question is on Ember and Ember CLI 2.13.3.

Do you have any insight into what this error is about? If there’s any information I can provide that would help with this, let me know.

Idea: fingerprinted-less approach to assets

To skip fingerprinting assets we have to think about following problems:

  • Asset file shouldn't change
  • Command is needed that will validate which assets where changed
  • Create another assets manifest file that can hold all images that where created as assets and now about their names. This might be needed to notify, if you've created an asset with potentially same name, that you've already deleted, as such asset might be cached in the user's browser.
  • ...?

This problem possibly, don't need a solution, but it might be good to think about it.

Fastboot, ifa, and vedor SRI hashes

Hi there,
I am using a configuration with fastboot, as I understand, this is supported.
I am currently having an issue where ember-cli-ifa will modify the index.html file (as intended), however this is done after ember-cli-sri has already created a hash for it, as a result, my fastboot ember app will not load at all due to tampering of vendor.js.
Aside from manually regenerating the correct hash and manually putting this into index.html, is there a faster way to achieve this?
Thanks for your great work

Having options to remove certain files from the asset map

Hi,

I found the add-on super helpful for the project that I'm working on. One improvement that I think would be very nice to have is to provide users with an option to ignore certain files in the asset map so that they only have to keep track of the mappings for assets that they really need.

I'm imagining having some of these options available:

  • excludeExtensions: an array of file extensions that will be ignored. In my project we don't care about mappings of .js, .css, or .map files.
  • filter: a function that will be called on each mapping in the asset map. If it returns true for a key-value pair, the pair will be kept. Otherwise the pair will be removed from the map.

Once again, thanks for the awesome add-on!

Asset map not being added to index.html

Hello again! I’ve been trying in this branch to test out integrating your promising addon into our PR deployment workflow, which will address a long-standing problem where dynamically-sourced images don’t display properly. I’m finding that the assetMap file isn’t being included in the index.html.

I changed the dynamically-sourced digit images on the landing page to use the asset map, to no avail:

image

Here’s what it looks like on the production site, for reference:

image

The PR deployment is here, in case that’s helpful. If you look at the source for index.html, you can see the asset map isn’t loaded. Can you imagine any reason why this would be the case? I’m confused. I suspect there’s some dreadfully obvious thing I’m missing.

I really look forward to being able to use your addon; while I’m aware of the limitation that dynamically-sourced images don’t load properly in PR deployments, I’m continually having to assure people that it’s a known problem when they’re looking at them to check into other changes. Thanks for your work on this!

vendor.js file MD5 checksum

ifa: { enabled: true, inline: true }

IFA injects asset map into vendor.js on postBuild hook, looks like checksum calculated before this step. As result I can get same filename but content of files is different. This is kind of problem for cloudfront for example. Any ideas?

Error: asset-map helper not found

Using same syntax as in your usage example:
model=e
<img src={{ asset-map (concat "assets/images/" e.photoFilename)}} />

e.photoFilename = "photo.jpg"

How do I access the helper, or write my own?

Script insertion with contentFor triggers CSP warning

Hi there,

first of all thanks for this great addon! We're currently trying to set up a Content Security Policy for our Ember CLI app. Since this addon is inserting an inline script in the contentFor hook, we're getting a CSP error on page load if we don't allow 'unsafe-inline' in our script-src directive (thus making the whole app vulnerable).

Basically what we would need is the option to hash the script and tell ember-csp about the generated hash in the environment file. This issue is not unique to this addon, quite a few seem to be struggling to find a solution to this.

See:
rwjblue/ember-cli-content-security-policy#67
dfreeman/ember-ace#3
pgrippi/ember-cli-google-analytics#21

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.