Giter Site home page Giter Site logo

matthewp / haunted Goto Github PK

View Code? Open in Web Editor NEW
2.6K 32.0 92.0 2.96 MB

React's Hooks API implemented for web components πŸ‘»

License: BSD 2-Clause "Simplified" License

JavaScript 3.07% HTML 2.08% TypeScript 94.86%
hooks lit-html react-hooks web-components

haunted's Introduction

Haunted πŸ¦‡ πŸŽƒ

npm npm

React's Hooks API but for standard web components and lit-html or hyperHTML.

πŸ“š Read the Docs πŸ“–

<html lang="en">
  <my-counter></my-counter>

  <script type="module">
    import { html } from 'https://unpkg.com/lit?module';
    import { component, useState } from 'https://unpkg.com/haunted/haunted.js';

    function Counter() {
      const [count, setCount] = useState(0);

      return html`
        <div id="count">${count}</div>
        <button type="button" @click=${() => setCount(count + 1)}>
          Increment
        </button>
      `;
    }

    customElements.define('my-counter', component(Counter));
  </script>
</html>

More example integrations can be found in this gist.

Hooks

Haunted supports the same API as React Hooks. The hope is that by doing so you can reuse hooks available on npm simply by aliasing package names in your bundler's config.

Currently Haunted supports the following hooks:

Function Signatures

// Or another renderer, see Guides
type Renderer = (element: Element) => TemplateResult;

interface Options {
  baseElement: HTMLElement;
  observedAttributes: string[];
  useShadowDOM: boolean
}

declare function component(
  renderer: Renderer,
  options: Options
): Element;

declare function component<BaseElement = HTMLElement>(
  renderer: Renderer,
  baseElement: BaseElement,
  options: Options
): Element

declare function virtual(renderer: Renderer): Directive

License

BSD-2-Clause

haunted's People

Contributors

alfredosalzillo avatar beefonweck avatar bennypowers avatar chuanqisun avatar cristinecula avatar crisward avatar danychi avatar davidnx avatar dependabot-preview[bot] avatar dependabot[bot] avatar github-actions[bot] avatar gladear avatar idoros avatar joryphillips avatar jpray avatar jul-sh avatar jwharrie avatar matthewp avatar matthewpblog avatar micahjon avatar mikabytes avatar nicolasparada avatar noahlaux avatar pdeona avatar rawrmonstar avatar sgtpep avatar styfle avatar sunesimonsen avatar webreflection avatar

Stargazers

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

Watchers

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

haunted's Issues

Website

I started working on a website a while back. It's available in the site branch. it looks like below:

screen shot 2019-02-18 at 7 36 34 am


I don't think this should be that big of a site. I was thinking of adding a small section below this hero area that shows a code example. Then maybe just link to the repo.

Oh, and currently it says it's made by me, but really others have been taking on the bulk of the work in the last couple of months so it should say made by collaborators instead. Or maybe we should use ghouls or some other ghostly word...

initialize the property from attribute value

import { html } from 'lit-html'
import { component, useState } from 'haunted'

function Counter() {
  const [count, setCount] = useState(0);

  return html `
    <div id="count">${count}</div>
    <button type="button" 
      @click=${() => setCount(count + 1)}>
      Increment
    </button> 
  `;
}

customElements.define('my-counter', component(Counter))
<my-counter count="10"></my-counter>

the count property will have default value of 10

stackblitz
https://stackblitz.com/edit/typescript-p7v7ds

Benefits of virtual?

I'm not sure what the advantage of:

const v = virtual(text => html`<b>${text}</b>`);

…is over:

const v = text => html`<b>${text}</b>`;

…when used in this context:

const App = ({name}) => html`Hello ${v(name)}!`;
App.observedAttributes = ['name'];
customElements.define('my-app', component(App));

document.body.innerHTML += `<my-app name="asdf"></my-app>`;

Ensure build before releasing

