Giter VIP home page Giter VIP logo

jsexpr.jl's People

Contributors

andreasnoack avatar juliatagbot avatar kristofferc avatar mikeinnes avatar rdeits avatar sglyon avatar shashi avatar twavv 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

Watchers

 avatar  avatar  avatar  avatar  avatar

jsexpr.jl's Issues

Add RPC syntax

Cross-post: JuliaGizmos/WebIO.jl#262

Per @shashi

An idea for an @jl macro you might enjoy:

@js () -> @jl println("callback was called")

This might also require making all functions async functions. They're supported by all modern desktop browsers, which I think is our target audience (I don't think we're beholden to IE 11). The alternative would be to detect any usage of @jl within a function (hard?) or requiring the user to mark their functions as async. For example, we could require an async decorator (maybe not exactly @async since that has its own semantics in Julia?).

Another question is whether or not we want to await by default; I think this makes sense in the case of JSExpr (otherwise things like @var x = @jl foo() don't work).

onjs(myobs, @js @async function()
    # Option 1: auto-await
    @var foo = @jl calculate_something()
    @async @jl println("foo!")
    # Option 2: manual-await
    @var foo = @await @jl calculate_something()
    @jl println("foo!")
end)

I think the former (auto-await) makes sense here since the code looks synchronous and JSExpr.jl is already sufficiently magical. But I have no strong opinion.

Add Math. to known math function calls

julia> @js (a,b) -> a*sin(b)
JSString("(function (a,b){return (a*sin(b))})")

it could be nice to map the standard mathematical functions like sin, cos, tan, sqrt, ... to their JS counterpart like Math.sin, Math.cos, Math.tan, Math.sqrt; the list of js math functions seems to suggest this is fairly straightforward (names are mostly identical).

Another nice step would be to interpolate symbols like π or .

PS: I'm happy to try to open a PR but I'd need some guidance as to what's the likely good way to go about this

Way to iterate over hash inside @js macro

is there a way to loop over a dictionary inside a @js macro?

example code:

cur_key_map = Dict(
  "left" => [37, 65],
  "up" => [38, 87],
  "right" => [39, 68],
  "down" => [40, 83],
  "clock" => [69],
  "counter" => [81]
)

cur_container = dom"div[tabindex=1]"(
  cur_table,=
  events=Dict(
    "keyup" =>
      @js function (cur_event)
        cur_key_code = cur_event.keyCode;

        # Pseudocode of what the uncommented code below does // what i'm asking about: (top)

        # for (cur_key, cur_values) in cur_key_map
        #   for cur_value in cur_values
        #     if ( cur_key_code == cur_value ) ; $cur_observer[] = cur_key ; end
        #   end
        # end

        # Pseudocode of what the uncommented code below does // what i'm asking about: (bot)

        if ( cur_key_code == 37 ) ; $cur_observer[] = "left" ; end
        if ( cur_key_code == 38 ) ; $cur_observer[] = "up" ; end
        if ( cur_key_code == 39 ) ; $cur_observer[] = "right" ; end
        if ( cur_key_code == 40 ) ; $cur_observer[] = "down" ; end

        if ( cur_key_code == 65 ) ; $cur_observer[] = "left" ; end
        if ( cur_key_code == 87 ) ; $cur_observer[] = "up" ; end
        if ( cur_key_code == 68 ) ; $cur_observer[] = "right" ; end
        if ( cur_key_code == 83 ) ; $cur_observer[] = "down" ; end

        if ( cur_key_code == 81 ) ; $cur_observer[] = "counter" ; end
        if ( cur_key_code == 69 ) ; $cur_observer[] = "clock" ; end
      end
  )
)

Way to do more complicated functions

currently, the following doesn't work:

@js :(
      e ->
        console.log("woof")
        $cur_observer[] = e.keyCode
        console.log("bark")
    ))

can you think of why this would fail to compile?

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

JSExpr should consider serializing `nothing` as `undefined`

