Published

Scala.js opens a big world of frontend development to Scala fans. Most of the time Scala.js project ends up being an independent browser or Node.js application. But there are cases, where you would want to make a library for general frontend developers.

There’re some interesting gotchas in writing Scala.js library such way, that it will be natural to use for an average JS developer. In this article we will develop a simple Scala.js library (code) to work with Github API and will focus on the idiomaticity of it’s JS API.

But first, I’m sure you want to ask

Why would I do that?

Sad, you will hardly have a chance to write it from scratch with Scala.js, but at least it makes sense to write a communication / interpretation library for those guys.
It will simplify interaction between you and frontenders in two ways:

You can hide some tricky client-side logic there, and expose much simpler API.

Your library can work on model classes, defined in backend project (see Cross-Building). You get typesafe isomorphic code almost for free and can forget about client-server protocol synchronization problems.

Reading model properties

Seamless types

There’re no problem with native types like
String ,
Boolean and
Int . They can be exported as is:

1

2

3

4

5

sealedtraitRepo{

@JSExport

defname:String

// ...

}

A case class field can be exported with
@(JSExport@field) annotation. An example for
forks property, that’s not a member of
Repo trait:

1

2

3

4

5

caseclassOrigin(name:String,

description:String,

stargazersCount:Int,

homepage:Option[String],

@(JSExport@field)forks:Int)extendsRepo

Option

But as you already can expect, there’s a problem withhomepage:Option[String] . Well, we can export it, but this would be useless – to get the actual string value JS developer would have to call something on an option, and nothing is exported.

On the other side, we’d like to keep
Option in place, so that our Scala code, that manipulates value classes, remains powerful and simple.
js.UndefOr[T] API is way less expressive.

I choose the second one, because it can be abstracted and used throughout the whole codebase. Here’s how it can be done. Let’s declare a mixin that exports a
type property:

1

2

3

4

traitTyped{self=>

@JSExport("type")

def`type`:String=self.getClass.getSimpleName

}

We have to use a different name for scala definition, because it’s a reserved word.
That’s it! We can now mix it in:

1

2

3

sealedtraitRepoextendsTyped{

// ...

}

… and use it:

1

2

// JS

fork.type// "Fork"

To make this a little safer, we can store type names constants, that can be compared with instance
type property. This can be done typesafe:

1

2

3

4

classTypeNameConstant[T:ClassTag]{

@JSExport("type")

def`type`:String=classTag[T].runtimeClass.getSimpleName

}

Having this helper class we can define these constants in our
Github global for example:

1

2

3

4

5

6

7

@JSExportAll

objectGithub{

//...

valFork=newTypeNameConstant[model.Fork]

valOrigin=newTypeNameConstant[model.Origin]

}

Now we can avoid strings in Javascript! An example:

1

2

3

4

// JS

functionisFork(repo){

returnrepo.type==Github.Fork.type

}

That’s how we dealt with sum types.

What if I can’t change object, that I want to export?

This is a case if you want to (maybe, partially) export your cross-built model classes or other imported library objects. The solution is the same to
Option and
List with the only difference: you have to implement JS-friendly replacement classes and conversion yourself.

An important rule here is to use JS replacements only for export (
Scala=>JS) and instance creation (
JS=>Scala ). All business logic must be implemented with pure Scala classes.

Let’s say you have a
Commit class, that you can’t change:

1

caseclassCommit(hash:String)

Here what you can do to export it:

1

2

3

4

5

6

objectCommitJS{

deffromCommit(c:Commit):CommitJS=CommitJS(c.hash)

}

caseclassCommitJS(@(JSExport@field)hash:String){

deftoCommit:Commit=Commit(hash)

}

Then, for example, a
Branch class, that you own, would look like this:

1

2

3

4

caseclassBranch(initial:Commit){

@JSExport("initial")

definitialJS:CommitJS=CommitJS.fromCommit(initial)

}

Since in JS environment commits are represented with
CommitJS objects, a factory method for
Branch would be:

1

2

@JSExport

defcreateBranch(initial:CommitJS)=Branch(initial.toCommit)

Of-course, this workaround is not a beautiful thing, but at least it’s type checked. That’s why I think it’s preferable to view your library not only as a value-classes proxy, but as a facade that hides redundant details and simplifies the API. That way you won’t even need to export the underlying model.

That’s all for exporting model. Let’s move on to the more interesting part – loading the content from Github API.

AJAX

Implementation

For the brevity purposes we will use scalajs-domAjax extension as a “network” layer. Let’s for some time forget about how we’re going to export things, let’s just implement the API.

For the simplicity, we’ll put everything AJAX-related into
API object. It will have two public methods: for loading user and loading repos.