I think the last 2 releases weren't built with the merged changes:

  • 3.2.0 doesn't seem to include useCallback
  • 3.1.0 doesn't seem to use the useState updater function.

I think it might be a good idea to add a preversion: npm run build script in the package.json that will run a new build whenever npm version [ major | minor | patch | etc ] is run to bump the version.

Boolean attribute behavior confusing

The current boolean attribute behavior might be intentional, but I find it really confusing that something that is used a string attribute turns into a boolean attribute when the value is an empty string:

boolean attribute

It could be that I just need to use some other mechanism that I don't know about. But I just wanted to say that I found this behavior surprising.

I would still expect an attribute without any value to be true and a missing attribute to be undefined when retrieving the value.

The code that is doing this conversion can be found here:
https://github.com/matthewp/haunted/blob/master/src/component.js#L34

Typing for `component` appears incorrect

Given the following example on the readme:

const App = component(({ name }) => {
  return html`Hello ${name}!`;
});

... the following typing in index.d.ts seem incorrect:

export function component(renderer: (el: HTMLElement) => TemplateResult, BaseElement?: Function, options?: {
    useShadowDOM: boolean
}): Function;

Specifically, I would expect the arrow function passed to component in the example to have the following type:

({ name }: { name: string }) => TemplateResult

... which will cause a type error because Property 'name' does not exist on type 'HTMLElement'. ts(2339).

Assuming any arbitrary parameters can be defined for a function passed to component, a solution may be (off the top of my head):

export function component<T>(renderer: (props: T) => TemplateResult, BaseElement?: Function, options?: {
    useShadowDOM: boolean
}): Function;

To implement React Hooks API or to be based/inspired by it?

There are a number of React hooks that aren't implemented in haunted (useRef, useImperativeHandle, useLayoutEffect, useDebugValue). Personally I am ok with this as I think there are substantive differences between web components and React that should influence the hooks patterns for each.

If the goal of haunted is to be "React's Hooks API implemented for web components", when we are kinda stuck needing to implement the full API, and nothing more. If the goal is tweaked to something like "Web Component Hooks, inspired by React", then not implementing everything React has is okay and we have more freedom to innovate. Thoughts?

Renderer function should be called with the element as the this

Currently the renderer receives the element as the first and only argument. It makes sense to also provide it as the this value. This way you can destruct for properties in the argument and use this for the element if needed for imperative usage. For example:

function App({ open }) {
  useEffect(() => {
    this.querySelector('div').addEventListener ...
  }, []);

  return html`<div class="${open ? 'open' : ''}>...</div>
}

Add useContext(Context): ContextValue

I think this might need context implementation first of all.

I have been thinking about this for quite a while:), but was struggling with design of the API,
I think hooks can simplify the API and finally we can have context in DOM.

As a solution CustomEvents can be used, since the CustomEvents are synchronous and you can pass detail object to it, parent provider could listen to an event and provide the context value in the same object or consumer can pass subscriber function that provider will call when context value changes.

API can be:

context

export const Context = createContext(defaultValue)

app

import { Context } from 'context';
// ... .provide can be a directive that gets the value and 
// assigns to prop of any DOM element, it can also 
// just wrap whole element, but this way you can easily debug it from elements inspector
html`
   <div .value=${Context.provide(contextValue)}>
       ....someCode
       ....and somewhere in a tree
               <my-element></my-element>
   </div>
`

my-element

import { Context } from 'context';

function MyElement() {
    const contextValue = useContext(Context);

    return html`
      <div>${contextValue}</div>
    `;
}

customElements.define('my-element', component(MyElement));

Relationship can be defined by reference of created context (like shown above) or alternatively by unique string, but then drawback is name collision.

With references drawback is, if my-element` is living in different source code, it needs to mock Context and intercept import to inject mocked module, otherwise it is perfect.

Let me know what you think

Error in readme

I was looking at the readme, and i saw this:

import { component } from 'haunted';
import { html } from 'lit-html';

const App = component(({ name }) => {
  return html`Hello ${name}!`;
});

customElements.define('my-app', component(App));

Why is it calling component 2 times? I think it should only call it once!

Make use of directives

Most of the time application developers do not need shadowDom encapsulation, since they consume ready components and don't need custom elements for each reusable block of code, they can just create functions that return templates, but hooks will be used for sure.

In order to achieve this, lit-html directives could be used, and some internal base Element needs to be created with life cycle hooks, but would this mean creating React over lit-html?

LitElement Support?

Hi @matthewp

First of all thanks a lot for your library.
I am using it now in my project and I see a great potential.

I have a question / proposal.

I wanted to combine haunted with LitElement but of course it does not work if you just do something like:

component(() => html``, LitElement)

(That was my first attempt :)).

Then I have studied the source code to understand the logic and came up with the following:

// lit-component.js
export {LitElement} from 'https://unpkg.com/lit-element@^2.1.0/lit-element.js';
import {effectsSymbol, hookSymbol} from "./symbols";
import {clear, setCurrent} from "./interface";

class LitContainer {

    constructor(renderer, host) {
        this.renderer = renderer;
        this.host = host;
        this[hookSymbol] = new Map();
    }

    update() {
        this.host.requestUpdate();
    }

    render() {
        setCurrent(this);
        const result = this.renderer.call(this.host, this.host);
        clear();
        return result;
    }

    runEffects(symbol) {
        let effects = this[symbol];
        if (effects) {
            setCurrent(this);
            for (let effect of effects) {
                effect.call(this);
            }
            clear();
        }
    }

    teardown() {
        let hooks = this[hookSymbol];
        hooks.forEach((hook) => {
            if (typeof hook.teardown === 'function') {
                hook.teardown();
            }
        })
    }
}

/**
 * Works with components based on LitElement
 * @param renderer the renderer function
 * @param BaseElement - LitElement or its subclass
 * @param options ...
 * @returns {Element}
 */
export function component(renderer, BaseElement = LitElement, options = {useShadowDOM: true}) {
    class Element extends BaseElement {
        static get properties() {
            return renderer.observedAttributes || [];
        }

        createRenderRoot() {
            return options.useShadowDOM ? this.attachShadow({mode: "open"}) : this;
        }

        constructor() {
            super();
            this._container = new LitContainer(renderer, this);
        }

        render() {
            return this._container.render()
        }

        updated(_changedProperties) {
            if (this._container[effectsSymbol]) {
                this._container.runEffects(effectsSymbol);
            }
            super.updated(_changedProperties);
        }

        disconnectedCallback() {
            this._container.teardown();
            super.disconnectedCallback();
        }
    }

    return Element
}

It does work for me at the moment although I might miss something. I need to test it a bit more.

But the problem is that in order to use this piece of code I have to compile my own lit-element-haunted :).

So haven't you thought about adding support for lit-element or about adding some kind of plugin infrastructure so that it would be possible to add this kind of stuff in runtime without recompilation?

I am ready to help if it is only a time issue :)

Thanks a lot!

Something like useHost....

Sometimes we may need to access the raw element within the render function. This is easy enough when using a real component (this) but when the render is a arrow function, this is undefined. Do you think it would be useful to add a useHost (name TBD) hook? Something like:

  const useHost = hook(class extends Hook {
    constructor(id, el, initialValue) {
      super(id, el);
    }

    update() {
      return this.el.host;
    }
  });

Context and virtual components

