Giter Site home page Giter Site logo

slinky's Introduction

Write Scala.js React apps just like you would in ES6

Get started at slinky.dev

What is Slinky?

Slinky is a framework for writing React apps in Scala with an experience just like using ES6.

Slinky lets you:

  • Write React components in Scala with an API that mirrors vanilla React
  • Implement interfaces to other React libraries with automatic conversions between Scala and JS types
  • Write apps for React Native, React 360, and Electron, including the ability to share code with web apps
  • Develop apps iteratively with included hot-reloading support

Contributing

Slinky is split up into several submodules:

  • core contains the React.js facades and APIs for creating components and interfaces to external components
  • web contains bindings to React DOM and definitions for the HTML/SVG tag API
  • reactrouter contains bindings to React Router
  • history contains a facade for the HTML5 history API
  • native contains bindings to React Native and external component definitions for native UI elements
  • vr contains bindings to React 360 and external component definitions for VR UI elements
  • readWrite contains the Reader and Writer typeclasses used to persist state for hot reloading
  • hot contains the entrypoint for enabling hot-reloading
  • scalajsReactInterop implements automatic conversions between Slinky and Scala.js React types
  • testRenderer contains bindings to react-test-renderer for unit testing components
  • coreIntellijSupport contains IntelliJ-specific support for the @react macro annotation
  • tests contains the unit tests for the above modules (except native and vr which have local tests)
  • docs and docsMacros contains the documentation site, which is a Slinky app itself

To run the main unit tests, first install the dependencies by running npm install inside the tests folder, then from the base folder run sbt tests/test. Similarly for React Native tests, run npm install inside the native folder, then from the base folder run sbt native/test.

Note to IntelliJ IDEA users. When you try to import Slinky SBT definition in IDEA and encounter an exception like java.nio.file.NoSuchFileException: /Users/someuser/.slinkyPluginIC/sdk/192.6817.14/plugins, you should try to download required IntelliJ files for plugin subproject manually before importing:

sbt coreIntellijSupport/updateIntellij

And then import the project again.

slinky's People

Contributors

alexitc avatar armanbilge avatar asamsig avatar atry avatar dispalt avatar gehnaphore avatar jedahu avatar joan38 avatar kladdad avatar mattkohl avatar mcallisto avatar mn98 avatar nadenf avatar nartamonov avatar ngbinh avatar oleg-py avatar perebarcelo avatar pme123 avatar povder avatar ramnivas avatar scala-steward avatar seamusv avatar shadaj avatar sideeffffect avatar steinybot avatar strobe avatar taqas avatar virusdave avatar zakpatterson avatar zoosky 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

slinky's Issues

Props with Type Parameters

How can I use Props with type parameters?

For example

@react class Example extends Component {

  case class Props[T](data: Seq[T])

  type State = Unit

  def initialState = ()

  def render() = ???
}

will just result in the following errors:

