org.scalablytyped, coursier and polyglot projects
With a variety of scala compilation targets these days, there is a growing appreciation that CLI tools are an important part of the toolbelt allowing you to integrate scala into polyglot project–projects with more than one programming language.
For example, scalablytyped recently introduced an experimental CLI and a new feature to compile typescript types in the same project. For some projects I have in mind, this is an important feature. Here’s a real server project:
- nodejs app server, say using apollo server for graphql
- typescript, because there are some parts you may have already coded in typescript, such as data access objects and server startup
- scala.js, so you can leverage scala and scala based programming approaches
You need interop. If your data access objects are written in typescript but need to use them in scala.js, you will have to write interfaces. Writing interfaces is error prone and time consuming.
No longer!
Both scalablytype and coursier have CLIs that make interop easier.
Let’s say you have your typescript dir in “./src”–standard for js projects. We need to inject their scala.js counterparts generated from scalablytyped into the our build.sbt. Here are the steps:
- Generate typescript
.d.ts
files. - Generate scalablytyped sources and jars.
- Create scaladocs so we can see what was generated.
- If you have a great IDE, your IDE can pick up the definitions. But you may not have an IDE that does this and you may want to browse the scaladocs. Scaladocs are always useful!
Here’s the recipe assuming a top level project definition in folder my-project
.
Recipe
Step 1: Generate typings
Generate typings: For me, that’s:
cd my-project
npx tsc
# or
npm run script_that_runs_tsc
You may already have typings output automatically in your projec. Make sure tsconfig.json has emitDeclarations set to true and if needed an “emit” directory in case you don’t want to keep them around because you are not publishing your “server” repo.
Step2: Install and use scalablytyped CLI
org.scalablytyped is great because it parses typescript .d.ts
files, compiles and then places the “interface” jars into your local ivy2 repo. It also outputs a source directory called out
. Don’t forget to add out
to your .gitignore. While you could compile those sources yourself in your build.sbt, there is no reason to do that since they are already compiled by scalablytyped.
I’m using an experimental version of scalablytyped so when it is released you could just use coursier to download and install a command line. In the mean time, clone scalablytyped. You will want to note the version of the lib that’s published locally.
git clone https://github.com/ScalablyTyped/Converter.git
cd Converter
sbt publishLocal
cd ..
rm -rf ./Converter
Generate scala.js sources and libs using coursier to run the converter program on your local project. You could also create an install file using coursier:
cd my-project
coursier launch -r ivy2local org.scalablytyped.converter:cli_2.12:1.0.0-beta9+21-d0a27697 -- \
--includeProject true --scalajs 1.0.1 --ignoredLibs=logform
I’ve excluded logform to highlight that sometimes the typescript is strange and we need to exclude some libs. If you run your conversion above and get errors, file a bug report. But you may be able to ignore the lib on the next run using --ignoredLibs
. If its not something you use directly, its safe to do that.
scalablytyped looks in your typescript declarations in the local project as well as package.json so its easy to pull in dependencies that are not directly used. You will generate a large amount of scala code as scalablytyped traces dependencies.
Since scalablytyped generates the list of libs to use and we want to use those in our build.sbt, you need to include the output jars in your build. Scalablytyped uses hashes to avoid recompilation and it does not have an option to output a build.sbt
fragment. Everytime we make a relevant source change, the output library changes name because the hash is part of the name. We just need to output the results of running the converter to a generated.sbt
file that includes the lib under a stable name. The below assumes a shell environment:
VERSION=`cs resolve "org.scalablytyped::my-project_sjs1:latest.release | grep my_project | cut -f 3 -d:`
echo "import sbt._; object Generated { \
val libs = Seq(\"org.scalablytyped\" %% \"my-project_sjs1" % \"$VERSION\")}" > project/generated.sbt
Add the dependency in your build.sbt: librarySettings ++= Generated.libs
Step 3: Generate project docs
To generate docs, we simple go into the generated “project” and run the sbt “doc” command.
Notice the out/*
where *
is the first letter of your ployglut project.
cd my-project
cd out/m/my-project
sbt doc
Then run sbt gen/doc
and xdg-open target/scala-2.13/api/index.html
to open the docs. This process creates docs for your one project.
Step 3 Alternate: Generate all the docs…“all” means all generated docs
We could also generate all the docs using the CLI version of scaladoc and a short script.
coursier install scaladoc
cd my-project
scaladoc -outdir generated_docs -d generated_docs -doc-title "generated local" \
-doc-no-compile `find ./out -name *.scala`
# there's alot of doc...wait a long time...
xdg-open ./generated_docs/index.html
You may also want to add the sclablytyped, scalajs and jsdom libraries for scaladoc resolution:
CP=`coursier fetch --classpath org.scala-js:scalajs-library_2.13:1.0.1 \
org.scala-js:scalajs-dom_sjs1_2.13:1.0.0 com.olvind:scalablytyped-runtime_sjs1_2.13:2.1.0`
scaladoc -classpath $CP ...
Make adjustments as necessary for your project setup. Add generated_docs
your .gitignore. Obviously, all of this goes into a 3 line script so once that’s done, it is automated.
Done
The problem is solved with scalablytyped and coursier, both great tools with cli support so you can adapt them to your workflow.
The scalablytyped team made this happen…literally overnight with an enhancement. You cannot get any more responsive than that.
Thanks!
scala.js front end and Node backend??? that's like the weirdest thing I've heard :) Have you thought of using Caliban as a graphql client/server on a pure scala framework?
ReplyDeleteYou don't always have that choice.
ReplyDelete