Now we have a React-like context (see issue #17). But we can't use it in an application built with virtual components only (without web components).

I think we should provide some directives for lit-html after creating context. However, we need to discuss and choose the comfortable api for it...

An abstract example of usage:

const ThemeContext = createContext('dark');

const { provide, consume } = ThemeContext;

const MyConsumer = virtual(() => {
  const context = useContext(ThemeContext);

  return context;
});

const App = virtual(() => {
  const [theme, setTheme] = useState('light');

  return html`
      <select value=${theme} @change=${(e) => setTheme(e.target.value)}>
        <option value="dark">Dark</option>
        <option value="light">Light</option>
      </select>
      
      ${provide(theme, html`
        ${MyConsumer()}
        ${provide(theme === 'dark' ? 'light' : 'dark', html`
          ${consume((value) => html`<h1>${value}</h1>`)}
        `)}
      `)}
  `;
});

render(App(), document.body);

bundle size

Why is the library size so big?
haunted - 4.9 kb with gzip
lit-html - 3.3kb gzip/lighterhtml 5.8kb

for comparison Preact 3.5kb
what is the advantage?

Error in safari mobile 10.3

Hi, I'm building a component and testing it on safari mobile 10.3 and I getting this error: TypeError: The Element.shadowRoot getter can only be used on instances of Element on this line: this._container = new Container(renderer, this.shadowRoot, this); do you know why is this error? I need to make works haunted on Safari 10.3, please help me :(

Type coercion and other property goodies

LitElement has a lot of flexibility in how properties are handled: https://lit-element.polymer-project.org/guide/properties

The following options are available:

  • converter: Convert between properties and attributes.
  • type: Use LitElement’s default attribute converter.
  • attribute: Configure observed attributes.
  • reflect: Configure reflected attributes.
  • noAccessor: Whether to set up a default property accessor.
  • hasChanged: Specify what constitutes a property change.

The only 2 features that I'm missing when using haunted are

  • "type" to coerce attributes (String) to String, Number, Boolean, Array, and Object properties
  • "reflect" to specify whether to reflect property updates back to the attribute

Of those 2, "type" is the most common need for me. I also think handling of default values for properties/attributes might be a bit awkward currently.

What might a good approach be for accomplishing some of the above in haunted?

For reference, here's a simple use case, maintaining state in a property instead of in useState(): https://codesandbox.io/s/z275vomrqp

Implicit dependency on lit-html and unpkg

Line #1 imports from a URL which seems like it could be bad practice, especially if the user wants to pin a version of lit-html instead of using the latest version.

Instead, maybe the API could be changed to accept the lit-html object or maybe just the render() function to maintain low-coupling?

It could look something like this:

  import { html, render } from 'https://unpkg.com/[email protected]/lit-html.js';
  import { Hooks } from 'https://unpkg.com/@matthewp/[email protected]/haunted.js';
  const { component, useState } = new Hooks(render);

  function Counter() {
    const [count, setCount] = useState(0);

    return html`
      <div id="count">${count}</div>
      <button type="button" @click=${() => setCount(count + 1)}>Increment</button>
    `;
  }

That way both html and render are coming from the same version of lit-html πŸ˜„

virtual component uses part to associate with container

Since, container for virtual component is mapped to part in cache, same container is reused for other component:

html`${<p>${predicate ? ComponentOne() : ComponentTwo()}</p>}`

I made demo reproducing the issue

I haven't done deep dive into solving it, but from first sight container can be mapped to renderer instead of part

Unable to use with Puppeteer

I have some lit-element components what I'm migrating to haunted and works great, with the exception that cannot be tested using puppeteer.

const card = await page.evaluate(_ => document.querySelector('ck-card'))
expect(card).toBeDefined() // test fails due `card` is undefined.

Researching about this, I found the following note in puppeteer docs:

evaluate method returns undefined if the item to return is not a serializable object.

I understand what serialization is, but I don't understand how is related to a DOM element. Can someone explain me a bit please?

PD: LitElement components works good.

Try to be agnostic of rendering engine, try to remove hard dependency on lit-html

If haunted can't be completely agnostic, then at least we should have first-class support for hyperHTML/lighterhtml and make it so people can use that library without pulling in lit-html also. But if we can be completely agnostic by making the setup/initialization slightly more verbose, then I think its worth it.

Before getting into the weeds of the implementation details, I'd be interested to hear what the most preferred public API would be to support this.

Example 1:
my-app/wc-toolkit.js

import { haunted } from 'haunted';
import { render, html } from 'lit-html';

const component = haunted(render); // returns a component function setup to use lit's render function

export { component, html}

Any other ideas?

Typescript typings

Are there any plans to add Typescript definition files to the project any time soon?

add option to disable shadowDom for createContext

I know it is not possible right now, so I just copied it like so

import { useContext, component } from "haunted";

const contextEvent = "haunted.context";

export const createContext = defaultValue => {
  const Context = {};

  Context.Provider = class extends HTMLElement {
    constructor() {
      super();
      this.listeners = [];

      this.eventHandler = event => {
        const { detail } = event;

        if (detail.Context === Context) {
          detail.value = this.value;

          detail.unsubscribe = () => {
            const index = this.listeners.indexOf(detail.callback);

            if (index > -1) {
              this.listeners.splice(index, 1);
            }
          };

          this.listeners.push(detail.callback);

          event.stopPropagation();
        }
      };

      this.addEventListener(contextEvent, this.eventHandler);
    }

    disconnectedCallback() {
      this.removeEventListener(contextEvent, this.eventHandler);
    }

    set value(value) {
      this._value = value;
      this.listeners.forEach(callback => callback(value));
    }

    get value() {
      return this._value;
    }
  };

  Context.Consumer = component(
    function({ render }) {
      const context = useContext(Context);

      return render(context);
    },
    HTMLElement,
    { useShadowDOM: false }
  );

  Context.defaultValue = defaultValue;

  return Context;
};

haunted doesn't manage "dashed" name attributes

☝️ when i want to create a Web Component with a, for example, <my-wc first-order="342"></my-wc> the attribute first-order cannot be used like an MyWc.observedAttributes = ['first-order'] i think the "dashed attributes" are the regular way to name webcomponents attributes

Virtual component never unmounts

I've found an issue when using virtual components. The teardown function returned from useEffect is never invoked. Also, state remains after a virtual component is unmounted.

The following sandbox uses the same function as both a real component and a virtual component.

When showing/hiding the real component, the count is reset, as expected.
However, when showing/hiding the virtual component, the count remains the same.

Also, as you can see from console.log, the virtual counter's useEffect teardown is never invoked.

https://codesandbox.io/s/mzvrmj2z99

I noticed issue #42, but I don't know if this is related, so I created a new issue.

Typescript error with context

Exporting context throws a Typescript error when setting the --declaration
-d flag
. This is needed in order to create typed context that can be imported into dependent projects (generate the .d.ts files for the published project).

Example:

import {createContext } from 'haunted'
export const MyContext = createContext({})

Will fail with: Exported variable 'MyContext' has or is using name 'Context' from external module ".../node_modules/haunted/index" but cannot be named.ts(4023)

This can be fixed by simply adding export for the Context interface in haunted/index.d.ts:

export interface Context {...}

--- Edit:

Actually the other issue with the context types is that it doesn't really support the type that is passed through it. The Context type doesn't offer any generics type for the value. So useContext simply returns any instead of the value type of the context.

Would you accept a PR adding this to the Typescript definitions?

IE11

A bit of a longshot, but has anyone got this working in IE11, perhaps with babel transpiling? Got it working in Edge, but IE11 seems to trip up on the template literals. I'm using it with lit-html as per examples.

Incompatabilities between lit0.13 and lit0.14

I tried running one of the demos and it took me a second to understand what was going wrong. I used the following code:

<!doctype html>
<html lang="en">
<my-counter></my-counter> 
<script type="module">
  import { html as lithtml } from 'https://unpkg.com/lit-html/lit-html.js';
  import { component, useState, html as hauntedhtml} from 'https://unpkg.com/haunted/haunted.js'; 

  function Counter() {
    const [count, setCount] = useState(0); 
    return lithtml`
      <div id="count">${count}</div>
      <button type="button" @click=${() => setCount(count + 1)}>Increment</button>
    `;
  } 

  customElements.define('my-counter', component(Counter));
</script>

and got this as my render output:
image

Switching to hauntedhtml works fine. From what I can tell, it looks like lit is on v0.14 while haunted is using 0.13. Not sure what changed between the two version but I just wanted to give a heads up! My time is a little limited right now but I can try and pitch in if the change is simple.

Support for not using ShadowDOM and guidance on contributing new hooks

This library is my favorite thing since lit-html/hyperHTML and a joy to work with. Major kudos for creating it. I have 2 questions:

  1. I don't use ShadowDOM. It was easy adding an option to make this optional (jpray@044da1e). If I add tests and documentation, is this something you'd consider?

  2. I've created a number of hooks, some of which might be useful to others. Do you see the number of hooks added to this project continuing to grow? If so, would the idea be that unused hooks in an app should be tree-shaken away during a build (since they are all being imported and re-exported from the base module). Or should they exist in the library but require a separate import? e.g:

import { component } from "haunted";
import { useSharedState } from "haunted/hooks/use-shared-state";

I guess another alternative would be for a "haunted-extra" library to popup that has lots more hooks in it. What would you like to see happen?

Attributes

Haunted doesn't yet support attributes. I feel like this is a missing feature! Ideally you would be able to do:

import { html } from 'lit-html';
import { useMemo } from '@matthewp/haunted';

function FullName({ first, last }) {
  const name = useMemo(() => first + " " + last, [first, last]);

  return html`<span>${name}</span>`
}

function App() {
  return html`<full-name first="Wilbur" last="Phillips"></full-name>`
}

customElements.define('my-app', component(App));

Since the functional component receives the element instance as its argument you can destruct to get props. This works for props today but not for attributes.

Options:

useAttrs

My first thought was to have a useAttrs(["first", "last"]). To define attributes. This feels the most "hooks" way of doing it. It would look like:

import { useAttrs } from '@matthewp/haunted';

function FullName() {
  useAttrs(["first", "last"]);
}

Options bag

We could also support passing options into component() like so:

import { component } from '@matthewp/haunted';

component(App, { attrs: ["first", "last"] });

The downside here is that we would need to run the render() once before actually rendering, just to see what attrs are used. We could maybe be sneaky and regex extract these 😲

Use mutation observers

Lastly we could just not rely on attributeChangedCallback but instead set up a mutation observer for the element and observe attribute changes that way. In this scenario I think we would automatically set those attributes as property values as well.

The benefit of this method is that you wouldn't need to predefine which attributes your element supports; it would support arbitrary attributes. This method does slightly scare me though.

Support boolean attributes

Currently the attribute <some-el open> will reflect as an empty string. We should have special support for these boolean attributes to turn them into booleans.

Something I am not quite getting in update schedules

I want to clarify motivation behind current implementation of updates in components.
As I understand getting template result is done in RAF for batching then next RAF is scheduled for commit or DOM updates, then next RAF for effect hooks.

There are few things that I think might cause problems, but may be that I am not seeing the whole picture:

  • read and commit could happen in the same phase, since we already got the results in previous RAF but haven't painted anything. Is it for splitting up the work, aren't microtasks better for batching?
  • in commit phase which is scheduled in second spin of render loop, DOM is updated and so do props of components that will start their own update schedule, so for each nesting 2 frames are needed to render one change.For example, passing down the prop from top component that has 30 levels of nested components, that also change as a result of property changes it means we get 60 frames to render whole tree which might take 1 second at best:)

In my imagination all the prop updates need to be batched and then single run to read whole tree and only after one RAF to update whole tree, pretty much how I imagined React to work before fiber.

And I would prefer webcomponents to be rendering syncronously without scheduling since they are leaf nodes, unless there are some exceptional needs.

And virtual components, ones that wrap many of those leafs, need to have these scheduling mechanism, but they have to work in sync, don't schedule another RAF if you already scheduled one.

From docs about effects in React page, seems like RAF is the closes place to run them, after commit. So no questions about that:)

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.