Code Monkey home page Code Monkey logo

cssstyle's Introduction


jsdom

jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG DOM and HTML Standards, for use with Node.js. In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.

The latest versions of jsdom require Node.js v18 or newer. (Versions of jsdom below v23 still work with previous Node.js versions, but are unsupported.)

Basic usage

const jsdom = require("jsdom");
const { JSDOM } = jsdom;

To use jsdom, you will primarily use the JSDOM constructor, which is a named export of the jsdom main module. Pass the constructor a string. You will get back a JSDOM object, which has a number of useful properties, notably window:

const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

(Note that jsdom will parse the HTML you pass it just like a browser does, including implied <html>, <head>, and <body> tags.)

The resulting object is an instance of the JSDOM class, which contains a number of useful properties and methods besides window. In general, it can be used to act on the jsdom from the "outside," doing things that are not possible with the normal DOM APIs. For simple cases, where you don't need any of this functionality, we recommend a coding pattern like

const { window } = new JSDOM(`...`);
// or even
const { document } = (new JSDOM(`...`)).window;

Full documentation on everything you can do with the JSDOM class is below, in the section "JSDOM Object API".

Customizing jsdom

The JSDOM constructor accepts a second parameter which can be used to customize your jsdom in the following ways.

Simple options

const dom = new JSDOM(``, {
  url: "https://example.org/",
  referrer: "https://example.com/",
  contentType: "text/html",
  includeNodeLocations: true,
  storageQuota: 10000000
});
  • url sets the value returned by window.location, document.URL, and document.documentURI, and affects things like resolution of relative URLs within the document and the same-origin restrictions and referrer used while fetching subresources. It defaults to "about:blank".
  • referrer just affects the value read from document.referrer. It defaults to no referrer (which reflects as the empty string).
  • contentType affects the value read from document.contentType, as well as how the document is parsed: as HTML or as XML. Values that are not a HTML MIME type or an XML MIME type will throw. It defaults to "text/html". If a charset parameter is present, it can affect binary data processing.
  • includeNodeLocations preserves the location info produced by the HTML parser, allowing you to retrieve it with the nodeLocation() method (described below). It also ensures that line numbers reported in exception stack traces for code running inside <script> elements are correct. It defaults to false to give the best performance, and cannot be used with an XML content type since our XML parser does not support location info.
  • storageQuota is the maximum size in code units for the separate storage areas used by localStorage and sessionStorage. Attempts to store data larger than this limit will cause a DOMException to be thrown. By default, it is set to 5,000,000 code units per origin, as inspired by the HTML specification.

Note that both url and referrer are canonicalized before they're used, so e.g. if you pass in "https:example.com", jsdom will interpret that as if you had given "https://example.com/". If you pass an unparseable URL, the call will throw. (URLs are parsed and serialized according to the URL Standard.)

Executing scripts

jsdom's most powerful ability is that it can execute scripts inside the jsdom. These scripts can modify the content of the page and access all the web platform APIs jsdom implements.

However, this is also highly dangerous when dealing with untrusted content. The jsdom sandbox is not foolproof, and code running inside the DOM's <script>s can, if it tries hard enough, get access to the Node.js environment, and thus to your machine. As such, the ability to execute scripts embedded in the HTML is disabled by default:

const dom = new JSDOM(`<body>
  <div id="content"></div>
  <script>document.getElementById("content").append(document.createElement("hr"));</script>
</body>`);

// The script will not be executed, by default:
console.log(dom.window.document.getElementById("content").children.length); // 0

To enable executing scripts inside the page, you can use the runScripts: "dangerously" option:

const dom = new JSDOM(`<body>
  <div id="content"></div>
  <script>document.getElementById("content").append(document.createElement("hr"));</script>
</body>`, { runScripts: "dangerously" });

// The script will be executed and modify the DOM:
console.log(dom.window.document.getElementById("content").children.length); // 1

Again we emphasize to only use this when feeding jsdom code you know is safe. If you use it on arbitrary user-supplied code, or code from the Internet, you are effectively running untrusted Node.js code, and your machine could be compromised.

If you want to execute external scripts, included via <script src="">, you'll also need to ensure that they load them. To do this, add the option resources: "usable" as described below. (You'll likely also want to set the url option, for the reasons discussed there.)

Event handler attributes, like <div onclick="">, are also governed by this setting; they will not function unless runScripts is set to "dangerously". (However, event handler properties, like div.onclick = ..., will function regardless of runScripts.)

If you are simply trying to execute script "from the outside", instead of letting <script> elements and event handlers attributes run "from the inside", you can use the runScripts: "outside-only" option, which enables fresh copies of all the JavaScript spec-provided globals to be installed on window. This includes things like window.Array, window.Promise, etc. It also, notably, includes window.eval, which allows running scripts, but with the jsdom window as the global:

const dom = new JSDOM(`<body>
  <div id="content"></div>
  <script>document.getElementById("content").append(document.createElement("hr"));</script>
</body>`, { runScripts: "outside-only" });

// run a script outside of JSDOM:
dom.window.eval('document.getElementById("content").append(document.createElement("p"));');

console.log(dom.window.document.getElementById("content").children.length); // 1
console.log(dom.window.document.getElementsByTagName("hr").length); // 0
console.log(dom.window.document.getElementsByTagName("p").length); // 1

This is turned off by default for performance reasons, but is safe to enable.

Note that in the default configuration, without setting runScripts, the values of window.Array, window.eval, etc. will be the same as those provided by the outer Node.js environment. That is, window.eval === eval will hold, so window.eval will not run scripts in a useful way.

We strongly advise against trying to "execute scripts" by mashing together the jsdom and Node global environments (e.g. by doing global.window = dom.window), and then executing scripts or test code inside the Node global environment. Instead, you should treat jsdom like you would a browser, and run all scripts and tests that need access to a DOM inside the jsdom environment, using window.eval or runScripts: "dangerously". This might require, for example, creating a browserify bundle to execute as a <script> element—just like you would in a browser.

Finally, for advanced use cases you can use the dom.getInternalVMContext() method, documented below.

Pretending to be a visual browser

jsdom does not have the capability to render visual content, and will act like a headless browser by default. It provides hints to web pages through APIs such as document.hidden that their content is not visible.

When the pretendToBeVisual option is set to true, jsdom will pretend that it is rendering and displaying content. It does this by:

  • Changing document.hidden to return false instead of true
  • Changing document.visibilityState to return "visible" instead of "prerender"
  • Enabling window.requestAnimationFrame() and window.cancelAnimationFrame() methods, which otherwise do not exist
const window = (new JSDOM(``, { pretendToBeVisual: true })).window;

window.requestAnimationFrame(timestamp => {
  console.log(timestamp > 0);
});

Note that jsdom still does not do any layout or rendering, so this is really just about pretending to be visual, not about implementing the parts of the platform a real, visual web browser would implement.

Loading subresources

Basic options

By default, jsdom will not load any subresources such as scripts, stylesheets, images, or iframes. If you'd like jsdom to load such resources, you can pass the resources: "usable" option, which will load all usable resources. Those are:

  • Frames and iframes, via <frame> and <iframe>
  • Stylesheets, via <link rel="stylesheet">
  • Scripts, via <script>, but only if runScripts: "dangerously" is also set
  • Images, via <img>, but only if the canvas npm package is also installed (see "Canvas Support" below)

When attempting to load resources, recall that the default value for the url option is "about:blank", which means that any resources included via relative URLs will fail to load. (The result of trying to parse the URL /something against the URL about:blank is an error.) So, you'll likely want to set a non-default value for the url option in those cases, or use one of the convenience APIs that do so automatically.

Advanced configuration

To more fully customize jsdom's resource-loading behavior, you can pass an instance of the ResourceLoader class as the resources option value:

const resourceLoader = new jsdom.ResourceLoader({
  proxy: "http://127.0.0.1:9001",
  strictSSL: false,
  userAgent: "Mellblomenator/9000",
});
const dom = new JSDOM(``, { resources: resourceLoader });

The three options to the ResourceLoader constructor are:

  • proxy is the address of an HTTP proxy to be used.
  • strictSSL can be set to false to disable the requirement that SSL certificates be valid.
  • userAgent affects the User-Agent header sent, and thus the resulting value for navigator.userAgent. It defaults to `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}`.

You can further customize resource fetching by subclassing ResourceLoader and overriding the fetch() method. For example, here is a version that overrides the response provided for a specific URL:

class CustomResourceLoader extends jsdom.ResourceLoader {
  fetch(url, options) {
    // Override the contents of this script to do something unusual.
    if (url === "https://example.com/some-specific-script.js") {
      return Promise.resolve(Buffer.from("window.someGlobal = 5;"));
    }

    return super.fetch(url, options);
  }
}

jsdom will call your custom resource loader's fetch() method whenever it encounters a "usable" resource, per the above section. The method takes a URL string, as well as a few options which you should pass through unmodified if calling super.fetch(). It must return a promise for a Node.js Buffer object, or return null if the resource is intentionally not to be loaded. In general, most cases will want to delegate to super.fetch(), as shown.

One of the options you will receive in fetch() will be the element (if applicable) that is fetching a resource.

class CustomResourceLoader extends jsdom.ResourceLoader {
  fetch(url, options) {
    if (options.element) {
      console.log(`Element ${options.element.localName} is requesting the url ${url}`);
    }

    return super.fetch(url, options);
  }
}

Virtual consoles

Like web browsers, jsdom has the concept of a "console". This records both information directly sent from the page, via scripts executing inside the document, as well as information from the jsdom implementation itself. We call the user-controllable console a "virtual console", to distinguish it from the Node.js console API and from the inside-the-page window.console API.

By default, the JSDOM constructor will return an instance with a virtual console that forwards all its output to the Node.js console. To create your own virtual console and pass it to jsdom, you can override this default by doing

const virtualConsole = new jsdom.VirtualConsole();
const dom = new JSDOM(``, { virtualConsole });

Code like this will create a virtual console with no behavior. You can give it behavior by adding event listeners for all the possible console methods:

virtualConsole.on("error", () => { ... });
virtualConsole.on("warn", () => { ... });
virtualConsole.on("info", () => { ... });
virtualConsole.on("dir", () => { ... });
// ... etc. See https://console.spec.whatwg.org/#logging

(Note that it is probably best to set up these event listeners before calling new JSDOM(), since errors or console-invoking script might occur during parsing.)

If you simply want to redirect the virtual console output to another console, like the default Node.js one, you can do

virtualConsole.sendTo(console);

There is also a special event, "jsdomError", which will fire with error objects to report errors from jsdom itself. This is similar to how error messages often show up in web browser consoles, even if they are not initiated by console.error. So far, the following errors are output this way:

  • Errors loading or parsing subresources (scripts, stylesheets, frames, and iframes)
  • Script execution errors that are not handled by a window onerror event handler that returns true or calls event.preventDefault()
  • Not-implemented errors resulting from calls to methods, like window.alert, which jsdom does not implement, but installs anyway for web compatibility

If you're using sendTo(c) to send errors to c, by default it will call c.error(errorStack[, errorDetail]) with information from "jsdomError" events. If you'd prefer to maintain a strict one-to-one mapping of events to method calls, and perhaps handle "jsdomError"s yourself, then you can do

virtualConsole.sendTo(c, { omitJSDOMErrors: true });

Cookie jars

Like web browsers, jsdom has the concept of a cookie jar, storing HTTP cookies. Cookies that have a URL on the same domain as the document, and are not marked HTTP-only, are accessible via the document.cookie API. Additionally, all cookies in the cookie jar will impact the fetching of subresources.

By default, the JSDOM constructor will return an instance with an empty cookie jar. To create your own cookie jar and pass it to jsdom, you can override this default by doing

const cookieJar = new jsdom.CookieJar(store, options);
const dom = new JSDOM(``, { cookieJar });

This is mostly useful if you want to share the same cookie jar among multiple jsdoms, or prime the cookie jar with certain values ahead of time.

Cookie jars are provided by the tough-cookie package. The jsdom.CookieJar constructor is a subclass of the tough-cookie cookie jar which by default sets the looseMode: true option, since that matches better how browsers behave. If you want to use tough-cookie's utilities and classes yourself, you can use the jsdom.toughCookie module export to get access to the tough-cookie module instance packaged with jsdom.

Intervening before parsing

jsdom allows you to intervene in the creation of a jsdom very early: after the Window and Document objects are created, but before any HTML is parsed to populate the document with nodes:

const dom = new JSDOM(`<p>Hello</p>`, {
  beforeParse(window) {
    window.document.childNodes.length === 0;
    window.someCoolAPI = () => { /* ... */ };
  }
});

This is especially useful if you are wanting to modify the environment in some way, for example adding shims for web platform APIs jsdom does not support.

JSDOM object API

Once you have constructed a JSDOM object, it will have the following useful capabilities:

Properties

The property window retrieves the Window object that was created for you.

The properties virtualConsole and cookieJar reflect the options you pass in, or the defaults created for you if nothing was passed in for those options.

Serializing the document with serialize()

The serialize() method will return the HTML serialization of the document, including the doctype:

const dom = new JSDOM(`<!DOCTYPE html>hello`);

dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></html>";

// Contrast with:
dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";

Getting the source location of a node with nodeLocation(node)

The nodeLocation() method will find where a DOM node is within the source document, returning the parse5 location info for the node:

const dom = new JSDOM(
  `<p>Hello
    <img src="foo.jpg">
  </p>`,
  { includeNodeLocations: true }
);

const document = dom.window.document;
const bodyEl = document.body; // implicitly created
const pEl = document.querySelector("p");
const textNode = pEl.firstChild;
const imgEl = document.querySelector("img");

console.log(dom.nodeLocation(bodyEl));   // null; it's not in the source
console.log(dom.nodeLocation(pEl));      // { startOffset: 0, endOffset: 39, startTag: ..., endTag: ... }
console.log(dom.nodeLocation(textNode)); // { startOffset: 3, endOffset: 13 }
console.log(dom.nodeLocation(imgEl));    // { startOffset: 13, endOffset: 32 }

Note that this feature only works if you have set the includeNodeLocations option; node locations are off by default for performance reasons.

Interfacing with the Node.js vm module using getInternalVMContext()

The built-in vm module of Node.js is what underpins jsdom's script-running magic. Some advanced use cases, like pre-compiling a script and then running it multiple times, benefit from using the vm module directly with a jsdom-created Window.

To get access to the contextified global object, suitable for use with the vm APIs, you can use the getInternalVMContext() method:

const { Script } = require("vm");

const dom = new JSDOM(``, { runScripts: "outside-only" });
const script = new Script(`
  if (!this.ran) {
    this.ran = 0;
  }

  ++this.ran;
`);

const vmContext = dom.getInternalVMContext();

script.runInContext(vmContext);
script.runInContext(vmContext);
script.runInContext(vmContext);

console.assert(dom.window.ran === 3);

This is somewhat-advanced functionality, and we advise sticking to normal DOM APIs (such as window.eval() or document.createElement("script")) unless you have very specific needs.

Note that this method will throw an exception if the JSDOM instance was created without runScripts set, or if you are using jsdom in a web browser.

Reconfiguring the jsdom with reconfigure(settings)

The top property on window is marked [Unforgeable] in the spec, meaning it is a non-configurable own property and thus cannot be overridden or shadowed by normal code running inside the jsdom, even using Object.defineProperty.

Similarly, at present jsdom does not handle navigation (such as setting window.location.href = "https://example.com/"); doing so will cause the virtual console to emit a "jsdomError" explaining that this feature is not implemented, and nothing will change: there will be no new Window or Document object, and the existing window's location object will still have all the same property values.

However, if you're acting from outside the window, e.g. in some test framework that creates jsdoms, you can override one or both of these using the special reconfigure() method:

const dom = new JSDOM();

dom.window.top === dom.window;
dom.window.location.href === "about:blank";

dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https://example.com/" });

dom.window.top === myFakeTopForTesting;
dom.window.location.href === "https://example.com/";

Note that changing the jsdom's URL will impact all APIs that return the current document URL, such as window.location, document.URL, and document.documentURI, as well as the resolution of relative URLs within the document, and the same-origin checks and referrer used while fetching subresources. It will not, however, perform navigation to the contents of that URL; the contents of the DOM will remain unchanged, and no new instances of Window, Document, etc. will be created.

Convenience APIs

fromURL()

In addition to the JSDOM constructor itself, jsdom provides a promise-returning factory method for constructing a jsdom from a URL:

JSDOM.fromURL("https://example.com/", options).then(dom => {
  console.log(dom.serialize());
});

The returned promise will fulfill with a JSDOM instance if the URL is valid and the request is successful. Any redirects will be followed to their ultimate destination.

The options provided to fromURL() are similar to those provided to the JSDOM constructor, with the following additional restrictions and consequences:

  • The url and contentType options cannot be provided.
  • The referrer option is used as the HTTP Referer request header of the initial request.
  • The resources option also affects the initial request; this is useful if you want to, for example, configure a proxy (see above).
  • The resulting jsdom's URL, content type, and referrer are determined from the response.
  • Any cookies set via HTTP Set-Cookie response headers are stored in the jsdom's cookie jar. Similarly, any cookies already in a supplied cookie jar are sent as HTTP Cookie request headers.

fromFile()

Similar to fromURL(), jsdom also provides a fromFile() factory method for constructing a jsdom from a filename:

JSDOM.fromFile("stuff.html", options).then(dom => {
  console.log(dom.serialize());
});

