Replies: 1 comment 3 replies
-
Thank you so much for starting this discussion! 💯 I completely concur. It would be excellent if scala-cli would support this feature. I recently attempted to motivate this exact idea in #297 (comment). Link-time "import mapping" would enable scala-cli to create standalone, ready-to-deploy applications with JS dependencies out-of-the-box, without having to rely on additional tooling and bundlers, like npm and vite. The key idea is to take advantage of the fact that:
All we have to do to make this work is:
This mapping can and should happen at user application linking time, because they may have their own preferred CDNs (or even self-host). Published libraries can and should continue to reference the NPM packages for their declared ExampleConsider this popular web component library for Laminar:
That library depends on the @ui5/webcomponents npm package. So, out-of-the-box, it is impossible to use that library with sbt or scala-cli without involving a JavaScript bundler. Indeed, the demo uses vite. However, with import mapping, we can rewrite that library's The configuration using sbt-scalajs-importmap looks like this: scalaJSImportMap := { (rawImport: String) =>
"https://unpkg.com/" + rawImport + "?module"
} Concretely, it makes this change in the generated JS: - import * as $i_$0040ui5$002fwebcomponents$002fdist$002fInput$002ejs from "@ui5/webcomponents/dist/Input.js";
+ import * as $i_https$003a$002f$002funpkg$002ecom$002f$0040ui5$002fwebcomponents$002fdist$002fInput$002ejs$003fmodule from "https://unpkg.com/@ui5/webcomponents/dist/Input.js?module"; Full project configuration below.
addSbtPlugin("com.armanbilge" % "sbt-scalajs-importmap" % "0.1.0")
scalaVersion := "3.2.2"
enablePlugins(ScalaJSPlugin, ScalaJSImportMapPlugin)
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule))
scalaJSUseMainModuleInitializer := true
libraryDependencies ++= List(
"be.doeraene" %%% "web-components-ui5" % "1.9.2",
"com.raquo" %%% "laminar" % "15.0.0"
)
scalaJSImportMap := { (rawImport: String) =>
"https://unpkg.com/" + rawImport + "?module"
}
import be.doeraene.webcomponents.ui5.*
import be.doeraene.webcomponents.ui5.configkeys.*
import com.raquo.laminar.api.L.{*, given}
import com.raquo.airstream.core.*
import org.scalajs.dom
@main def main = render(
dom.document.querySelector("#app"),
div(
Input(
_.required := true,
_.valueState := ValueState.Information,
_.placeholder := "Enter your name",
onChange.mapToValue --> Observer(println)
)
)
)
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="app"></div>
<script type="module" src="target/scala-3.2.2/sandbox-fastopt/main.js"></script>
</body>
</html> That works well enough for sbt. But as Simon points out this feature could be even more valuable in scala-cli for fast prototyping and deploying. |
Beta Was this translation helpful? Give feedback.
-
@armanbilge Please chime in, should this already be better explained elsewhere. I'm opening a discussion here, because I've been thinking on and off for a couple of days about this, and I think this could hold transformational potential for scalaJS, particularly if done in scala-cli...
Here is the best description I'm aware of;
scala-js/scala-js#4837
In that issue, @sjrd suggests scalaJS core might not be the right place. I will claim, scala-cli is.
A first cut on a solution, produced on an astonishingly short timescale;
https://github.com/armanbilge/scalajs-importmap
What I'd propose to avoid;
#297
Why would this is worthy of its own discussion / important / worth spamming people over?
An example; the other day, I wanted to cache an http request persistently through a page reload. Because whatever.
I had a good key for a simple "GET", so I wanted I a simple key -> val store. I'm not aware of one in scala JS. So hit up google, discovered indexedDB, and after experimenting scalaJSdom, struggling a little, experimenting with scalablytyped on a couple of libraries, research, try fail, eventually hit on;
https://www.npmjs.com/package/idb-keyval
So cool, works.
Now, I have a promise runtime, an
IO
runtime to make the request I want to cache (love smithy) and laminar (love that too) EventStream to manage the UI. 3 runtimes! As an aside, It is a marvel of engineering and testament to the robustness of scala JS, that actually works (!).Not so ideal about that solution
It took, quite some time, and quite a bit of headspace to get it to all work. Also, now I have scalably typed in the build. Scalably typed is awesome, but does add some complexity to the build, and certainly after
clean
, some non-trivial amount of time to the application feedback loop.I was basically solving a "POC" problem, with "project" infrastructure. I see two issues;
Maybe it was an idiosyncratic need, but what I really wanted to do, was publish a tiny helper library, that married this idb-keyval facade to a thin Laminar skin in a clean room.
Why didn't I
Because I have only a fuzzy idea of how to go about doing it. I think I'd need a new sbt project, the vite config, the scalable typed plugin plus its library incantations, bundler setup, dev server setup etc - the toolchain is actually... kind of long. I concluded, that it was an investment that would cost me more time, than was justifiable.
How would this help?
If in fact it was possible to just skip the entire bundling thing, which I believe this would allow, then scala-cli already has everything else it needs to experiment with, test and publish that tiny helper library. I could have solved my POC problem in the small, and then sucked it into my "actual project". Plus anyone else who hit the same problem, might then have stumbled across an idiosyncratic interface, rather than re-inventing it themselves in isolation.
What I'm going to claim here, (on a small sample size of me) is that this is actually quite a general pattern in scala JS.
Interfacing into existing javascript / typescript libraries is a killer feature - but right now, I suspect lots of collective headspace is lost, to "skinning" or squeezing those libraries into idiomatic scala JS inside application code. Unless I'm missing something, there are remarkably few "higher level" libraries built on top of, say Laminar - I'm not aware of a
CachedEventStream
for example (hoping I don't come away red faced here).I make three claims;
I could well have missed something in this thought experiment, but I think it has exciting potential...
Beta Was this translation helpful? Give feedback.
All reactions