Giter VIP home page Giter VIP logo

bs-webapi-incubator's Introduction

bs-webapi

IMPORTANT: this repository is deprecated in favour of https://github.com/tinymce/rescript-webapi/

Please use ReScript-WebAPI going forward.

Experimental bindings to the DOM and other Web APIs.

npm Travis

API docs are available at https://reasonml-community.github.io/bs-webapi-incubator/api/Webapi/ , but documentation comments are sparse as the code mostly just consists of external declarations with type signatures. The bindings generally also correspond very well to the Web APIs they bind to, so using MDN along with GitHub should go a long way.

Installation

npm install bs-webapi

Then add bs-webapi to bs-dependencies in your bsconfig.json. A minimal example:

{
  "name": "my-thing",
  "sources": "src",
  "bs-dependencies": ["bs-webapi"]
}

Usage

See the examples folder

Please only use the modules exposed through the toplevel module Webapi, for example Webapi.Dom.Element. In particular, don't use the 'flat' modules like Webapi__Dom__Element as these are considered private and are not guaranteed to be backwards-compatible.

Some notes on the DOM API

The DOM API is mostly organized into interfaces and relies heavily on inheritance. The ergonomics of the API is also heavily dependent on dynamic typing, which makes it somewhat challenging to implement a thin binding layer that is both safe and ergonomic. To achieve this we employ subtyping and implementation inheritance, concepts which aren't very idiomatic to OCaml (or Reason), but all the more beneficial to understand in order to be able to use these bindings effectively.

Subtyping

The Dom types, and the relationships between them, are actually defined in the Dom module that ships with bs-platform (Source code), where you'll find a bunch of types that look like this:

type _element('a);
type element_like('a) = node_like(_element('a));
type element = element_like(_baseClass);

This is subtyping implemented with abstract types and phantom arguments. The details of how this works isn't very important (but see #23 for a detailed explanation of how exactly this trickery works) in order to just use them, but there are a few things you should know:

  • If you expand the alias of a concrete DOM type, you'll discover it's actually a list of abstract types. e.g. element expands to _baseClass _element _node _eventTarget_like This means element is a subtype of _element, _node and _eventTarget_like.
  • The _like type are "open" (because they have a type variable). This means that a function accepting an 'a element_like will accept any "supertype" of element_like. A function accepting just an element will only accept an element (Technically element is actually a "supertype" of element_like too).

This system works exceptionally well, but has one significant flaw: It makes type errors even more complicated than they normally are. If you know what to look for it's not that bad, but unfortunately the formatting of these errors don't make looking for it any easier. We hope to improve that in other ways (see BetterErrors)

Implementation inheritance

If you've looked through the source code a bit, you've likely come across code like this:

include Webapi__Dom__EventTarget.Impl({ type nonrec t = t });
include Webapi__Dom__Node.Impl({ type nonrec t = t });
include Webapi__Dom__ParentNode.Impl({ type nonrec t = t });
include Webapi__Dom__NonDocumentTypeChildNode.Impl({ type nonrec t = t });
include Webapi__Dom__ChildNode.Impl({ type nonrec t = t });
include Webapi__Dom__Slotable.Impl({ type nonrec t = t });
include Impl({ type nonrec t = t });

This is the implementation inheritance. Each "inheritable" module defines an "Impl" module where all its exported functions are defined. include Webapi__Dom__Node.Impl { type nonrec t = t }; means that all the functions in Webapi__Dom__Node.Impl should be included in this module, but with the t type of that module replaced by the t type of this one. And that's it, it now has all the functions.

Implementation inheritance is used instead of subtyping to make it easier to understand which functions operate on any given "subject". If you have an element and you need to use a function defined in Node, let's say removeChild you cannot just use Node.removeChild. Instead you need to use Element.removeChild, which you can since Element inherits from Node. As a general rule, always use the function in the module corresponding to the type you have. You'll find this makes it very easy to see what types you're dealing with just by reading the code.

Changes

See CHANGELOG.md.

bs-webapi-incubator's People

Contributors

alex35mil avatar bbqbaron avatar bloodyowl avatar bsansouci avatar chenglou avatar clentfort avatar dmartinjs avatar evilpie avatar glennsl avatar heklarun avatar henoc avatar illusionalsagacity avatar johnridesabike avatar leomayleomay avatar malthe avatar mransan avatar msvbg avatar parkerziegler avatar risto-stevcev avatar samwyld avatar spocke avatar stuartkeith avatar tiansijie avatar tsnobip avatar utkarshkukreti avatar vincenthelwig avatar vramana avatar wyze avatar yawaramin avatar zploskey 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

bs-webapi-incubator's Issues

History

Shouldn't History.state be parameterized?

Should we drop the test script and just check in the test artifacts?

cc @chenglou

Other than potentially forgetting to check in the rendered artifacts and not having it be caught by CI, do we really lose anything by not having a test script and the explicit "expected" snapshots, compared to just using checked in artifacts as snapshots and the review process to verify them? It might even be a win, since there would be more eyes on it.

Upstream canvas bindings

I've seen quite a few user land canvas bindings now, and upon perusing webapi's, I feel they're ready to be upstreamed? Tell me if I'm mistaken. Otherwise, great stuff once again, @glennsl @bloodyowl =D

Also, anyone dogfooding webGLRe.re?

Event types are confusing for newcomers

I'm trying out Reason and BuckleScript and trying to convert a simple canvas demo as a learning vehicle. There are a couple of other bs canvas demos that have been published, and it involves little interaction with the DOM. So far so good.

But getting the mouse coordinates and satisfying the type system was surprisingly confusing with these bindings. This is what I tried:

let display = Document.getElementById "display" document;

let canvas = switch display {
  | Some display => display
  | None => raise (Failure "Could not find #display DOM Node")
};

let ctx = ReasonJs.CanvasElement.getContext canvas "2d";

let onMouseMove e => {
  state.mouse.x = MouseEvent.clientX e;
  state.mouse.y =  MouseEvent.clientY e;
};

Element.addEventListener "mousemove" onMouseMove canvas;

but this results in a type error:

Error: This expression has type ReasonJs.Dom.MouseEvent.t => unit
       but an expression was expected of type Dom.event => unit
       Type
         ReasonJs.Dom.MouseEvent.t =
           Dom.event_like (Dom._uiEvent (Dom._mouseEvent Dom._baseClass))
       is not compatible with type Dom.event = Dom.event_like Dom._baseClass 
       Type Dom._uiEvent (Dom._mouseEvent Dom._baseClass)
       is not compatible with type Dom._baseClass

@glennsl helped on Discord and suggested adding a type conversion function:

external eventToMouseEvent : Dom.event => MouseEvent.t = "%identity";

which works great! But:

  1. Is there a better way of doing this?
  2. This is very confusing for newcomers like myself. Any documentation or improvement in the underlying type signatures would go a long way here. I suspect that others will also hit this straight away as well.

Bug with Url bindings

When doing:

let url = Webapi.Url.make("http://www.example.com");
Js.log(url->Webapi.Url.hostname);

The generated javascript is:

console.log(url.hostname());

This causes an error as the property is not a function.

Should change [@bs.send.pipe : t] to [@bs.get]

WebSocket

I need to use websocket for the project I am working on, and it is missing from here.

Here is what I am using so far:

module MessageEvent = {
  type t;
  external data : t => ArrayBuffer.t = "" [@@bs.get];
};

module WebSocket = {
  type t;
  external create_ws : string => t = "WebSocket" [@@bs.new];
  external setBinaryType : t => string => unit = "binaryType" [@@bs.set];
  external onMsg : t => (MessageEvent.t => unit) => unit = "onmessage" [@@bs.set];
  external onOpen : t => (unit => unit) => unit = "onopen" [@@bs.set];
  external send : Uint8Array.t => unit = "" [@@bs.send.pipe : t];
  external onError : t => (unit => unit) => unit = "onerror" [@@bs.set];
  external onClose : t => (unit => unit) => unit = "onclose" [@@bs.set];
  let make url => {
    let ws = create_ws url;
    setBinaryType ws "arraybuffer";
    ws
  };
  let onMsg ws cb => onMsg ws (fun ev => MessageEvent.data ev |> Uint8Array.fromBuffer |> cb);
};