The returned promise will fulfill with a JSDOM instance if the given file can be opened. As usual in Node.js APIs, the filename is given relative to the current working directory.

The options provided to fromFile() are similar to those provided to the JSDOM constructor, with the following additional defaults:

  • The url option will default to a file URL corresponding to the given filename, instead of to "about:blank".
  • The contentType option will default to "application/xhtml+xml" if the given filename ends in .xht, .xhtml, or .xml; otherwise it will continue to default to "text/html".

fragment()

For the very simplest of cases, you might not need a whole JSDOM instance with all its associated power. You might not even need a Window or Document! Instead, you just need to parse some HTML, and get a DOM object you can manipulate. For that, we have fragment(), which creates a DocumentFragment from a given string:

const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);

frag.childNodes.length === 2;
frag.querySelector("strong").textContent === "Hi!";
// etc.

Here frag is a DocumentFragment instance, whose contents are created by parsing the provided string. The parsing is done using a <template> element, so you can include any element there (including ones with weird parsing rules like <td>). It's also important to note that the resulting DocumentFragment will not have an associated browsing context: that is, elements' ownerDocument will have a null defaultView property, resources will not load, etc.

All invocations of the fragment() factory result in DocumentFragments that share the same template owner Document. This allows many calls to fragment() with no extra overhead. But it also means that calls to fragment() cannot be customized with any options.

Note that serialization is not as easy with DocumentFragments as it is with full JSDOM objects. If you need to serialize your DOM, you should probably use the JSDOM constructor more directly. But for the special case of a fragment containing a single element, it's pretty easy to do through normal means:

const frag = JSDOM.fragment(`<p>Hello</p>`);
console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"

Other noteworthy features

Canvas support

jsdom includes support for using the canvas package to extend any <canvas> elements with the canvas API. To make this work, you need to include canvas as a dependency in your project, as a peer of jsdom. If jsdom can find the canvas package, it will use it, but if it's not present, then <canvas> elements will behave like <div>s. Since jsdom v13, version 2.x of canvas is required; version 1.x is no longer supported.

Encoding sniffing

In addition to supplying a string, the JSDOM constructor can also be supplied binary data, in the form of a Node.js Buffer or a standard JavaScript binary data type like ArrayBuffer, Uint8Array, DataView, etc. When this is done, jsdom will sniff the encoding from the supplied bytes, scanning for <meta charset> tags just like a browser does.

If the supplied contentType option contains a charset parameter, that encoding will override the sniffed encoding—unless a UTF-8 or UTF-16 BOM is present, in which case those take precedence. (Again, this is just like a browser.)

This encoding sniffing also applies to JSDOM.fromFile() and JSDOM.fromURL(). In the latter case, any Content-Type headers sent with the response will take priority, in the same fashion as the constructor's contentType option.

Note that in many cases supplying bytes in this fashion can be better than supplying a string. For example, if you attempt to use Node.js's buffer.toString("utf-8") API, Node.js will not strip any leading BOMs. If you then give this string to jsdom, it will interpret it verbatim, leaving the BOM intact. But jsdom's binary data decoding code will strip leading BOMs, just like a browser; in such cases, supplying buffer directly will give the desired result.

Closing down a jsdom

Timers in the jsdom (set by window.setTimeout() or window.setInterval()) will, by definition, execute code in the future in the context of the window. Since there is no way to execute code in the future without keeping the process alive, outstanding jsdom timers will keep your Node.js process alive. Similarly, since there is no way to execute code in the context of an object without keeping that object alive, outstanding jsdom timers will prevent garbage collection of the window on which they are scheduled.

If you want to be sure to shut down a jsdom window, use window.close(), which will terminate all running timers (and also remove any event listeners on the window and document).

Debugging the DOM using Chrome DevTools

In Node.js you can debug programs using Chrome DevTools. See the official documentation for how to get started.

By default jsdom elements are formatted as plain old JS objects in the console. To make it easier to debug, you can use jsdom-devtools-formatter, which lets you inspect them like real DOM elements.

Caveats

Asynchronous script loading

People often have trouble with asynchronous script loading when using jsdom. Many pages load scripts asynchronously, but there is no way to tell when they're done doing so, and thus when it's a good time to run your code and inspect the resulting DOM structure. This is a fundamental limitation; we cannot predict what scripts on the web page will do, and so cannot tell you when they are done loading more scripts.

This can be worked around in a few ways. The best way, if you control the page in question, is to use whatever mechanisms are given by the script loader to detect when loading is done. For example, if you're using a module loader like RequireJS, the code could look like:

// On the Node.js side:
const window = (new JSDOM(...)).window;
window.onModulesLoaded = () => {
  console.log("ready to roll!");
};
<!-- Inside the HTML you supply to jsdom -->
<script>
requirejs(["entry-module"], () => {
  window.onModulesLoaded();
});
</script>

If you do not control the page, you could try workarounds such as polling for the presence of a specific element.

For more details, see the discussion in #640, especially @matthewkastor's insightful comment.

Unimplemented parts of the web platform

Although we enjoy adding new features to jsdom and keeping it up to date with the latest web specs, it has many missing APIs. Please feel free to file an issue for anything missing, but we're a small and busy team, so a pull request might work even better.

Some features of jsdom are provided by our dependencies. Notable documentation in that regard includes the list of supported CSS selectors for our CSS selector engine, nwsapi.

Beyond just features that we haven't gotten to yet, there are two major features that are currently outside the scope of jsdom. These are:

  • Navigation: the ability to change the global object, and all other objects, when clicking a link or assigning location.href or similar.
  • Layout: the ability to calculate where elements will be visually laid out as a result of CSS, which impacts methods like getBoundingClientRects() or properties like offsetTop.

Currently jsdom has dummy behaviors for some aspects of these features, such as sending a "not implemented" "jsdomError" to the virtual console for navigation, or returning zeros for many layout-related properties. Often you can work around these limitations in your code, e.g. by creating new JSDOM instances for each page you "navigate" to during a crawl, or using Object.defineProperty() to change what various layout-related getters and methods return.