Currently, the JSON module serializes both nothing and missing to null. However, if you know your target language is Javascript, a more appropriate target would convert nothing to undefined.

In a similar light, NaN doesn't have to be converted to null, it could just remain NaN. On the other hand, Julia's Inf would need to be converted to Javascript's equivalent, Infinity.

Call @js fails in Julia 0.7

JSExpr.@js () -> begin
                 @var valcopy = JSON.parse(JSON.stringify(val))
             end
ERROR: LoadError: JSExpr: Unsupported `macrocall` expression, #= REPL[25]:2 =# @var valcopy = JSON.parse(JSON.stringify(val))
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] macro expansion at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:44 [inlined]
[3] macro expansion at /Users/vdayanan/.julia/packages/MacroTools/ZXz1/src/macro.jl:18 [inlined]
[4] jsexpr(::Expr) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:157
[5] macro expansion at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:44 [inlined]
[6] macro expansion at /Users/vdayanan/.julia/packages/MacroTools/ZXz1/src/macro.jl:18 [inlined]
[7] jsexpr(::Expr) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:157
[8] _broadcast_getindex at ./broadcast.jl:587 [inlined]
[9] getindex at ./broadcast.jl:520 [inlined]
[10] copy at ./broadcast.jl:771 [inlined]
[11] materialize at ./broadcast.jl:737 [inlined]
[12] jsexpr_joined(::Array{Any,1}, ::String) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:26
[13] block_expr(::Array{Any,1}, ::Any) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:33 (repeats 2 times)
[14] jsexpr(::Expr) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:155
[15] _broadcast_getindex at ./broadcast.jl:587 [inlined]
[16] getindex at ./broadcast.jl:520 [inlined]
[17] copy at ./broadcast.jl:771 [inlined]
[18] materialize at ./broadcast.jl:737 [inlined]
[19] jsexpr_joined(::Array{Any,1}, ::String) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:26
[20] block_expr(::Array{Any,1}, ::Any) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:33 (repeats 2 times)
[21] jsexpr(::Expr) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:155
[22] func_expr(::Any, ::Any) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:87
[23] macro expansion at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:44 [inlined]
[24] macro expansion at /Users/vdayanan/.julia/packages/MacroTools/ZXz1/src/macro.jl:18 [inlined]
[25] jsexpr(::Expr) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:157
[26] jsstring(::Any) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:29
[27] @js(::LineNumberNode, ::Module, ::Any) at /Users/vdayanan/.julia/packages/JSExpr/ySTC/src/JSExpr.jl:9
in expression starting at REPL[25]:1 

Missing 1.0.x ? can't install 1.0.x

According to https://github.com/JuliaRegistries/General/blob/master/J/JSExpr/Versions.toml or https://github.com/JuliaGizmos/JSExpr.jl/blob/master/Project.toml#L4, it looks like this Julia package JSExpr.jl released with 1.0.x (x=0 or 1). However I can't install on my MacBook. 🤔

pkg> add JSExpr@1.0.0
  Resolving package versions...
ERROR: Unsatisfiable requirements detected for package JSExpr [97c1335a]:
 JSExpr [97c1335a] log:
 ├─possible versions are: [0.3.0-0.3.1, 0.4.0, 0.5.0-0.5.2] or uninstalled
 └─restricted to versions 1.0.0 by an explicit requirement — no versions left

Way to insert javascript in Jupyter without output-area div

Lets say you just want to run the following (simple) javascript:

\$(".js-active-piece").removeClass();

What is the most simple way to send that information from the middle of a julia function?


My workaround has been:

cur_html = """
  <script class="cs-step-script">
    var cur_parent = \$(".cs-step-script").parent();
    cur_parent.css("padding", 0);

    \$(".js-active-piece").removeClass();

    var cur_grand_parent = cur_parent.parent();
    if ( !cur_grand_parent.hasClass("cs-visual") ) {
      cur_grand_parent.remove();
    }
  </script>
"""

