Scala.js Documentation

repository·main·Indexed 42 days ago

https://github.com/scala-js/scala-js-website

Documentation and release notes for Scala.js, including guides on JavaScript interoperability, sbt plugin configuration (fastOptJS, fullOptJS), and CLI tools like scalajsc and scalajsld. Covers version-specific updates from 0.5.0 to 0.5.4, including js.UndefOr, TypedArrays, and binary compatibility guidelines.

Tokens
112.8K
Snippets
362
Records
592
Agent score
47%

What's inside Scala.js

  1. Export Scala.js APIs to JavaScript

    main

    By default, Scala.js entities (classes, objects, methods, and properties) are not accessible to JavaScript. To make them available, you must explicitly annotate them using @JSExportTopLevel or @JSExport.

    • Use @JSExportTopLevel for entities that should exist in the JavaScript global scope (like top-level objects or classes) or as module exports.
    • Use @JSExport for members (methods, properties) inside an exported class or object.
    package example
    
    import scala.scalajs.js.annotation._
    
    @JSExportTopLevel("HelloWorld")
    object HelloWorld {
      @JSExport
      def sayHello(): Unit = {
        println("Hello world!")
      }
    }
  2. Explore Scala.js library ecosystem and tools

    main

    The Scala.js ecosystem provides several ways to extend your development experience:

    • JavaScript facades: Use these to interact with existing JavaScript libraries in a safe, strongly-typed manner within Scala.
    • Scala libraries: Access libraries that traditionally run on the JVM but have been ported to run on JavaScript.
    • Testing: Tools and patterns to ensure your Scala.js code remains correct.
    • Project Skeletons: Templates to help you get started with a new project quickly.
  3. Explore Scala.js presentations and videos

    main

    The Scala.js community maintains a collection of presentations and videos categorized by topic to help developers learn the ecosystem. Available resources include:

    • Featured presentations: High-level overviews and practical guides (e.g., Hands-on Scala.js, Cross-Platform Development).
    • For JavaScript developers: Content specifically tailored to bridge the gap between JS and Scala.
    • User stories: Real-world applications and experiences from developers using Scala.js in production or specific workflows (e.g., Full stack Scala, React to Scala.js).
    • Live coding: Demonstrations of Scala.js in action.
    • Internals: Deep dives into the technical aspects like Semantics, the Compilation Pipeline, and Optimizations.
    • Multilingual content: Presentations available in Polish, Russian, Japanese, and German.
  4. New JavaScript interoperability features in Scala.js 0.6.15

    main

    Scala.js 0.6.15 introduced several new features for better JS interoperability:

    • @JSExportStatic: Use this to define static members on Scala.js-defined JavaScript classes.
    • Enhanced @JSExportTopLevel: Now supports fields in addition to methods and top-level classes/objects.
    • ECMAScript 2015 Support: The standard library now includes APIs for:
      • js.Symbol (ES2015 symbols)
      • js.Iterable[+A]
      • js.Iterator[+A]
    • Symbolic @JSName: The @JSName annotation now accepts a js.Symbol (instead of just a constant string) to declare members whose name is a JavaScript symbol.
  5. Optimize Splitting Granularity by Structuring Code by Feature

    main

    Scala.js splits modules along class boundaries. To ensure effective module splitting (e.g., when using SmallestModules), avoid grouping unrelated features into a single object or class.

    Bad Pattern (Poor Splitting): Grouping multiple features into one object prevents the linker from splitting them into separate files.

    object UI {
      def renderFeature1(): Unit = ???
      def renderFeature2(): Unit = ???
    }

    Good Pattern (Better Splitting): Group code that belongs to the same feature into its own object/class. This allows the linker to isolate features into their own modules.

    object Feature1 {
      def render(): Unit = ???
      def calc(): Unit = ???
    }
    
    object Feature2 {
      def render(): Unit = ???
      def calc(): Unit = ???
    }
  6. Compatibility notes for Scala.js 0.6.10

    main

    Scala.js 0.6.10 is a minor release with the following compatibility characteristics:

    • Backward Compatibility: It is backward source and binary compatible with previous releases in the 0.6.x series. Libraries compiled with earlier 0.6.x versions can be used with 0.6.10 without change.
    • Forward Compatibility: It is forward binary compatible with 0.6.8 and 0.6.9. However, libraries compiled with 0.6.10 cannot be used by projects using versions 0.6.0 through 0.6.7.
    • Upgrading from 0.6.5 or earlier: You must review the breaking changes documented in the 0.6.6 release notes before upgrading.
  7. How default parameters in @js.native defs work

    main

    In Scala.js 1.7.1, default parameter values specified in @js.native def facades are ignored during compilation. Instead, the parameter is omitted in the generated JavaScript call, allowing the underlying JavaScript implementation to handle its own default behavior (typically by checking if the parameter is undefined).

    Key behaviors:

    • The value provided in the Scala facade (e.g., y: Int = 2) has no impact on the actual JavaScript call. The parameter is simply omitted.
    • You can now use = js.native as a default value in @js.native defs, which was previously unsupported for top-level method facades.

    Example: If you have a JavaScript object:

    const Foo = {
      bar(x, y) {
        return x + (y === undefined ? 1 : y);
      }
    };

    Defining the Scala facade as:

    @js.native @JSGlobal
    object Foo extends js.Object {
      def bar(x: Int, y: Int = 2): Int = js.native
    }

    Calling Foo.bar(5) will compile to Foo.bar(5) in JavaScript. The JavaScript engine will then use its own default (1), ignoring the Scala-defined default of 2.

    @js.native @JSGlobal
    object Foo extends js.Object {
      def bar(x: Int, y: Int = js.native): Int = js.native
    }
    
    // Calling this:
    println(Foo.bar(5))
    
    // Compiles to JS:
    // Foo.bar(5)
  8. Use call-by-name parameters to defer evaluation

    main

    Scala allows you to define parameters that are evaluated only when they are actually used inside the function body, rather than when the function is called. This is known as call-by-name. In the function signature, this is denoted by the => syntax (e.g., cPos: => Int). This is more concise and safer than the JavaScript pattern of wrapping expensive computations in anonymous functions.

    def compute(value: Int, cPos: => Int, cNeg: => Int) = {
      if (value >= 0)
        cPos
      else
        cNeg
    }
    
    // cPos and cNeg are only evaluated if the condition is met
    compute(x, expCalc, expCalc2)
  9. Use multiple parameter lists and currying

    main

    Scala supports defining functions with multiple parameter lists (e.g., def func(a: Int)(b: String)). This enables several patterns:

    1. Currying: You can call the function partially, e.g., val f = func(1)_, which returns a new function expecting the remaining parameters.
    2. Type Inference: Multiple parameter lists allow the compiler to infer types from the first list to be used in the second. For example, in def minmaxBy[T](seq: Seq[T])(f: T => Int), the type T is inferred from seq, so you don't need to explicitly provide it when calling the function with the second parameter list.
    // Example of currying and type inference
    def minmaxBy[T](seq: Seq[T])(f: T => Int): (Int, Int) = {
      seq.foldLeft((Int.MaxValue, Int.MinValue)) {
        case ((min, max), e) =>
          val v = f(e)
          (math.min(min, v), math.max(max, v))
      }
    }
    
    // Type T is inferred from 'persons', so no explicit type needed for the second list
    val (youngest, oldest) = minmaxBy(persons)(_.age)
  10. Integrate imperative JS libraries with Laminar

    main

    To bridge a reactive Laminar application with an imperative JavaScript library (like Chart.js), use the onMountUnmountCallback within a Laminar tag (e.g., canvasTag).

    1. Mounting: In the mount callback, obtain the underlying DOM element via nodeCtx.thisNode.ref and instantiate the JS library using that element and your configuration.
    2. Lifecycle Management: Store the JS instance in a local variable (e.g., an Option[T]). In the unmount callback, call the library's cleanup method (e.g., .destroy()) to prevent memory leaks.
    3. Reactive Updates: Use a Laminar binder (like dataSignal --> { ... }) to bridge the FRP (Functional Reactive Programming) world to the imperative world. Inside the binder, access the stored JS instance and call its update methods (e.g., .update()) whenever the signal changes.
    import scala.scalajs.js.JSConverters.*
    import typings.chartJs.mod.*
    
    def renderDataChart(): Element =
      var optChart: Option[Chart] = None
    
      canvasTag(
        onMountUnmountCallback(
          mount = { nodeCtx =>
            val domCanvas: dom.HTMLCanvasElement = nodeCtx.thisNode.ref
            val chart = Chart.apply.newInstance2(domCanvas, chartConfig)
            optChart = Some(chart)
          },
          unmount = { thisNode =>
            for (chart <- optChart) chart.destroy()
            optChart = None
          }
        ),
        // Bridge FRP signal to imperative JS update
        dataSignal --> { data =>
          for (chart <- optChart) {
            chart.data.labels = data.map(_.label).toJSArray
            chart.data.datasets.get(0).data = data.map(_.price).toJSArray
            chart.update()
          }
        }
      )