Coder Social home page Coder Social logo

x-element's Introduction

  x-element
  _________
 / /__ __\ \
/ / \ \ / \ \
\ \ /_\_\ / /
 \_\_____/_/

A dead simple starting point for custom elements. It provides the following functionality:

  • Efficient element generation and data binding via an integrated templating engine
  • ...or use another engine (e.g., lit-html)
  • Automatic .property to [attribute] reflection (opt-in)
  • Automatic [attribute] to .property synchronization (one-directional, on connected)
  • Simple and efficient property observation and computation
  • Simple delegated event handling
  • Useful error messages

Installation:

curl https://raw.githubusercontent.com/Netflix/x-element/main/x-element.js > x-element.js

or

import XElement from 'https://deno.land/x/element/x-element.js';

...or if you're fancy:

npm install @netflix/x-element

Project Philosophy:

  1. No compilation step is necessary for adoption, just import x-element.js
  2. Implement a minimal set of generalized functionality
  3. Make as few design decisions as possible
  4. Presume adopters are browser experts already (stay out of their way)
  5. Follow web platform precedents whenever possible
  6. Remain compatible with any browser which fully supports custom elements
  7. Prioritize simple syntax and useful comments in the code itself
  8. Zero dependencies

Development:

npm install && npm start

Then...

See SPEC.md for all the deets.

x-element's People

Contributors

codestryke avatar daviande avatar klebba avatar mmarchini avatar odysseuslives avatar theengineear 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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

x-element's Issues

XElement should warn authors when shadowing asymmetric inherited attributes.

While many attributes have a simple relationship to properties (e.g., .title >> "title"), there isn't a clear standard for naming conventions when it comes to properties reflecting to attributes. Some interesting cases:

class <<>> .className
contenteditable <<>> .contentEditable
aria-valuemin <<>> .ariaValueMin
itemscope <<>> ???

XElement should attempt to WARN authors when they're inadvertently shadowing attribute interfaces based on mappings between attributes and property names.

Property definition "initial" value is unexpectedly reused across instances

For example, given an element x-baz with property block as follows:

static get properties() {
  return {
    bar: {
        type: Foo,
        initial: () => new Foo(),
    },
  };
}

Newly created instances of <x-baz> will reuse the same instance of Foo. In my case this creates an issue when attempting to destroy unwanted copies of x-baz. Because the reference is shared, any newly created <x-baz> will point to the original Foo

Happy to create a repro case if it helps

Allow import of x-element with lit-html imported from unpkg.

While the supported way to use x-element is to perform a flat-install of lit-html as a sibling to the x-element such that the import ../../lit-html/lit-html.js will work — this presents a barrier to entry to trying x-element.

Since lit-html supports import from unpkg, we could expose a file with alternative imports that don't require the special setup. It would also allow online code editors import and use the element.

The following script would simply replace the import strings with new ones:

"bundle": "cat x-element.js | sed \"s/\\.\\.\\/\\.\\.\\/lit-html\\/\\([^']*\\)/https:\\/\\/unpkg\\.com\\/lit-html\\/\\1?module/\" > x-element-bundle.js"

E.g.,

import { asyncAppend } from '../../lit-html/directives/async-append.js';

turns to...

import { asyncAppend } from 'https://unpkg.com/lit-html/directives/async-append.js?module';

Then, you could pop into a code pen or something and write import XElement from 'https://unpkg.com/@netflix/x-elementx-element.js' and it would just work. I think this is pretty powerful and worthwhile.

I'd like to add this as part of #58.

Note that a simple gut-check test could be as follows where we minimally check functionality and assert that the text in the bundle file is as expected:

import XElement from '../x-element-bundle.js';
import { assert, it } from '../../../@netflix/x-test/x-test.js';

class TestElement extends XElement {
  static get properties() {
    return {
      property: {
        type: String,
        initial: 'Ferus',
      },
    };
  }
  static template(html) {
    return ({ normalProperty }) => {
      return html`<div id="normal">${normalProperty}</div>`;
    };
  }
}
customElements.define('test-element', TestElement);

it('bundle smoke test', () => {
  const el = document.createElement('test-element');
  document.body.append(el);
  assert(el.shadowRoot.getElementById('normal').textContent === 'Ferus');
});

it('bundle was built', async () => {
  const srcText = await (await fetch('../x-element.js')).text();
  const bundleText = await (await fetch('../x-element-bundle.js')).text();
  const expectedText = srcText.replace(
    /\.\.\/\.\.\/lit-html\/([^']+)/g,
    'https://unpkg.com/lit-html/$1?module'
  );
  assert(bundleText === expectedText);
});

No way to listen to errors dispatched during initialization.

Currently, our pattern is to call .dispatchError such that some integrator can do .addEventListener('error', () => { /* .. */ }) and listen to errors from children. However, errors can happen during connection.

We need to do something different here since we're missing signals about errors which can cause silent failures.

Decouple templating engine and ship with a minimal default.

Decoupling rendering logic from core x-element logic and rolling our own internal templating engine would provide the following benefits:

  1. Zero Dependencies — This makes installation, integration, and consumption of this library incredibly straightforward and tightly controlled.
  2. Simple by Default — A major goal of this project is to keep the interface size to a minimum. It's not meant to do it all. By rolling a custom templating engine, we can (1) expose only functionality that we think is necessary and (2) allow integrators to pick their own templating engine if ours doesn't meet their needs (e.g., lit-html or µhtml).

How might the interface change?

This list is incomplete and subject to change — but, to give an idea:

  • Fewer built-in template utilities. Over many years of developing Web Components, we've grown to favor a pattern where attribute/property/content values are managed by the element such that the render loop is incredibly simple and lightweight.
  • Less syntactic sugar. There are really only three things a templating engine needs to consider — attributes, properties, and content. As long as there is a way to inject these core things, everything else can be managed outside the templating engine. This may prevent blurring the lines between separate areas of concern. For example, the ? and @ behavior may not strictly be necessary.
  • Stricter interpolation patterns. By more clearly defining what is/isn't valid interpolation, we can nudge integrators into cleaner code. For example attr="foo-${value}-bar" is quite complicated to read and difficult to internally manage — instead, we could insist it be written as attr="${value}", where value is computed elsewhere to be foo-${userInput}-bar or some such. Additionally, we could consider not supporting interpolation within <style> tags at all.

Cannot assume first stylesheet in document is in document.head.

We infrequently get errors when an extension of some sort puts stylesheets outside the document head. We could protect against this by limiting our search to the document head in x-style.

Here's the error we hit:

Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': tables:1
The node before which the new node is to be inserted is not a child of this node.
    at https://elements.prod.netflix.net/@netflix/x-elements/x-app/x-style.js:161:15

Do not perform initial compute until at least one dependency is defined

We already do this for observer, but computed properties have not changed because it would be a major breaking change. We should take this on separately since it will cause a lot of rework. Any computed method that relies on returning a default value when dependencies are undefined will need to be rewritten to account for the change.

Possible changes users could leverage:

  • define a value in the properties block. This will at least initialize the computed property correctly, but doesn't account for future cases where dependencies are undefined
  • set a value on a dependency to force the computation to happen at initialization. if this is practical, it may be the simplest refactor

Can we help you possibly use LitElement?

It's exciting to see another web component base class based on lit-html, but from a quick glance this seems to do a lot of the same things that LitElement does, and some of the additional things (like property observers) could be layered on. Is there anything we can do on the LitElement side of things to make it so you can use that instead of maintaining your own base class? I'm happy to help explore ideas if this is interesting to you at all.

Bug: issue with using property shadowing element.id

Sample case:

import XElement from 'https://import.dog/@netflix/x-element/x-element.js';

export default class Demo extends XElement {
  static get properties() {
    return {
      id: {
        type: Number,
      },
      value: {
        type: Number,
      },
    };
  }

  static template(html) {
    return ({ id, value }) => html`
      <style>
        :host {
          display: block;
          width: 5em;
          height: 5em;
          background-color: yellow;
        }
      </style>
      <div>${id}</div>
      <div>${value}</div>
    `;
  }
}

customElements.define('id-number-bug', Demo);

If we inspect the id-number-bug in the DOM the id property will be 0 instead of undefined which is unexpected. If we inspect the value property, it will be undefined. It appears that shadowing id property of an element is a special case, though it's not obvious why

Cache static analysis per-class and reuse during initialization.

#23 improves the factoring of x-element. Another refactor we would like to take on is handling analysis at first construction and caching the result to be shared across all future instances.

Currently, we do the same "static" analysis per instance, which is mostly duplicative. Ideally, we would be able to initialize with a cached analysis value which basically just binds the current instance to all the methods etc.

Related to #31

Cancel queued render if render runs.

Minor issue, but we have unnecessary, duplicative renders that get called during initialization because we:

  1. queue a render and then
  2. force a render and then
  3. render again after a microtask tick

Instead, we should:

  1. queue a render and then
  2. force a render and cancel the queued render

Allow extending `observedAttributes`.

Super minor change, but we expect all observedAttributes to come from our statically-declared properties. Instead, we should assume that authors may want to observe additional attributes beyond what they declared in the properties block. That is, we should allow this:

static get observedAttributes() {
  return [...super.observedAttributes, 'some-other-thing'];
}

Discussion: Should we get rid of the computed DSL?

The DSL was a convenience ported over from Polymer. However, this forces us to do some additional parsing work. From the perspective of staying lean, this could just be considered bloat.

Here's a potentially better, more declarative way forward:

computed: ['computeC', 'a', 'b']

That doesn't feel terribly hard to write out to me and then there's basically no guesswork as to what could be happening here.

Discussion: Enable support for event bindings in Template Engine.

Authors can already leverage the listeners block or listen / unlisten methods in connectedCallback / disconnectedCallback to manage event handling. An event binding would potentially provide a more ergonomic solution, maybe.

Why we might add it:

  • It may be expected behavior at this point (as it exists in other templating engines)

Why we might not add it:

  • It’s redundant — you can already achieve this behavior.
  • It adds complexity / size to the base element.
  • It adds an additional runtime check — this may or may not be a performance drawback.
  • In many years of leveraging other templating engines — we've not once used this functionality.
  • Integrators can choose to use other templating engines like lit-html or uhtml if this is desired.

Proposal

Users would be able to declare something like the following to add an event handler:

static template(html) {
  return ({ onClick }) => {
    return html`<button type="button" @click="${onClick}">Click Me</button>`;
  };
}

readOnly property initial value can't be set

static get properties() {
    return {
      readOnlyProperty: {
        type: String,
        value: 'foo',
        readOnly: true,
      },
    };
  }

I would expect this to work — Polymer had this behavior so that you could configure constants that could be fed through the property pipeline

Support Form-Associated Custom Elements

Form-Associated Custom Elements have been implemented in Chrome 77+ (no other browsers yet though). Form controls have different behaviors and needs compared to most other Custom Elements we're used to working with.

Here's a short rundown of things we need to keep in mind:

  • Decoupling of attributes and properties (note that the specifications typically refer to these as "content attributes" and "IDL attributes" respectively). Consider the "value" attribute which has very different behavior than the "value" property. Or, the "form" attribute and the "form" property.
  • Similar to adoptedCallback (which we don't actually use), define prototype methods for the new formAssociatedCallback, formDisabledCallback, formResetCallback, and formStateRestoreCallback.
  • Consider mechanisms to support synchronous property-attribute syncing. Form controls have attributes and properties which remain perfectly in sync. Because it's always possible for a user to click the "submit" button while currently-focusing a form control, we need to consider synchronous changes that happen during event handling. E.g., the browser will fire input, change, maybe invalid, maybe formdata, and maybe submit (in that order). Depending on the timing, debouncing changes on a microtask might cause problems! In the specification, the properties are discussed as "reflecting the attributes", not the other way around. This nuance might cause headaches for us if we are in the habit of doing lazy computation / observation / and reflection.

It's also important to put bounds on what we want and need from XElement — perhaps custom form control authorship is nuanced enough that attempting to support it would ultimately make XElement less useful for the other 99% of cases we use it for! It's completely possible to write a form control from scratch using vanilla JS.

References:

Form Participation API Explained
Chrome Platform Status
Mozilla Bug
WHATWG Specification

Leverage the pattern of "Constructable Stylesheet Objects" for shared CSS

The general spec lives here: https://wicg.github.io/construct-stylesheets/#proposed-solution

Here's a Google article on it: https://developers.google.com/web/updates/2019/02/constructable-stylesheets

Here's how LitElement incorporates it: https://lit-element.polymer-project.org/guide/styles#static-styles
Something to keep an eye on!

Here's the motivation section from that spec for reference:

Most web components uses Shadow DOM. For a style sheet to take effect within the Shadow DOM, it currently must be specified using a style element within each shadow root. As a web page may contain tens of thousands of web components, this can easily have a large time and memory cost if user agents force the style sheet rules to be parsed and stored once for every style element. However, the duplications are actually not needed as the web components will most likely use the same styling, perhaps one for each component library.

Some user agents might attempt to optimize by sharing internal style sheet representations across different instances of the style element. However, component libraries may use JavaScript to modify the style sheet rules, which will thwart style sheet sharing and have large costs in performance and memory.

Food for thought:

// Helper method to create a stylesheet to be included via adoptedStylesheets.
static createStyleSheet(string) {
  const styleSheet = new CSSStyleSheet();
  styleSheet.replaceSync(string);
  return styleSheet;
}

Analysis-time errors should be halting

Currently, analysis work actually uses an instance (to resolve methods, etc). This allows us to dispatch errors at runtime instead of just throwing errors. Because we're not throwing halting errors, we're able to consider these errors non-fatal and move on.

In light of the comment below about the truly static observeAttributes hook and in light of discussions about making both computed methods and observers static #30, I propose we do cause a halting error.

It will:

  1. Remove forks in logic that deal with graceful failure
  2. Allow us to be truly static with our analysis
  3. Can't be missed by developers since it will fail to render anything

As long as analysis is truly static, you know that if an instance boots in development, it will boot in production, so it's not really possible to cause an error in production if you got the thing working in development.

Allow typeless properties to be set via an attribute.

The goal of typeless properties is to just let me do it. In this case, while it still doesn't make sense to reflect a typeless property, it's reasonable to allow an attribute to sync down to a typeless property.

I.e., consider the following element ...

const MyElement extends XElement {
  static get properties() {
    return { data: {} };
  }
  static template(html) {
    return ({ data }) => html`<span>Here is some data: ${data}.</span>`;
  }
}
customElements.define('my-element', MyElement);

... I'd expect to be able to do the following ...

<my-element data="123"></my-element>

Such that the output would read as:

Here is some data: 123.

However, because the property is typeless, "123" will never be synced from the attribute to the property.

Publish a playground for simple fiddling with “x-element”.

We should host static page (perhaps within the GitHub ecosystem) to allow fiddling with “x-element”. It'd be interesting to see how light-weight a solution we could come up with which would still essentially give folks a light-weight text editor and rendered output.

Not sure on exactly the where / how yet, but I think this would be valuable to lower the barrier-to-entry.

Discussion: Add syntactic sugar for boolean attribute bindings to Template Engine.

Authors can already leverage a boolean updater to accomplish a boolean attribute binding. Adding syntactic sugar may potentially be a more ergonomic solution.

Why we might add it:

  • It may be expected behavior at this point (as it exists in other templating engines)
  • We have leveraged this syntactic sugar in the past, so there’s some precedent for us.

Why we might not add it:

  • It’s redundant — you can already achieve this behavior.
  • It adds complexity / size to the base element.
  • It adds an additional runtime check — this may or may not be a performance drawback.
  • Conceptually, it blurs the lines between “what to do” and “how to do it”. We may not want to cross that line.

Proposal

Users would be able to declare something like the following to bind an attribute as a boolean (see the ? in there?):

static template(html) {
  return ({ trueOrFalse }) => {
    return html`<div ?data-true="${trueOrFalse}">Hi There.</div>`;
  };
}

Currently

…for comparison, this is how you would do this currently.

static template(html, { boolean }) {
  return ({ trueOrFalse }) => {
    return html`<div data-true="${boolean(trueOrFalse)}">Hi There.</div>`;
  };
}

Note that the what to do (i.e., bind an attribute) is nicely separated from the how to do it (i.e., in a boolean fashion).

Stack trace for warnings is unhelpful.

If you get a warning due to a validation issue, we create an error and then print it to the console. This isn't actually super helpful though:

Error: Unexpected key "HelloElement.properties.tabIndex" has attribute "tab-index" which is related to the inherited property "tabIndex", behavior not guaranteed.
    at Function.__validateProperties (x-element.js:307)
    at Function.__analyzeConstructor (x-element.js:254)
    at Function.get observedAttributes [as observedAttributes] (x-element.js:22)
    at index.js:84

The only important part is really the message there. We might as will shorten this a bit.

Unexpected key "HelloElement.properties.tabIndex" has attribute "tab-index" which is related to the inherited property "tabIndex", behavior not guaranteed.

Invalid template results can cause cryptic errors.

A template result can successfully be created and then fail to render. Because it's common that an element will only override the "template" method, the stack trace will not actually print the name of the problematic file — this makes debugging incredibly difficult.

One approach would be to wrap the call to lit-html's render in a try-catch such that we can append DOM pathing information when we encounter an error.

Example cryptic stack trace:

Uncaught TypeError: items is not iterable
    at repeat.ts:122
    at NodePart.commit (parts.ts:210)
    at TemplateInstance.update (template-instance.ts:53)
    at NodePart.__commitTemplateResult (parts.ts:280)
    at NodePart.commit (parts.ts:221)
    at render (render.ts:55)
    at HTMLElement.render (x-element.js:158)
    at Function.__updateHost (x-element.js:838)
    at Function.__initializeHost (x-element.js:742)
    at HTMLElement.connectedCallback (x-element.js:131)

But we could modify the message to be:

Uncaught TypeError: items is not iterable — Invalid template for "HelloElement" at path "hello-world < div#container"
    at repeat.ts:122
    at NodePart.commit (parts.ts:210)
    at TemplateInstance.update (template-instance.ts:53)
    at NodePart.__commitTemplateResult (parts.ts:280)
    at NodePart.commit (parts.ts:221)
    at render (render.ts:55)
    at HTMLElement.render (x-element.js:158)
    at Function.__updateHost (x-element.js:837)
    at Function.__initializeHost (x-element.js:741)
    at HTMLElement.connectedCallback (x-element.js:131)

In the later case, you immediately know where to start debugging. You may not know why items is not iterable, but at least you know where to look for the culprit.

Make internals of x-element #private when support lands in modern browsers.

In #58, we'll be refactoring our code to have a clearer distinction between what is "public facing" and what is "private facing". We've put double underscores in front of methods which we intend to truly internalize later.

Ultimately, this ticket is just a reminder to switch the __ in our code to # when it's supported in browsers we target.

Pull `listeners` block into base classes.

We've been playing with the notion of having a static listeners block akin to the properties block which will handle setup / teardown of listeners on connection / disconnection.

There are two important opinions that our listeners block has so far made:

  1. The target to attach listeners to is the shadowRoot.
  2. We will not support additional listeners args (e.g,. capture)

These simplifications have, thus far, been helpful. However, (1) is important because we may not have a shadowRoot in the future, in which case we have to decide if we just fallback to the element itself. It's also important because there are valid use-cases for listening on the host element, not the shadowRoot. For now, you'll just need to manually hook up these listeners using listen and unlisten.

Point (2) seems to be another edge case. Again, if you want to add additional args, the listen and unlisten methods provide for that.

Bug: computed properties can fall out of sync with attributes

We have a property definition like this:

open: {
  type: Boolean,
  reflect: true,
  computed: 'computeOpen(model)',
  observer: 'observeOpen',
},

Then we have a demo page with plain HTML like this: <my-element open></my-element>open should be read only since it’s computed, but we have declared it in the markup accidentally due to a refactor. When I poll the instance of my-element for the value of it's open property the result is undefined which is correct.

The issue though is that the attribute is not removed when the element boots up. This causes a bug where styles which look for my-element[open] are being matched, but the inner template which is binding open to a child element is binding the value undefined

Force observer and computed methods to be static

This is related to #28. Also, it's potentially a nice way for us to remove a fork that may not be providing much benefit.

Benefits to making these static:

  • no chance of naming collision for instance level properties
  • one less fork in our code
  • information can be used in static analysis since it doesn't depend on an instance

Downsides to making these static:

  • a bit more restrictive
  • observer callback may fell a bit less ergonomic since we'll change the call signature

For computed... There's not too much argument here, there should be no need to provide a target. Forcing these methods to be static will also help with potential, future memoization of computations.

For observer... We want to support access to the target still, so we would need to change the signature to be myCallback(target, value, oldValue)--or something like that.

"id" behavior not guaranteed

Unexpected key "FooElement.properties.id" shadows inherited interface, behavior not guaranteed.

This warning is somewhat frustrating when I've purposely shadowed id in order to observe and react to changes on this property. I would choose another property name, but deep linking to an anchor like page.htlm#id is intrinsic behavior that I can't easily recreate. The easy workaround is to mirror the attribute for my own purposes, e.g. <element id="foo" my-id="foo" ...>

Expose lit-html's "svg" for creating svg template results.

See https://lit-html.polymer-project.org/api/modules/lit_html.html#svg

And here's the source: https://github.com/Polymer/lit-html/blob/master/src/lib/template-result.ts#L134-L144

Without svg, you cannot do the following:

static templateCircle(html, circle) {
  return html`
    <!-- This doesn't work, lit-html never sees an svg tag to set context. -->
    <circle cx="${circle.x}" cy="${circle.y}" r="${circle.r}"></circle>
  `;
}

static template(html) {
  return ({ circles }) => {
    return html`
      <svg xmlns="http://www.w3.org/2000/svg">
        <!-- This works, lit-html sees the svg tag and sets context. -->
        <circle cx="0" cy="0" r="10"></circle>
        ${circles.map(circle => this.renderCircle(html, circle))}
      </svg>
    `;
  };
}

The tricky part will be figuring out the correct interface here. Let's update this as we come up with new ideas:

static templateCircle(svg, circle) {
  return svg`
    <circle cx="${circle.x}" cy="${circle.y}" r="${circle.r}"></circle>
  `;
}

static template({ html, svg }) {
  return ({ circles }) => {
    return html`
      <svg xmlns="http://www.w3.org/2000/svg">
        <circle cx="0" cy="0" r="10"></circle>
        ${circles.map(circle => this.renderCircle(svg, circle))}
      </svg>
    `;
  };
}

Switch order of observer arguments

Currently, you get the values in the following order: oldValue, newValue

This was done to match attributeChangedCallback. However, in practice this is not as ergonomic. It's often the case that you do not care about the previous value, causing code like this: myObserver(_, value).

This is pulled into a separate issue since it will cause a sweeping change of all code leveraging observers.

Initialize properties before initial render is called

If we initialize our properties before the initial render, we have a chance to synchronously set up properties before our initial, forced render call. This means that we can have a single, synchronous render call instead of having to await a microtask.

XElement should warn when improperly shadowing "data-*" attributes.

Similar to the class attribute, data-* attributes are meant for the integrator, not for the custom element author. When composing custom elements, it is expected that there won't be any conflict when you set data-* attribute on an element.

Therefore, x-element should WARN authors when they've inadvertently created a property name (e.g., dataValue) since that is associated with the attribute data-value — which has special handling (i.e., syncing to the .dataset.value property).

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.