[error] <macro>:3: class Props takes type parameters
[error]     type Props = Example.Props
[error]                          ^
[error] <macro>:13: could not find implicit value for parameter propsReader: me.shadaj.slinky.core.Reader[example.Example.Props]
[error]     @__SJSDefined class Def(jsProps: scala.scalajs.js.Object) extends Definition(jsProps) {
[error]                                                                       ^
[error] <macro>:17: overloaded method value apply with alternatives:
[error]   [T](data: Seq[T])me.shadaj.slinky.core.KeyAndRefAddingStage[example.Example.Def] <and>
[error]   (p: example.Example.Props)(implicit propsWriter: me.shadaj.slinky.core.Writer[example.Example.Props], implicit constructorTag: scala.scalajs.js.ConstructorTag[example.Example.Def])me.shadaj.slinky.core.KeyAndRefAddingStage[example.Example.Def]
[error]  cannot be applied to (example.Example.Props[T])
[error]     def apply[T](data: Seq[T]): me.shadaj.slinky.core.KeyAndRefAddingStage[Def] = this.apply(Props.apply[T](data))
[error]                                                                                        ^
[error] three errors found

Literal identifiers cause macro errors when referenced inside @react components

@js.native
trait Color extends js.Object {
  val `50`: String                 = js.native
  val `100`: String                = js.native
  val `200`: String                = js.native
  val `300`: String                = js.native
  val `400`: String                = js.native
...
}

@react class App extends StatelessComponent {
  type Props = Unit

  private val css = AppCSS

  println(MaterialUi.colors.blue.`300`)
...
}
[info] Compiling 1 Scala source to /home/fhargreaves/repos/slinky-wrappers/demo/target/scala-2.12/classes ...
[error] <macro>:8:35: ')' expected but double literal found.
[error]     println(MaterialUi.colors.blue.300)
[error]                                   ^
[error] <macro>:9:92: ';' expected but ')' found.
[error]     private val themeSettings = literal(palette = literal(primary = MaterialUi.colors.blue))
[error]                                                                                            ^
[error] two errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 1 s, completed Mar 4, 2018 8:31:33 PM

Tighten acceptable types passed to ExternalComponentWithTagMods

Currently, ExternalComponentWithTagMods's type parameter isn't constrained.

abstract class ExternalComponentWithTagMods[E] {

As a result, anything can be passed. It would be helpful if it were to only accept types that are tags. I don't think this will work until the generated tags extend a marker trait (such as object div extends DOMTag), which can then be used as ExternalComponentWithTagMods[E <: DOMTag].

Make event handler parameter type more specific

For example, currently, onMouseDown is defined as:

object onMouseDown  {
object tag

def :=(v: org.scalajs.dom.Event => Unit) = new AttrPair[_onMouseDown_attr.type]("onMouseDown", v)
def :=(v: () => Unit) = new AttrPair[_onMouseDown_attr.type]("onMouseDown", v)
}

It would be much better (and avoid a asInstanceOf on the usage type) if it could be defined as:

object onMouseDown  {
object tag

def :=(v: org.scalajs.dom.MouseEvent => Unit) = new AttrPair[_onMouseDown_attr.type]("onMouseDown", v)
def :=(v: () => Unit) = new AttrPair[_onMouseDown_attr.type]("onMouseDown", v)      
}

Basically, the change is Event to MouseEvent

Forgetting to provide the type parameter to ExternalComponentWithAttributes result in a undecipherable compiler error

Compiling the following code (which forgets to specify the type parameter to the ExternalComponentWithAttributes class) results in the error (pasted at the end).

@react object Scatter extends ExternalComponentWithAttributes/*[canvas.tag.type]*/ {
  case class Props(data: js.Object, options: js.Object, width: Int, height: Int)

  override val component = global.selectDynamic(getClass.getSimpleName).asInstanceOf[js.Object]
}
exception during macro expansion: 
[error] scala.meta.internal.inline.AbortException: @react must annotate a class that extends Component Defn.Object(Nil, Term.Name("Scatter"), Template(Nil, Seq(Term.Apply(Ctor.Ref.Name("ExternalComponentWithAttributes"), Nil)), Term.Param(Nil, Name.Anonymous(), None, None), Some(Seq(Defn.Class(Seq(Mod.Case()), Type.Name("Props"), Nil, Ctor.Primary(Nil, Ctor.Ref.Name("this"), Seq(Seq(Term.Param(Nil, Term.Name("data"), Some(Type.Select(Term.Name("js"), Type.Name("Object"))), None), Term.Param(Nil, Term.Name("options"), Some(Type.Select(Term.Name("js"), Type.Name("Object"))), None), Term.Param(Nil, Term.Name("width"), Some(Type.Name("Int")), None), Term.Param(Nil, Term.Name("height"), Some(Type.Name("Int")), None)))), Template(Nil, Nil, Term.Param(Nil, Name.Anonymous(), None, None), None)), Defn.Val(Seq(Mod.Override()), Seq(Pat.Var.Term(Term.Name("component"))), None, Term.ApplyType(Term.Select(Term.Apply(Term.Select(Term.Name("global"), Term.Name("selectDynamic")), Seq(Term.Select(Term.Name("getClass"), Term.Name("getSimpleName")))), Term.Name("asInstanceOf")), Seq(Type.Select(Term.Name("js"), Type.Name("Object")))))))))
[error] 	at scala.meta.inline.Api.abort(Api.scala:15)

Support union type props

I have a situation where the property type needs to be String | ReactElement. Currently, I get the following message:

magnolia: could not find any direct subtypes of trait |

Bug - 0.3.0 - Option of component as child of a tag not compiling.

e.g. div(Some(MyComponent())) doesn't compile but was working in 0.2.0:

[error] :13:11: type mismatch;
[error] found : Some[slinky.core.KeyAndRefAddingStage[MyComponent.Def]]
[error] (which expands to) Some[slinky.core.KeyAndRefAddingStage[MyComponent]]
[error] required: slinky.core.facade.ReactElement
[error] Some(MyComponent())
[error] ^
[error] one error found

Simplify the typing of ExternalComponentWithTagMods

Currently, I have to do something like:

object Button extends ExternalComponentWithTagMods {
  override type Element = button.tag.type
  ...
}

It would be nice if the Element type could be passed as a type parameter as in:

object Button extends ExternalComponentWithTagMods[button.tag.type] {
  ...
}

js.undefined field should be not be mapped to a property

When mapping a case class with fields defaulting to js.undefined, leaving those fields to the default shouldn't lead to a property in the written object. This is especially important for external components, where those components often test for existence of a field (and not if the field is defined) before using the default value.

Here is a failing test case (which could be added to ReaderWriterTest)

  test("Read/write - case class with default js.undefined") {
    case class CaseClass(int: Int, boolean: js.UndefOr[Boolean] = js.undefined)
    readWrittenSame(CaseClass(1))
    readWrittenSame(CaseClass(1, true))

    // Assert that any undefined property in the input does not map to the written object's property;
    // not even as js.undefined
    val written = implicitly[Writer[CaseClass]].write(CaseClass(1))
    assert(written.asInstanceOf[js.Object].hasOwnProperty("int"))
    assert(!written.asInstanceOf[js.Object].hasOwnProperty("boolean"))
  }

Provide support for specific events

Consider the following code, where I am trying to get the value of a text field in response to a change event.

    def setCity(event: Event): Unit = {
      val value = event.target.asInstanceOf[HTMLInputElement].value
      setState(state.copy(city = value))
    }

It would be nice to be able to specify a more specific event type instead of Event in onChange to avoid the asInstanceOf[HTMLInputElement] part.

[Q] @react Component syntax

This bugs me even though just a small thing. Is it possible to not have to repeat ourselves? I'm talking about going with either inheritance or annotation and not having to do both? What does the @react annotation give us just out of curiosity?

Unused import warnings

I've recently upgraded to 0.3.0 and am receiving a lot of unused import warnings for Component and StatelessComponent. It looks like when the macro expands no reference remains of Component/StatelessComponent. Any chance we can correct this?

Minus the warnings, everything works fine with 0.3.0 in my small app!

Allow relaxed Element type for ExternalComponents

Consider https://github.com/kradio3/react-mdc-web/blob/master/src/List/ListDivider.js

The top-level element in this component is either li or hr depending on the props passed. Currently, there is no way to specify the appropriate Element type in such situation. It would help if Element type can be specified such that one can pass TagMods allowed for either type. If that is not easy, something like *.tag.type to accept any TagMods may be fine as well.

Provide a better error message when @react component is defined without props

If I define the following component:

@react class Example extends Component {
}

I get the following error:

exception during macro expansion: 
[error] java.util.NoSuchElementException: head of empty list
[error] 	at scala.collection.immutable.Nil$.head(List.scala:428)
[error] 	at scala.collection.immutable.Nil$.head(List.scala:425)
[error] 	at me.shadaj.slinky.core.annotations.react$inline$.createBody$1(react.scala:11)
[error] 	at me.shadaj.slinky.core.annotations.react$inline$.apply(react.scala:106)
[error] @react class Example extends Component {
[error]  ^

A clearer message pointing to missing props will be nice.

Simplify support for stateless components

Currently requires adding a line:

type State = Unit

and override the initialState as follows:

override def initialState: State = ()

It will be nice to be able to avoid this.

Support new features from React 16

  • Returning arrays of elements from render
  • Returning strings from render
  • Portal component for rendering elements in different part of the tree
  • Fragment component for returning a fragment element
  • componentDidCatch for error boundaries

Wildcard importing (and using fields from it) breaks the compilation

Here is the minimal code that illustrates the issue:

@react class FooComponent extends Component {
  type Props = Unit
  type State = Unit

  override def initialState = ()

  import FooComponent._

  def render(): ReactElement = {
    div(className := something)
  }
}

object FooComponent {
  val something = ""
}

This results in the following errors:

[error] <macro>:16: reference to something is ambiguous;
[error] it is both defined in object FooComponent and imported subsequently by 
[error] import FooComponent._
[error]       def render(): ReactElement = div(className := something)
[error]                                                     ^
[error] one error found

Hot-loading sealed trait - MatchError

When hot-loading code, if I try to match on an old instance of a sealed trait, I get a MatchError.

ReactState

object ReactState extends Component {
  type Props = Unit
  sealed trait State
  case object A extends State
  case object B extends State

  class Def(jsProps: js.Object) extends Definition(jsProps) {
    def initialState = A
    
    def render() = {
      state match {
        case A => button("B!!", onClick := {(e: dom.Event) => setState(B)})
        case B => button("A!!", onClick := {(e: dom.Event) => setState(A)}) 
      }
    }
  }
}
[HMR] Waiting for update signal from WDS...
game-bundle.js:14403 creating proxy
game-bundle.js:735 [HMR] Accepted.
game-bundle.js:62597 [WDS] Hot Module Replacement enabled.
2game-bundle.js:62600 [WDS] App updated. Recompiling...
game-bundle.js:62726 [WDS] App hot update...
game-bundle.js:63154 [HMR] Checking for updates on the server...
0.e46a8a1eea7683d4b3a3.hot-update.js:sourcemap:13665 updating proxy
0.e46a8a1eea7683d4b3a3.hot-update.js:13663 scala.MatchError: A (of class com.jc776.motorcycle.dev.ReactState$A$)
...
game-bundle.js:63156 [HMR] Cannot apply update. Need to do a full reload!
[HMR] Error
    at $c_s_MatchError.webpackHotUpdate../client/target/scala-2.12/motorcycle-client-fastopt.js.$c_jl_Throwable.fillInStackTrace__jl_Throwable (http://localhost:8080/0.e46a8a1eea7683d4b3a3.hot-update.js:6415:14)
...
Navigated to http://localhost:8080/
game-bundle.js:sourcemap:63154 [HMR] Waiting for update signal from WDS...
game-bundle.js:14403 creating proxy
game-bundle.js:735 [HMR] Accepted.
game-bundle.js:62597 [WDS] Hot Module Replacement enabled.

HMR is otherwise set up correctly - modifying a component with simpler state still works.

This isn't directly a slinky problem (example) but it's a pretty big limitation on what state you can use with slinky-hot. Is there a workaround to convert old instances to new ones that could go either in my code or the library?

Forgetting to add the State type leads to an undecipherable compiler error

Compiling the following code (observe the commented State-related lines) results in a compiler error without a clear indication of the underlying cause:

[error] java.util.NoSuchElementException: head of empty list
[error] 	at scala.collection.immutable.Nil$.head(List.scala:428)
package example

import me.shadaj.slinky.core.Component
import me.shadaj.slinky.core.annotations.react
import me.shadaj.slinky.core.facade.ReactElement
import me.shadaj.slinky.web.svg._

@react class MyComponent extends Component {
  case class Props(something: Int)
  //type State = Unit
  //override def initialState = ()

  override def render(): ReactElement = {
    div()
  }
}

It would be nice if the macro would issue an error indicating the missing state.

An idea: Perhaps assume that if the State type is missing, it is a stateless component (auto-generating the two lines). This will better match how it is done in ES6.

Allow easier way to specify optional attributes

Currently, optional attributes can be defined using the Option type with default set to None, which is nice as that is more Scala-like. But that ends up requiring a bit more code on the use site. Like so:

object Button extends Component {
    case class Props(enabled: Option[Boolean] = None, raised: Option[Boolean] = None, ...)
}

which can be used as

Button(Button.Props(enabled = Some(true), raised = Some(true), ...)

If I could define optional types in terms of js.UndeOr, then I could avoid the extra code on usage. So it will be nice to able to do something similar to js.UnderOr (perhaps through a union type similar to js.UndefOr).

Allow boolean valued attributes to skip the value

For example, it should be possible to specify:

button(disabled)

instead of:

button(disabled := true)

I think this should be possible through an implicit conversion (from Attr with a boolean value to TagMod). If this can be done, the code will look even closer to its plain React equivalent.

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.