This is only designed for a binary use case / mine, its also incredibly incomplete.

I will overtime improve it as necessary for my use case, but otherwise, I am asking what the plan is for this api. I can definitely help write the types for it.

Although I know it isn't in the browser, making a Sink / Stream api from this would make it more pleasant to use. Perhaps taking something from most.js (which I know someone in the discord is making type definitions for).

Activate namespaces

Wouldn't it make senes to activate namespaces in this project? I see that the Bs_webapi module was deprecated in favour of Webapi but I still think it makes sense to activate namespaces here.

We're using this for some shimmed APIs in React Native but we needed to vendor this package to activate namespaces because both projects have a file called ImageRe.re

Can't dispatch custom events.

This doesn't typecheck:

Webapi.Dom.EventTarget.dispatchEvent(
  Webapi.Dom.CustomEvent.makeWithOptions(
    "my-event",
    {
      "detail": {
        "test": "test",
      },
    },
  ),
  element
)

Errors:

Webapi.Dom.CustomEvent.makeWithOptions(...

  This has type:
    Webapi.Dom.CustomEvent.t (defined as Dom.event_like(Dom._customEvent))
  But somewhere wanted:
    Dom.event (defined as Dom.event_like(Dom._baseClass))
  
  The incompatible parts:
    Dom._customEvent
    vs
    Dom._baseClass

If I switch from CustomEvent to Event it works and dispatches but the details are not included in the result.

records for DOM events

I'm curious about the choice of functions to get event properties instead of passing recordings to event handlers. I come from a JS background and find calling functions to access fields on events quite awkward.

Fetch only supports GET requests

I'm trying to do POST requests with the fetch API, but I think currently it's not supported (according to the signature, it only takes a string, nothing more).

external fetch : string => CoreRe.promise response = "" [@@bs.val];

Are there plans to do a fetchPost version?

"Cannot find module 'bs-platform/lib/js/curry.js' from 'bs-webapi/src/dom/nodes/ElementRe.js'" error when used with brunch

Hi,

I've stumbled upon this error when trying to integrate bucklescript into our JS for a project. We don't currently have the possibility to change the build system so I'm stuck with brunch.

Building with bsb isn't really an issue, but on page load when trying to use bs-webapi I get the following error:

Cannot find module 'bs-platform/lib/js/curry.js' from 'bs-webapi/src/dom/nodes/ElementRe.js'

This, to me, seemed strictly like a brunch issue until I tried using another library to see if it actually was a general issue. bs-fetch is usable even though it also depends on bs-platform. I'm not sure if this is just because of dead code elimination and it ends up not using any modules from bs-platform, though.

bs-platform is at v2.2.3
bs-webapi is at v0.8.3

Should setters be converted into pipe form?

Pipe form would enable usage like:

let el = document
  |> Document.createElement "div"
  |> Element.setInnerText "</>"
  |> Element.setClassName "reason_tools_button"
  |> setOnClick swap
  |> Body.appendChild;

And it would be more consistent with "regular" functions. But without BS support, it would be a bit of work. See also rescript-lang/rescript-compiler#1188

Missing "navigator"

E.g.

external navigator: navigator = "" [@@bs.val]
external userAgent: string = "" [@@bs.val] [@@bs.scope "navigator"]

Missing *_to_opt annotations

Hi, similarly to #49, several FFI functions returning a 'a option miss a [@@bs.return (undifined|null)_to_opt], which breaks any match afterwards (getting undefined through Some).

I found this when trying to use:

[@bs.send.pipe : t] external item : int => option(Dom.node) = "";

But it seems that a bunch of other functions are suffering from the same issue, like

[@bs.get] external lastChild : T.t => option(Dom.node) = "";
or
[@bs.send.pipe : t] external nextNode : option(Dom.node) = "";
[@bs.send.pipe : t] external previousNode : option(Dom.node) = "";
etc

Improve types of cloneNode and cloneNodeDeep

Currently cloneNode has the type signature:
'a Dom.node_like -> Dom._baseClass Dom.node_like

But with this signature, if I clone an element, it becomes a node, so I can't appendChild etc.

As I understand it we could either use a different return type for each module cloneNode appears in, or make use of the generic inheritance thing and change it to:
'a Dom.node_like -> 'a Dom.node_like

Awkward syntax when working with Promises

Currently, to fetch and log out an API response requires something like the following:

let p = ReasonJs.fetch "https://jsonplaceholder.typicode.com/posts/1";
ReasonJs.Promise.thenDo (ReasonJs.Promise.thenDo p ReasonJs.Response.text) Js.log;

This is rather unwieldy, but can be made somewhat cleaner by aliasing some of the functions:

let fetch = ReasonJs.fetch;
let then_ = ReasonJs.Promise.thenDo;
let p = ReasonJs.fetch "https://jsonplaceholder.typicode.com/posts/1";
then_ (then_ p ReasonJs.Response.text) Js.log;

By flipping the arguments, I am able to get an even nicer syntax:

let fetch = ReasonJs.fetch;
let then_ a b => ReasonJs.Promise.thenDo b a;
let p = fetch "https://jsonplaceholder.typicode.com/posts/1";
p |> then_ ReasonJs.Response.text |> then_ Js.log;

Perhaps the API can be reshaped to make usage more idiomatic.

Add functional tests

Might have to write bindings for a test framework first. Or use/extend bs-mocha.

Suggestion: TypeScript typings to Reason bindings transformer

Instead of doubling the effort in re-creating the DOM bindings it might be simpler to transform the AST of TypeScript definitions into Reason bindings. The added benefit would be that such a transformer would be universal - given the huge number of good quality typings available for TypeScript, Reason could quickly become a hugely popular alternative to it, especially given the BuckleScript 1.0 release and the fantastic benefits it offers over TS.

Thoughts?

cancelAnimationFrame is not supported

Per MDN, requestAnimationFrame should return a long, which can then be passed to its sister function cancelAnimationFrame to abandon the animation request before it is rendered. However, the Webapi module currently only exports requestAnimationFrame : ((float) => unit) => unit.

URLSearchParams.get returns incorrect type

Currently the type of get in URLSearchParamsRe.re is:
[@bs.send.pipe : t] external get: string => option(string) = "";

As you can see in the documentation here

The type I think should be:
[@bs.send.pipe : t] external get: string => Js.Nullable.t(string) = "";

Quite happy to make a PR if I am correct about this :)

Rename htmlElementRe

Given that the spec calls this DocumentElement and that HTMLElement is something else, this is very confusing. Don't you think renaming the module DocumentElement would help make it easier?

Mysterious error with `-make-world`

:;  node_modules/.bin/bsb -make-world
    Regenerating build spec : File not found
    Fatal error: exception Sys_error("tests/fetch: No such file or directory")
    Error happened when running command .../reason-js/node_modules/bs-platform/bin/bsb.exe with args [ '-make-world' ]

It seems like it's looking for the directory structure in src/ to be mirrored in tests/.

:;  cp -a src/fetch/ tests/fetch/
:;  node_modules/.bin/bsb -make-world
    Regenerating build spec : File not found
    Fatal error: exception Sys_error("tests/gl: No such file or directory")
    Error happened when running command /Users/solidsnack/Downloads/reason-js/node_modules/bs-platform/bin/bsb.exe with args [ '-make-world' ]

How does the .re code get translated to .ml?

What should the naming convention for abbreviations be?

Due to OCaml's rules for which identifiers can be lower case and upper case, the current convention is PascalCase/camelCase, e.g. "Html", "Json", "dom" etc.

Should it be "DOM"/"dom", "JSON"/"json" instead? How would that work with module fiilenames? Should it be e.g. "jsonRe.re", it would then have to be used internally as "JsonRe" and be exported as "module JSON = JsonRe".