Note that other tools in the same space, such as PhantomJS, do support these features. On the wiki, we have a more complete writeup about jsdom vs. PhantomJS.

Supporting jsdom

jsdom is a community-driven project maintained by a team of volunteers. You could support jsdom by:

Getting help

If you need help with jsdom, please feel free to use any of the following venues:

cssstyle's People

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cssstyle's Issues

Doesn't support `calc`

import cssstyle from "cssstyle";

export const stringifyCssText = (styleObject: object): string => {
  let cssText = "";
  const style = new cssstyle.CSSStyleDeclaration(newCssText => {
    cssText = newCssText;
  });
  Object.assign(style, styleObject);

  return cssText;
};
console.log(
  stringifyCssText({
    width: "100vh",
    height: "calc(100vh * 2)"
  })
);
// will output `width: 100vh;` but `height` is missing

Demo here https://codesandbox.io/s/flamboyant-moore-983md

Breaking change

Hi, I'm downstream (using zombie which uses jsdom which uses cssstyle) and the code change from yesterday introduced an issue. The stack trace looks like:

Object 0 has no method 'toLowerCase' TypeError: Object 0 has no method 'toLowerCase'
    at CSSStyleDeclaration.module.exports.definition.set [as border] (/home/ubuntu/dashboard/node_modules/zombie/node_modules/jsdom/node_modules/cssstyle/lib/properties/border.js:35:15)
    at CSSStyleDeclaration.set (/home/ubuntu/dashboard/node_modules/zombie/node_modules/jsdom/node_modules/cssstyle/lib/CSSStyleDeclaration.js:206:24)

I'm happy to submit a pull request if you want to advise on an appropriate fix.

Colors are not interpreted correctly

Keywords
CSS has hundreds of color keywords, but CSSStyle only recognizes a few
https://github.com/chad3814/CSSStyleDeclaration/blob/master/lib/parsers.js#L100-L116

HSL and HSLA
CSSStyle has no logic to interpret HSL or HSLA values. Here's the regex for Hex, RGA, and RGBA. HSL and HSLA are missing.

https://github.com/chad3814/CSSStyleDeclaration/blob/master/lib/parsers.js#L28-L30

Computing the result
Hex values are correctly converted to RGB, however keywords (even valid ones) are not. If CSSStyle intends to emulate a browser's behavior, it should convert everything to RGB or RGBA

TLDR
CSSStyle doesn't handle color values correctly

Implement `float`

It seems that I can set/get cssFloat, but not float.

As I read the CSSOM standard, this should be possible. The browsers allow you to do this too:

node.style.float = 'left'; // might need ['float'] in old ES implementations
node.style.cssFloat === 'left; // true

"TypeError: Object 0 has no method 'toLowerCase'" during jQuery UI parsing

Hi,

After cssstyle module was updated from 0.2.3 to 0.2.4, we started to observe such error while trying to require jQuery-UI source in node:

TypeError: Object 0 has no method 'toLowerCase'
    at Object.parse [as shorthandParser] (/Users/piotr/Concord/csstest/node_modules/jsdom/node_modules/cssstyle/lib/parsers.js:349:11)
    at CSSStyleDeclaration.border (/Users/piotr/Concord/csstest/node_modules/jsdom/node_modules/cssstyle/lib/parsers.js:373:27)
    at $.widget.version (/Users/piotr/Concord/csstest/jquery-ui.js:12498:26)
    at $.widget.version (/Users/piotr/Concord/csstest/jquery-ui.js:12511:3)
    at Object.<anonymous> (/Users/piotr/Concord/csstest/jquery-ui.js:12513:2)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)

Basically we have to use jQuery and jQuery UI in node for testing purposes.

To replicate the problem you can:

  1. download jQuery and jQuery source (we use v2.0.3 and v1.10.3)
  2. npm install jsdom
  3. run this test script using node:
var jsdom = require('jsdom');

document = jsdom.jsdom('<html><body></body></html>');
window = document.createWindow();
navigator = window.navigator;
screen = window.screen;

require("./jquery.js");
$ = jQuery = window.jQuery;

// It will fail:
require("./jquery-ui.js");

width and height value of auto is not supported

Setting a style of auto for width or height doesn't work. This is because the use of parseMeasurement() in the set method doesn't pass auto, so nothing is done.

Here is an example:

var cssstyle = require("./lib/CSSStyleDeclaration");
var style = new cssstyle.CSSStyleDeclaration();
style.width = "auto";
console.log(style);
console.log(style.width);
console.log(style.cssText);

This prints three blank lines rather than the expected two lines of auto followed by width: auto;.

Cannot find './properties'

The latest version (0.2.19) on npm give me following error:

Error: Cannot find module './properties'
    at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (/Users/stefanbuck/projects/cloud-tag-challenge/node_modules/cssstyle/lib/CSSStyleDeclaration.js:209:1)
    at Module._compile (module.js:456:26)
    at Module._extensions..js (module.js:474:10)
    at Object.Module._extensions..js (/Users/stefanbuck/projects/cloud-tag-challenge/node_modules/gulp-istanbul/node_modules/istanbul/lib/hook.js:102:13)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)```

length: 12.23pt, and percent: 12.23%, angle: 12.23 is not acceptted

the following regex in parser.js should be correct

var lengthRegEx = /^(0|[\-+]?[0-9]?\.?[0-9]+(in|cm|em|mm|pt|pc|px))$/;
var percentRegEx = /^[\-+]?[0-9]?\.?[0-9]+%$/;
var angleRegEx = /^([\-+]?[0-9]?\.?[0-9]+)(deg|grad|rad)$/;

to

var lengthRegEx = /^(0|[\-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px))$/;
var percentRegEx = /^[\-+]?[0-9]*\.?[0-9]+%$/;
var angleRegEx = /^([\-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/;

font-size not covered by the parse function and can set with invalid values

According to this issue in jsDOM i figure out that the bug is in the cssStyleDeclertion

The bug happen because we don't use the parse function to check if the font-size is invalid

For example:

    var style = new cssstyle.CSSStyleDeclaration();
    style.cssText = 'font-size: 15px'
    style.length // equal 1
    style.cssText = 'font-size: 1r5px'
    style.length // equal 1 (should be 0 because the value [1r5px] is invalid)

I already open pull request that solve this issue.

0.2.4 breaks compatability with jsdom 0.8.6 / zombie 2.0.0-alpha24

I don't know if you care about this, but the latest release caused our built to start failing:

/home/ubuntu/cucumber-pro/node_modules/zombie/lib/zombie/jsdom_patches.js:61
Object.defineProperty(HTML.CSSStyleDeclaration.prototype, "opacity", {
       ^
TypeError: Cannot redefine property: opacity
    at Function.defineProperty (native)
    at Object.<anonymous> (/home/ubuntu/cucumber-pro/node_modules/zombie/lib/zombie/jsdom_patches.js:61:8)

I don't know what changed, but when I pin our dependency back to 0.2.3, it works again. Maybe the change would have been better in a 0.3.x version?

Support more css3 syntax

to do things right would require a big overhaul to the parsers I think. might make sense to use jison or some other context-free grammar parser.

Hex values without # are missed

The CSS Style declaration looks for only a # followed by 6 characters for Hex values, browsers treat the # as optional and will resolve any value 0-ffffff as hex. The change seems pretty simple, change the regex in parse.js .

update url parsing logic to match current browsers

it would be nice if url parsing worked like latest firefox/chrome.

for instance, running the following snippet in those browsers:

(() => {
  var div = document.createElement('div');
  div.style.backgroundImage = '   url(http://some/url/here1.png)   , URL(2)  ';
  console.log(`backgroundImage:${div.style.backgroundImage};`);
})();

prints:

backgroundImage:url("http://some/url/here1.png"), url("2");

i've searched the code a bit, and think we can achieve this by modifying the following function:

cssstyle/lib/parsers.js

Lines 210 to 247 in f2db1eb

exports.parseUrl = function parseUrl(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
return val;
}
var res = urlRegEx.exec(val);
// does it match the regex?
if (!res) {
return undefined;
}
var str = res[1];
// if it starts with single or double quotes, does it end with the same?
if ((str[0] === '"' || str[0] === "'") && str[0] !== str[str.length - 1]) {
return undefined;
}
if (str[0] === '"' || str[0] === "'") {
str = str.substr(1, str.length - 2);
}
var i;
for (i = 0; i < str.length; i++) {
switch (str[i]) {
case '(':
case ')':
case ' ':
case '\t':
case '\n':
case "'":
case '"':
return undefined;
case '\\':
i++;
break;
}
}
return 'url(' + str + ')';
};

we can also add some better unit tests here:

  • describe('parseUrl', () => {
    it.todo('test');
    });
  • test('url parsing works with quotes', () => {
    var style = new CSSStyleDeclaration();
    style.backgroundImage = 'url(http://some/url/here1.png)';
    expect(style.backgroundImage).toEqual('url(http://some/url/here1.png)');
    style.backgroundImage = "url('http://some/url/here2.png')";
    expect(style.backgroundImage).toEqual('url(http://some/url/here2.png)');
    style.backgroundImage = 'url("http://some/url/here3.png")';
    expect(style.backgroundImage).toEqual('url(http://some/url/here3.png)');
    });

if i make the "formatted" string look like url("foo.png") instead of the current url(foo.png), then I could see that breaking people's existing tests (but it would "match" modern browsers more closely). If I just allow trailing/closing whitespace, and multiple urls, then that would be "more" backwards compatible I think.

I can work on this in the next few days and submit a PR. I have quite a few ideas on how to get it to work.

Thanks!

`cssText` setter should not throw

I don't think the spec explicitly states this, but it is how all the browsers seem to behave.

Try this:

node.style.cssText = 'color:';
node.style.cssText === '';

node.style.cssText = 'color: red/*';
node.style.cssText === '';

// etc

Please include a license

Hi there. It would be awesome from a distribution packaging point of view if you could include a copy of the MIT license with your software either in your README or in a separate LICENSE file.

Thanks!

Publish 0.2.20 on npm

You fixed #20 by issuing a new version 0.2.20, then unpublished 0.2.19, but either never published 0.2.20, or unpublished it as well. Now it broke jsdom in jsdom/jsdom#912.

Please publish 0.2.20 or 0.2.21 whenever possible.

Please use tags when release new version

Hello,

I'm from debian/ubuntu world.

We would like to package your module, but we have some problem: our automatic system, which checks for new releases in github, looks for tags in the relative project homepage, but it seems you do not provide them.

Can you please consider to use tags when release a new version of your software?

This is really important for us.

Resolve npm publish warnings

Resolve npm warnings when using publish.

$ npm publish
npm WARN prepublish-on-install As of npm@5, `prepublish` scripts are deprecated.
npm WARN prepublish-on-install Use `prepare` for build steps and `prepublishOnly` for upload-only.
npm WARN prepublish-on-install See the deprecation note in `npm help scripts` for more information.

CSS Custom Properties not supported

CSS Custom properties are ignored as are the rules which use them.

const
  cssstyle = require('cssstyle'),
  customProp = new cssstyle.CSSStyleDeclaration(),
  normalProp = new cssstyle.CSSStyleDeclaration();

customProp.cssText = `--hello-world: blue; background: var(--hello-world);`
console.log(customProp.background) // "" -- ❌ expected "blue"

normalProp.cssText = `background: blue;`
console.log(normalProp.background) // "blue" -- ✅

Unlike the CSS color names PR, I have no idea how to resolve this issue.
I don't mind contributing to solve this issue, but I will likely need some help.

Setting background to `null` throws

See jsdom/jsdom#1499. Complete jsdom repro:

"use strict";
const jsdom = require("jsdom");

const document = jsdom.jsdom();
document.body.innerHTML = `<div id="ctrl" style="background:#111;border:1px"></div>`;

const div = document.body.firstChild;
div.style.background = null;

"webkit-*" vs "-webkit-*"

It seems there's a problem with the webkit prefixes (leading "-").
I think it should be -webkit-transform, not webkit-transform.

The following code shows the issue:

const
  cssstyle = require('cssstyle'),
  style = new cssstyle.CSSStyleDeclaration();

style.cssText = 'background-color: red; -webkit-transform: scale(2);'

// Be aware: Using "-webkit-transform" with leading "-"
console.log('background-color: ' + style.backgroundColor)  // working => "red"
console.log('-webkit-transform: ' + style.webkitTransform) // not working => "" <---------- !!!!


// Be aware: Using "webkit-transform" now, without leading "-"
style.cssText = 'background-color: green; webkit-transform: scale(3);'

console.log('background-color: ' + style.backgroundColor)  // working => "green"
console.log('webkit-transform: ' + style.webkitTransform) // working => "scale(3)"

setProperty must coerce value to a string.

Perhaps related to #2, but I noticed that CSSStyleDeclaration’s setProperty function does not coerce the value to a string. This causes further problems in your falsey check in getPropertyValue.

For example, if I say:

document.body.style.setProperty("opacity", 0);

Then later,

document.body.style.getPropertyValue("opacity"); // should return "0"

Instead, the result is that it returns the empty string (effectively equivalent to opacity 1).

/cc @tmpvar

Maintenance of this package

Hi! I ended up here because JSDOM's window.getComputedStyle relies on this package, and it's missing several flex properties. I noticed that there are some PRs that could help fix the problem, but they haven't been merged yet and the last commit was made 5 months ago. I know, we all have busy schedules and we also burn out from maintaining packages that outgrow our needs.

That's why I'd like to become a contributor here for a while to continue the cycle. If in doubt please check my public contributions. I don't need to steal Bitcoins, I need to have align-self and flex working and I'll gladly fix some other stuff while I'm at it (without stealing Bitcoins).

Add a callback for whenever cssText changes

Or maybe just whenever a property is modified?

I'm overhauling some of jsdom's style attribute mess, and I think such a hook would allow me a much cleaner architecture. In particular I want to reflect the spec's:

Mutating the declarations must set the style content attribute on the context object to the serialization of the declarations.

I think for now I will override _setProperty, but I don't like overwriting private members... some kind of callback I could pass to the CSSStyleDeclaration constructor would be best.

properties.js defines parse function twice

In v0.2.37 the prepublish command is producing a properties.js file that looks like this (I've added the line number comments):

...
function parse(v) { // line 1644
  if (String(v).toLowerCase() === 'auto') {
    return 'auto';
  }

  if (String(v).toLowerCase() === 'inherit') {
    return 'inherit';
  }

  return external_dependency_parsers_0.parseMeasurement(v);
}
...

function parse(v) { // line 5029
  if (String(v).toLowerCase() === 'auto') {
    return 'auto';
  }

  if (String(v).toLowerCase() === 'inherit') {
    return 'inherit';
  }

  return external_dependency_parsers_0.parseMeasurement(v);
}

Parsing this file fails when the parser sees the second declaration of function parse

SyntaxError: Variable 'parse' has already been declared

jsdom install fails because of cssstyle

$ npm install --save-dev jsdom
npm ERR! notarget No compatible version found: cssstyle@'>=0.2.20 <0.3.0-0'
npm ERR! notarget Valid install targets:
npm ERR! notarget ["0.1.0","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.7","0.2.8","0.2.9","0.2.10","0.2.11","0.2.12","0.2.13","0.2.14","0.2.15","0.2.16","0.2.17","0.2.18"]
npm ERR! notarget 
npm ERR! notarget This is most likely not a problem with npm itself.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.

npm ERR! System Linux 3.13.0-36-generic
npm ERR! command "/usr/local/bin/node" "/usr/local/bin/npm" "install" "--save-dev" "jsdom"
npm ERR! cwd /home/tjwebb/workspace/hashware/backbone-client
npm ERR! node -v v0.10.32
npm ERR! npm -v 1.4.28
npm ERR! code ETARGET
npm ERR! not ok code 0

0.2.19 has some breaking change over 0.2.18 that breaks jsdom 0.2.19

Error: Cannot find module './properties'
  at Function.Module._resolveFilename (module.js:338:15)
  at Function.Module._load (module.js:280:25)
  at Module.require (module.js:364:17)
  at require (module.js:380:17)
  at Object.<anonymous> (-------/requestify/node_modules/jquery/node_modules/jsdom/node_modules/cssstyle/lib/CSSStyleDeclaration.js:209:1)
  at Module._compile (module.js:456:26)
  at Object.Module._extensions..js (module.js:474:10)
  at Module.load (module.js:356:32)
  at Function.Module._load (module.js:312:12)
  at Module.require (module.js:364:17)
  at require (module.js:380:17)
  at Object.<anonymous> (-------/requestify/node_modules/jquery/node_modules/jsdom/lib/jsdom/level2/style.js:5:16)

`CSSStyleDeclaration.setProperty()` should NOT apply styles for camelCased property names

According to the official spec, CSSStyleDeclaration.setProperty()'s propertyName parameter is the CSS property name in hyphen case/dash-case. The current implementation sets the style for camelCase names too, which is not consistent with the browser behavior.

This isn't a nit-pick, it's an actual issue that's bleeding into JSDOM and causing unexpected behavior in testing environments for me, and I'm sure for other users too.

I'm willing to fix this myself, would you accept a PR ?

image

image

Browserify support

For jsdom/jsdom#711, I am working on making it possible to bundle jsdom with browserify. Unfortunately, that's not possible at present for this repo, becuase browserify does not attempt to understand dynamic requires like those done to pull in the property modules.

I have fixed this by expanding the array of property names into the equivalent static call for each. This is ugly as sin, but works (if you increase the maximum number of open files on OS X).

I am going to fork this repo and apply my fix, but I think the permanent fix is to have an optional build step that does the described transformation. If I build such a thing, would you consider merging it?

Error: Cannot find module './properties'

This issue, https://github.com/chad3814/CSSStyleDeclaration/issues/20, occurring again with version [email protected].

Cannot find module './properties'
at Function.Module._resolveFilename (module.js:336:15)
at Function.Module._load (module.js:286:25)
at Module.require (module.js:365:17)
at require (module.js:384:17)
at Object. (/node_modules/cssstyle/lib/CSSStyleDeclaration.js:230:1)
at Module._compile (module.js:434:26)
at Object.Module._extensions..js (module.js:452:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Module.require (module.js:365:17)
at require (module.js:384:17)

Crash when requiring

It's crashing since the last update

Error: Cannot find module './properties'
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Module.require (module.js:364:17)
at require (module.js:380:17)
at Object. (C:\Users\Julio\Desktop\visitavirtual-api\node_modules
\juice\node_modules\jsdom\node_modules\cssstyle\lib\CSSStyleDeclaration.js:209:1
)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.require (module.js:364:17)

Get to 1.0.0 and start following semantic versioning

Right now this project is < 1.0.0 so we have to pin its exact version since there is no guarantee of it only changing in backward-compatible ways. Releasing a 1.0.0 and committing to following semver would be great for dependencies like jsdom, so they could automatically get any backward-compatible improvements without needing to manually roll the version each time.

CSS Parser: rgba test incorrect

Hello,

is this library used by the Chrome audit PWA check? I landed here because the audit did not recognize my theme-color (meta tag in head area) rgba value.
I just checked 'CSSStyleDeclaration/lib/parsers.js' and the part:

res = colorRegEx3.exec(val);
if (res !== null) {
	parts = res[1].split(/\s*,\s*/);
	if (parts.length !== 4) {
		return undefined;
	}
	if (
		parts.slice(0, 3).every(percentRegEx.test.bind(percentRegEx)) ||
		parts.every(integerRegEx.test.bind(integerRegEx))
	) {
		if (numberRegEx.test(parts[3])) {
			return exports.TYPES.COLOR;
		}
	}
	return undefined;
}

seems wrong to me. There is just one case where it returns exports.TYPES.COLOR:
rgba(PERCENT,PERCENT,PERCENT,ALPHA);
But not i.e.:
rgba(NUMBER,NUMBER,NUMBER,ALPHA);

Or am I wrong?

cssstyle 0.2.17 makes my zombie tests break

After updating to cssstyle 0.2.17 (which is a dependency of jsdom which is a dependency of zombie.js), my zombie/mocha test suite breaks. The debug log from zombie goes

  zombie GET https://www.snartibox.no/altibox/js/commons/jquery-latest.min.js => 200 +49ms
  zombie Maximum call stack size exceeded undefined +18ms
  zombie jQuery is not defined ReferenceError: jQuery is not defined

If I downgrade cssstyle to 0.2.16 the test works again.

I'll try to make time to investigate this further, but I thought I'd raise the issue to make you aware that a problem exists.

padding/margin not completely fixed

https://github.com/chad3814/CSSStyleDeclaration/pull/35 helped but it seems like the sub-properties are not properly passed to the parent.

Test case from jsdom (with latest csstyle). Mostly passing except the one failing line.

const document = jsdom.jsdom();

for (const prop of ["padding", "margin"]) {
  document.body.style[prop] = "1px 2px 3px 4px";

  t.strictEqual(document.body.style[prop], "1px 2px 3px 4px");
  t.strictEqual(document.body.style[prop + "Top"], "1px");
  t.strictEqual(document.body.style[prop + "Right"], "2px");
  t.strictEqual(document.body.style[prop + "Bottom"], "3px");
  t.strictEqual(document.body.style[prop + "Left"], "4px");

  document.body.style[prop + "Top"] = "1em";
  document.body.style[prop + "Right"] = "2em";
  document.body.style[prop + "Bottom"] = "3em";
  document.body.style[prop + "Left"] = "4em";
  t.strictEqual(document.body.style[prop], "1em 2em 3em 4em"); // fails, left as 1px 2px 3px 4px

  document.body.style[prop] = "1mm";
  t.strictEqual(document.body.style[prop], "1mm");
  t.strictEqual(document.body.style[prop + "Top"], "1mm");
  t.strictEqual(document.body.style[prop + "Right"], "1mm");
  t.strictEqual(document.body.style[prop + "Bottom"], "1mm");
  t.strictEqual(document.body.style[prop + "Left"], "1mm");

  document.body.style[prop] = "1% 2%";
  t.strictEqual(document.body.style[prop], "1% 2%");
  t.strictEqual(document.body.style[prop + "Top"], "1%");
  t.strictEqual(document.body.style[prop + "Right"], "2%");
  t.strictEqual(document.body.style[prop + "Bottom"], "1%");
  t.strictEqual(document.body.style[prop + "Left"], "2%");

  document.body.style[prop] = "3pc 2pc 1pc";
  t.strictEqual(document.body.style[prop], "3pc 2pc 1pc");
  t.strictEqual(document.body.style[prop + "Top"], "3pc");
  t.strictEqual(document.body.style[prop + "Right"], "2pc");
  t.strictEqual(document.body.style[prop + "Bottom"], "1pc");
  t.strictEqual(document.body.style[prop + "Left"], "2pc");
}

t.done();

This project needs a better test runner

nodeunit is deprecated.

Likely to use Jest, however it would also be nice to write tests that can be run in a browser against different implementations of CSSStyleDeclaration as a smoke test for feature parity between this Node implementation and the Chrome/FF/etc implementations.

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.