display(HTML(cur_html))

Add method for `@js` macro with base (à la Blink)

Blink defines a method for @js that looks like

@js window console.log("foo")

We should define a base method for here it in this repo and then let other packages (e.g., Blink and WebIO) extend a method that acts on the specific type of the first arg.

# In JSExpr
macro js(target, expr)
    return :(js($(esc(target), ...))
end

# In Blink
function JSExpr.js(w::Window, code::JSString)
    # Evaluate the JS code in the Blink window
end

WebIO dependency

Nice idea to have this as a separate package (and it e.g. avoids me having to duplicate code here for example), but I wonder if we can avoid special-casing WebIO too much, to make things more general.

In general, packages that want custom semantics should probably provide their own @js and do custom lowering; I don't think this is a huge deal for WebIO as (1) it's important and (2) x[] isn't valid JS anyway, so it's not likely to bite non-WebIO users.

However, maybe it'd make sense for JSExpr to provide custom handlers for different syntax (eg. JSExpr.handle((io, Expr) -> Bool). This might be a nice refactoring anyway, and would allow us to reverse the WebIO/JSExpr dependency.

getting to 1.0 ?

This project seems quite interesting. I assume the lack of commits means that it's more or less stable? I started to add features like this to HypertextLiteral but it might be better to just delegate JS content interpolation to this library. That said, I'd like to tag HypertextLiteral as 1.0 before JuliaCon.

Formalize the adhoc parsing in jsexpr

I'm opening this for discussion, rather than any sort of request.

In reviewing the code of jsexpr, I have found that it is a mixed bag of catches, matches, and string escapes, which in the long run will be brittle and hard to maintain. In particular the processing of jsexpr relies heavily on waterfall knock out (i.e to reach c, cannot have been a or b). This works with small easily orthogonalized cases, but quickly becomes overwhelming with a large number of cases.

I think this could be addressed by replacing the use of the match macro with overloaded functions dispatched on Val()'ed symbols. The parser would extract the head symbol from the expression and then dispatch the found function, splatting the arguments vector of the expression to the function. Of course expressions not handled by jsexpr should raise an error.

The individual functions in would then be overloaded on the type patterns of the arguments vector. The caveat being: catching the method not found error, and adding to it the context that the particular Julia expression cannot be cross-parsed into JavaScript. This can be done with very little cost by calling hasmethod() on the appropriate signature and then raising an error on false.

Overall the refactoring would formalize the following strategy:

  1. Search the Julia AST for expressions that can be handled, and raise an error on everything that cannot be handled (currently mostly in jsexpr)
  2. Map remaining Julia AST into a JavaScript AST (currently sort of what struct F() contains)
  3. Deparse the JavaScript AST into text (currently sort of what flatten/simplify do)

The point is to isolate the boundary between the code you can explicitly control, the JS AST, and the code you cannot, the Julia AST.

The strategy of method overloading the parser can also be used to to address issue #2, because the args of the ref sub-expression of a[]=b is just Symbol, where as the args of the ref sub-expression of a[2]=b are a symbol and another value, in this case an Int64. WebIO would then provide the specialized overload of the ref method to catch the custom WebIO syntax.

Thoughts?

e.g.

# Generic placeholder for the production rules for parsing Julia AST to
# JavaScript JS. Expects a Val'ed head Symbol of the expression, and
# overloads on handled signatures of the args vector of the expression.
# The return should always be a JSAST. Delete in actual implementation.
function crawl(head::Val{juliasymbol}, args...)::JSAST end

# Generic placeholder for overriding function calls. Delete in actual implementation.
function crawl(c::Val{:call}, head::Val{juliasymbol}, args...)::JAST end

# Generic placeholder for overriding marco calls. Delete in actual implementation.
function crawl(m::Val{:macro}, head::Val{juliasymbol}, args...)::JAST end

# Generic placeholder for the production rules for parsing JavaScript AST
# to String's. Expects a Val'ed head Symbol of the JS expression, and
# the a vector JSAST sub-expressions. The return should always be a String.
# Delete in actual implementation.
function deparse(head::Val{jssymbol}, args::Vector{JSAST})::String end

# Note, any string can be turned into a symbol with a call to the Symbol() 
# constructor, so jshead really can store anything. Likewise symbols can be
# returned to strings through a call to the String() constructor.
struct JSAST
    jshead::Symbol
    jsbody::Vector{JSAST}
    function JSAST(
        h::Symbol,
        b::Union{Vector{JSAST}, Vararg{JSAST, N}} = Vector{JSAST}()
    ) where N
        if isa(b, Vector{JSAST})
            return new(h, b)
        else
            return new(h, [b...])
        end
    end
end

# Dumps the raw JS string literal into the argument of the parent 
# expression that the macro was called from. The common use 
# cases are for the macro to be called as the RHS of an assignment,
# or an interpolation into another literal.
macro js(ex)
    return Expr(:call, :JSString, deparse(crawl(ex)))
end

# The expectation is that each dispatched crawl function returns a JS AST
# by calling the crawl-function recursively on deeper expressions.
# Potentially unsafe catch all for terminals.
function crawl(ex::Expr)::JSAST

    # Recurse into expressions
    if hasmethod(crawl, typeof((Val(ex.head), ex.args...)))
        return crawl(Val(ex.head), ex.args...)::JSAST

    # Bail and explain
    else
        error("Expression $ex not supported.")
    end
end

# Protect string terminals
function crawl(ex::String)
    return JSAST(Symbol(string("\"", ex, "\"")))
end

# No coercion on symbols
function crawl(ex::Symbol)
    return JSAST(ex)
end

# All other terminals
function crawl(ex::T)::JSAST

    # Push terminals that have native symbol methods, maybe unsafe
    if hasmethod(Symbol, Tuple{T})
        return JSAST(Symbol(ex))

    # Push terminals, when all else fails try interpolation. This will be a source of bugs
    # because there are no guarantees the string representation will be sensible.
    elseif hasmethod(string, Tuple{T})
        return JSAST(Symbol(string(ex)))

    # Bail and explain
    else
        error(
            "The type $T cannot be a terminal node because " *
            "neither a string method nor a Symbol method were found."
        )
    end
end

# The expectation is that each dispatched deparse function returns a bare JSString
# literal, formed by appropriate ordering and concatenation of the output of 
# recursive calls to the deparse-function.
function deparse(ex::JSAST)::String
    if length(ex.body) == 0
        return string(ex.head)
    else
        return deparse(Val(ex.head), ex.jsbody)::String
    end
end

# Allow override-able parsing of calls
function crawl(h::Val{:call}, m::Symbol, b...)::JSAST

    # Check for overrides
    if hasmethod(crawl, typeof((Val(:call), Val(m), b...)))
        return crawl(Val(:call), Val(m), b...)::JSAST

    # Otherwise pass as a call
    else
        return JSAST(:call, [crawl(m), crawl.(b)...])
    end
end

# Allow override-able parsing of macros
function crawl(h::Val{:macrocall}, m::Symbol, l::LineNumberNode, b...)::JSAST

    # Check for overrides
    if hasmethod(crawl, typeof((Val(m), b...)))
        return crawl(Val(:macrocall), Val(m), b...)::JSAST

    # Otherwise expand and crawl the current module, but let crawl determine
    # what to do with subsequent marcos
    else
        return crawl(macroexpand(@__MODULE__, Expr(:macrocall, m, l, b...); recursive=false))::JSAST
    end
end

# Example crawl and deparse
crawl(h::Val{:ref}, lhs, rhs) = JSAST(:jsref, [crawl(lhs), crawl(rhs)])
deparse(h::Val(:jsref), b::Vector{JSAST}) = string(
    deparse(b[1])::String,
    "[",
    deparse(b[2])::String,
    "]"
)

# Assignmet
crawl(h::Val{:(=)}, lhs, rhs) = JSAST(:jsref, [crawl(lhs), crawl(rhs)])
deparse(h::Val{:(=)}, b::Vector{JSAST}) = string(deparse(lhs), "=", deparse(rhs))

# Objects
function crawl(h::Val{:tuple}, b::Vararg{Expr, N})::JSAST where N
    js = JSAST(:object)
    for ex in b
        if ex.head == :(=)
            push!(js.body, crawl(ex))
        else
            error("Expression $ex is not a named argument in the tuple.")
        end
    end
    return js
end
function deparse(h::Val{:object}, b::Vector{JSAST})::String
    if length(b) > 0
        js = fill(",", 2*length(b) - 1)
        js[1:2:2*length(b) - 1] = deparse.(b)
        js = string("{", js..., "}")
    else
        js = "{}"
    end
    return js
end

# Arrays
function crawl(h::Val{:vec}, b...)::JSAST where N
    return JSAST(:vec, crawl.(b))
end
function deparse(h::Val{:vec}, b::Vector{JSAST})::String
    if length(b) > 0
        js = fill(",", 2*length(b) - 1)
        js[1:2:2*length(b) - 1] = deparse.(b)
        js = string("[", js..., "]")
    else
        js = "[]"
    end
    return js
end

# Escape JS macros, treat as terminals
function crawlmacro(h::Val{Symbol("@js-str")}, b::String)
    return crawl(b)::JSAST
end

# Escape new macros
function crawlmacro(h::Val{Symbol("@new")}, b...)
    return JSAST(:new, crawl.(b))
end
function deparse(h::Val{:new}, b::Vector{JSAST})
    return string("new", " ", deparse(b))
end

# Escape var macros
function crawlmacro(h::Val{Symbol("@var")}, b...)
    return JSAST(:var, crawl.(b))
end
function deparse(h::Val{:var}, b::Vector{JSAST})
    return string("var", " ", deparse(b))
end

In the example the crawl functions are responsible for identifying Julia AST that can be parsed, stripping out the parts that can be ignored, and reorganizing the meaningful parts into a JS AST representation, recursively. The deparse functions are then responsible for taking lists of JS AST and turning them into JS strings, presumably through recursive concatenation.

This means that in the WebIO module we could have, for example

import JSExpr.crawl
import JSExpr.deparse

# Override the intrinsic JSExpr crawl to check for single argument ref's.
crawl(h::Val{:ref}, b) = JSAST(:WebIOref, crawl(b))

# Override the intrinsic JSExpr deparse to check for single argument
# ref's. Call the intrinsic method when there are multiple arguments
function deparse(h::Val{:(=)}, b::Vector{JSAST})::String

    # The left and right hand sides are empty ref's, short circuit to a 
    # chained set-get.
    if b[1].head == :WebIOref && b[2].head == :WebIOref
        return string(
            "WebIO.setval(", 
            deparse(Val(b[1].body[1].head), b[1].body[1].body)::String,
            ", WebIO.getval(",
            deparse(Val(b[2].body[1].head), b[2].body[1].body)::String, 
            "))"
        )

    # Only the left hand side is an empty ref, call set
    elseif b[1].head == :WebIOref
        return string(
            "WebIO.setval(",
            deparse(Val(b[1].body[1].head), b[1].body[1].body)::String,
            " , ",
            deparse(Val(b[2].head), b[2].body)::String,
            ")"
        )

    # Only the right hand side is an empty ref, call get
    elseif b[2].head == :WebIOref
        return string(
            deparse(Val(b[1].head), b[1].body)::String,
            " = WebIO.getval(",
            deparse(Val(b[2].body[1].head), b[2].body[1].body)::String,
            ")"
        )

    # Otherwise call the intrinsic method
    else
        return JSExpr.deparse(h, b)::String
    end
end

*because we expect crawl and deparse to be widely overloaded, we need a certain amount of paranoia about the return types.

This should also facilitate testing and development, as you could create the module with any empty dictionary, and then push and pop prototype functions to test the output in the REPL.

I think this also falls under the general rule of "don't build parsers with regular expressions".

JSString should be printable to text/javascript

Hello. So, the HTML object permits one to create objects that are HTML fragments, suitable to be displayed as "text/html".

julia> obj = HTML("<span>Hi</span>")
HTML{String}("<span>Hi</span>")
julia> display("text/html", obj)
<span>Hi</span>

The same should be true for JSString.

julia> expr = @js for i in 1:10
           console.log(i)
       end
JSString("for(var i = 1; i <= 10; i = i + 1){console.log(i)}")
julia> display("text/javascript", expr)
ERROR: MethodError: no method matching show(::Base.TTY, ::MIME{Symbol("text/javascript")}, ::JSString)

This issue can be addressed with the following:

julia> Base.show(io::IO, ::MIME"text/javascript", js::JSString) = 
           print(io, js)

julia> display("text/javascript", expr)
for(var i = 1; i <= 10; i = i + 1){console.log(i)}

Thanks.

Issues with macro expansion in `jsexpr`

This is the root cause of JuliaGizmos/Blink.jl#134

For a simple example, try:

julia> module Foo
         import JSExpr: @new, jsexpr
         f() = jsexpr(:(@new Int(1)))
       end
WARNING: replacing module Foo
Foo

julia> Foo.f()
ERROR: JSExpr: Unsupported `error` expression, $(Expr(:error, UndefVarError(Symbol("@new"))))
Stacktrace:
 [1] macro expansion at /home/rdeits/Downloads/blink-bug/packages/v0.6/JSExpr/src/JSExpr.jl:191 [inlined]
 [2] macro expansion at /home/rdeits/Downloads/blink-bug/packages/v0.6/MacroTools/src/macro.jl:18 [inlined]
 [3] jsexpr(::Expr) at /home/rdeits/Downloads/blink-bug/packages/v0.6/JSExpr/src/JSExpr.jl:159
 [4] macro expansion at /home/rdeits/Downloads/blink-bug/packages/v0.6/JSExpr/src/JSExpr.jl:179 [inlined]
 [5] macro expansion at /home/rdeits/Downloads/blink-bug/packages/v0.6/MacroTools/src/macro.jl:18 [inlined]
 [6] jsexpr(::Expr) at /home/rdeits/Downloads/blink-bug/packages/v0.6/JSExpr/src/JSExpr.jl:159
 [7] f() at ./REPL[12]:3

The issue is that

(@m_ xs__) => jsexpr(macroexpand(current_module(), x))
tries to expand the @new macro in current_module(), which in this case doesn't know what that macro is.

I think it would be safer to do macroexpand(JSExpr, x) instead, as we know that's where the correct version of @new, @var, etc. live.

Compat for JSON

Currently, the 1.0+ releases are yanked in the registry. The pre 1.0 version (0.5.1) claims to only be compatible with JSON 0.18 which is a quite old JSON release. It would be great if a new (0.5.2) release could be made that has compat with the newer JSON (up to 0.21).

Interpolating into `@js` breaks for nested field access

This is fine:

julia> @macroexpand @js $(x)
:((JSExpr.JSString)((JSExpr.string)((JSExpr.jsexpr)(x))))

and this is also fine:

julia> @macroexpand @js $(x.y)
:((JSExpr.JSString)((JSExpr.string)((JSExpr.jsexpr)(x.y))))

but with a nested field access, we get something pretty weird:

julia> @macroexpand @js $(x.y.z)
:((JSExpr.JSString)((JSExpr.string)((JSExpr.join)((JSExpr.map)(JSExpr.jsexpr, x), ","))))

which doesn't actually work.

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.