offsetParent should return an option?

offsetParent can return null (a common situation is recursively walking up the DOM until offsetParent returns null - i.e. the body element has been reached).

However, the binding of offsetParent does not return an option - so this case is not detectable and can cause an exception.

The following fixed it for me:

[@bs.get] [@bs.return nullable] external offsetParent : HtmlElementRe.t => option(Dom.element) = "";

Thanks!

Canvas set size

How to set width and height to canvas ?
I must write declaration like follow code ?

[@bs.set] external setWidth : (Dom.element, int) => unit = "width";
[@bs.set] external setHeight : (Dom.element, int) => unit = "height";

and then

  setWidth(canvas, ElementRe.clientWidth(DocumentRe.documentElement(document)));
  setHeight(canvas, ElementRe.clientHeight(DocumentRe.documentElement(document)));

or has any solution ?

Missing LICENSE

Repository has no LICENSE. We should add one. MIT? BSD? LGPL? WTF?

Missing "window.screen"

E.g.

external availHeight: int = "" [@@bs.val] [@@bs.scope "window", "screen"]
external availWidth: int = "" [@@bs.val] [@@bs.scope "window", "screen"]

Opening Webapi.Dom shadows `None`

Reason beginner here, so apologies in advance if this is not a bug.

If I open Webapi.Dom, it shadows the None constructor, and I cannot have optional variables in the same file.

It currently shadows None with Webapi.Dom.eventPhase.

2d canvas API?

Hi,
I'm writing a simple 2d canvas game, and have been building up a bunch of bindings for the CanvasRenderingContext2D API. Would it be helpful to add them in a pull request here? If so, a couple of questions:

  • There's an existing ReasonJs.CanvasElement.getContext function that takes a string, and returns a context. However the existing context API here is for WebGL, not the 2d API. So I'd propose changing this to getWebGLContext, and creating a new get2dContext function. It looks like [@bs.as] will work to pass the appropriate string parameter in to js's getContext

  • ints or floats for canvas methods? I've been using floats myself but I'm wondering if there's a case for integer arguments

discussions about namespace

I noticed that all files in such package ends with Re,
maybe we can add a style field in bsconfig, like

  { namespace : "re",   format: "suffix"}

So that the build system will generate a map file for only exported units

re.ml

module CssDeclaration = CssDeclarationRe

So that ender users could call Re.CssDeclaration with auto-completion.

Two issues:

  1. which style would be better suffix vs prefix
    (seems convention is prefix based: ocaml/ocaml#1010)
  2. Should we reuse package name
  3. We can hide the complexity by generating prefix even for in-source packages, but as you said, companies like unique file name (this is the same with Bloomberg), we can delay this a little bit

How do we make element creation and lookup both type safe and convenient.

As it is now you create an element by doing document |> Document.createElement "img" which gives you an element that you'll then have to cast down to an htmlImageElement to be able to use it with functions specific to that type of element. Even though you know damn well that you've just created an htmlImageElement the type system does not. So how do we force-feed it?

  1. Add element-specific create methods, e.g. HtmlImageElement.create. Can we then hard-code this to document.createElement or are elements bound to a specific document instance?

  2. Could we use GADTs somehow? e.g.

type elementType =
| ...
| HtmlImageElement : <put magic here> -> htmlImageElement

let imageElement = document |> Document.createElement HtmlImageElement

The issue is similar with getElementsByTagName and perhaps other functions.

type signatures for pixel count accessors

in the event of the user increasing / decreasing the level of zoom in their browser, Dom.Window.scrollY, Dom.Element.offsetTop, etc. will return return floats rather than integers. Current type signatures make it difficult to account for this in a sane manner.

Chrome 67.0.3396.99 / Firefox 60.0.1 - OS X 10.13.3

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.