We will also implement a DTO layer, to decouple API from the model. For type-safe error handling we’ll use
Xor type from Cats library. The result type of the method call will be
Future[StringXorDTO], where
DTO is the type of requested data and
String will represent error.

I’ve mentioned everything for this listing to be more understandable, here it is:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

objectAPI{

caseclassUserDTO(name:String,avatar_url:String)

caseclassRepoDTO(name:String,

description:String,

stargazers_count:Int,

homepage:Option[String],

forks:Int,

fork:Boolean)

defuser(login:String)

(implicitec:ExecutionContext):Future[StringXorUserDTO]=

load(login,s"$BASE_URL/users/$login",jsonToUserDTO)

defrepos(login:String)

(implicitec:ExecutionContext):Future[StringXorList[RepoDTO]]=

load(login,s"$BASE_URL/users/$login/repos",arrayToRepos)

privatedefload[T](login:String,

url:String,

parser:js.Any=>Option[T])

(implicitec:ExecutionContext):Future[StringXorT]=

if(login.isEmpty)

Future.successful("Error: login can't be empty".left)

else

Ajax.get(url).map(xhr=>

if(xhr.status==200){

parser(js.JSON.parse(xhr.responseText))

.map(_.right)

.getOrElse("Request failed: can't deserialize result".left)

}else{

s"Request failed with response code ${xhr.status}".left

}

)

privatevalBASE_URL:String="https://api.github.com"

privatedefjsonToUserDTO(json:js.Any):Option[UserDTO]=//...

privatedefarrayToRepos(json:js.Any):Option[List[RepoDTO]]=//...

}

Deserialization code is hidden, it’s not interesting. The
load method returns string error, if response code is not 200, otherwise it converts the response data to JSON and then to DTO’s.

Now we can convert our API results into model classes.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

importscala.scalajs.concurrent.JSExecutionContext.Implicits.queue

objectGithub{

// ...

defloadUser(login:String):Future[StringXorUser]={

for{

userDTO<-XorT(API.user(login))

repoDTO<-XorT(API.repos(login))

}yielduserFromDTO(userDTO,repoDTO)

}.value

privatedefuserFromDTO(dto:API.UserDTO,

repos:List[API.RepoDTO]):User=//..

}

Here we use a monad transformer to combine these “disjunctioned” futures, and then convert DTO’s into model classes.

Well, that is quite idiomatic functional Scala, lots of pleasure. Now let’s think about how we will export
loadUser method to library users.

Share the Future

To follow the article goals we need to answer the question: what is the idiomatic way to handle asynchronous call in Javascript? I already hear experienced frontenders laughing, because there are no such thing. Callbacks, event emitters, promises, fibers, generators, async/await — all of them are somehow valid approaches. So what should we choose?

I think, the closest thing to Scala Future in Javascript are Promises. Promises are very popular and are already native in most modern browsers. So we’ll stick with them.

First, we must let our Scala code know about those promises. Until Scalajs 0.6.7 we would have to use Promise typed facade from scalajs-dom. But with Scalajs 0.6.7 things became much easier, we will just use the “standard” Promises.

All we have to do now is to convert a
Future into
Promise. Again, since version 0.6.7 this is not more a problem — there’s a
toJSPromise converter in
JSConverters . We will just need to help it with the left side of our
Xor — convert it to a failed Future to get a rejected Promise:

1

2

3

4

5

6

7

8

9

10

objectpromise{

implicitclassJSFutureOps[R:ClassTag](f:Future[Xor[String,R]]){

deftoPromise(implicitectx:ExecutionContext):Promise[R]=

f.flatMap[R]{

caseXor.Right(res)=>Future.successful(res)

caseXor.Left(str)=>Future.failed(newJavaScriptException(str))

}.toJSPromise

}

}

So let’s share the promise with our JS friends! As usual, we put it to Github object, near the original method:

1

2

3

4

5

defloadUser(login:String):Future[StringXorUser]=//...

@JSExport("loadUser")

defloadUserJS(login:String):Promise[User]=

loadUser(login).toPromise

Here in case of failed future we’re rejecting promise with the exception message. That’s all, we can test the whole API now:

1

2

3

4

5

6

7

8

9

// JS

Github.loadUser("vpavkin")

.then(function(result){

console.log("Name: ",result.name);

},function(error){

console.log("Error occured:",error)

});

// Name: Vladimir Pavkin

Well, we did it! We can use Futures and everything else we are got used to — and still export idiomatic JS API.

For more API usage examples see full demo.js. To play more with the project, just fetch the repo, then build and run it.

Conclusion

Putting it all together, here are some general advice on writing a Javascript library with Scala.js: