{"id":15011809,"url":"https://github.com/lmnet/scala-js-preact","last_synced_at":"2025-04-12T03:31:26.307Z","repository":{"id":57721103,"uuid":"75536587","full_name":"LMnet/scala-js-preact","owner":"LMnet","description":"Scala.js facade for the Preact JavaScript library","archived":false,"fork":false,"pushed_at":"2018-01-06T11:16:18.000Z","size":82,"stargazers_count":26,"open_issues_count":1,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-25T23:24:07.395Z","etag":null,"topics":["javascript","preact","react","scala","scala-js-preact","scalajs","scalameta"],"latest_commit_sha":null,"homepage":"https://preactjs.com/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LMnet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-04T12:40:46.000Z","updated_at":"2024-05-27T08:19:30.000Z","dependencies_parsed_at":"2022-09-04T16:12:06.231Z","dependency_job_id":null,"html_url":"https://github.com/LMnet/scala-js-preact","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LMnet%2Fscala-js-preact","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LMnet%2Fscala-js-preact/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LMnet%2Fscala-js-preact/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LMnet%2Fscala-js-preact/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LMnet","download_url":"https://codeload.github.com/LMnet/scala-js-preact/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248512634,"owners_count":21116649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","preact","react","scala","scala-js-preact","scalajs","scalameta"],"created_at":"2024-09-24T19:41:44.622Z","updated_at":"2025-04-12T03:31:25.790Z","avatar_url":"https://github.com/LMnet.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"scala-js-preact\n===============\n\n[![Build Status](https://travis-ci.org/LMnet/scala-js-preact.svg?branch=master)](https://travis-ci.org/LMnet/scala-js-preact)\n\nScala.js facade for the [Preact](https://preactjs.com/) JavaScript library.\n\nQuick start\n-----------\n\nAdd the following lines into your `build.sbt` file:\n\n```scala\nlibraryDependencies ++= Seq(\n  \"com.github.lmnet\" %%% \"scala-js-preact-core\" % \"0.2.1\",\n  \"com.github.lmnet\" %%% \"scala-js-preact-dsl-tags\" % \"0.2.1\"\n)\naddCompilerPlugin(\"org.scalameta\" % \"paradise\" % \"3.0.0-M10\" cross CrossVersion.full)\nscalacOptions += \"-P:scalajs:sjsDefinedByDefault\"\n```\n\nNow you can create Preact component:\n\n```scala\nimport org.scalajs.dom.Event\nimport preact.Preact.VNode\nimport preact.macros.PreactComponent\n\nobject SomeComponent {\n  case class Props(name: String)\n}\n\n@PreactComponent[SomeComponent.Props, Unit]\nclass SomeComponent {\n\n  import preact.dsl.tags._\n\n  def render(): VNode = {\n    div(id := \"foo\",\n      span(s\"Hello, ${props.name}!\"),\n      button(onclick := { _: Event =\u003e println(\"hi!\") }, \"Click Me\")\n    )\n  }\n}\n```\n\nAnd render it:\n\n```scala\nimport org.scalajs.dom\nimport preact.Preact\n\nimport scala.scalajs.js.annotation.JSExportTopLevel\n\nobject App {\n\n  @JSExportTopLevel(\"App.main\")\n  def main(): Unit = {\n    val appDiv = dom.document.getElementById(\"app\")\n    val component = SomeComponent(name = \"scala-js-preact user\")\n    Preact.render(component, appDiv)\n  }\n}\n```\n\nYou can find more examples in the [examples directory](examples).\n\nDesign goals and motivation\n---------------------------\n\nWhy should you care about this \"yet another Scala.js facade for React\"? The current situation in the Scala.js ecosystem\nforced developers to spend quite a lot of time to be able to write frontend applications with React facades.\nAlso, React is a pretty heavy library and Scala.js runtime also adds its own overhead.\nEven after `fullOptJS` and with `gzip` it's common to have more than 1 MB of JavaScript code in the bundle.\n\n`scala-js-preact` is trying to solve this issues:\n\n1. Preact instead of React. Preact is a fast 3KB alternative to React, with the same API.\nIt helps to minimize js bundle size.\n2. Minimalistic and simple. `scala-js-preact` contains a small amount of code and this also helps to prevent\nyour bundle overgrowth. Also, it doesn't contain any complex functional stuff in the API which is good for the newcomers.\n3. Modular architecture. Use only parts you need in your application.\n4. Familiar API. Scala API should be more or less like JavaScript API.\n5. DSL agnostic. Scala is the very powerful language in terms of DSL. But this power also lead to a lot of debates.\n`scala-js-preact` doesn't limit you with the single HTML DSL. It provides a few DSL out of the box,\nbut you can easily create your own DSL, or use third-party DSL.\n\n\nModules\n-------\n\n1. `scala-js-preact-raw` - raw Preact facade. Just some low-level typings.\nYou will need this module only if you want to create your own Preact library.\n2. `scala-js-preact-core` - core module. Contains APIs for creating class and function components.\nThis is the main module of the library. To use this module you need to add macro-paradise compiler plugin\nbecause `core` uses some macro magic to provide nice API for creating components.\n3. `scala-js-preact-dsl-symbol` - minimalistic DSL module with dynamically typed tags and attributes.\nInspired by [levsha](https://github.com/fomkin/levsha).\n3. `scala-js-preact-dsl-tags` - DSL module with typesafe tags and attributes.\nInspired by [scalatags](https://github.com/lihaoyi/scalatags).\n\nUsage\n-----\n\n### Scala.js configuration\n\nTo use `scala-js-preact` you should add this line into your build.sbt file:\n\n```scala\nscalacOptions += \"-P:scalajs:sjsDefinedByDefault\"\n```\nYou can read more about this scalac option [here](https://www.scala-js.org/news/2017/09/01/announcing-scalajs-0.6.20/).\n\nThis requirement is temporary: after release Scala.js 1.0 this option will be always enabled without explicit configuration.\n\n### Components\n\nTo create a class component you should annotate your class with `@PreactComponent[PropsType, StateType]` annotation.\nYou should set up `Props` and `State` types of the component:\n```scala\ncase class Props(...)\ncase class State(...)\n\n@PreactComponent[Props, State]\nclass SomeComponent {...}\n```\n\nIf your component doesn't use `Props` or `State` you can set any of them as `Unit`:\n```scala\n@PreactComponent[Props, Unit]\nclass StatelessComponent {...}\n\n@PreactComponent[Unit, State]\nclass PropslessComponent {...}\n\n@PreactComponent[Unit, Unit]\nclass ConstantComponent {...}\n```\n\n*IMPORTANT NOTE!* You should always wrap `Props` and `State` in the class (or case class), even if it has only one parameter. This is necessary because these objects are handled by Preact's javascript code, which could mutate your immutable scala code from js side and cause undefined behavior.\n\nThe only one required method in the components is `render`:\n```scala\n@PreactComponent[Props, State]\nclass SomeComponent {\n  def render(): VNode = ???\n}\n```\n\nHere is the list of other `Component` methods:\n* `props` - gives access to the read-only `props` object.\n* `state` - gives access to the read-only `state` object.\n* `setState` - method to define a new state of the component. This is the only way to set up a new state.\n* `initialState` - if you want to define the initial state of the component, you should do this with this method.\nThis method could be called only once.\n* `children` - gives access to the component's children.\n* `key` - gives access to the component's [key](https://facebook.github.io/react/docs/lists-and-keys.html).\n* `base` - gives access to the component's DOM node.\n* `componentWillMount` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n* `componentDidMount` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n* `componentWillUnmount` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n* `componentWillReceiveProps` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n* `shouldComponentUpdate` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n* `componentWillUpdate` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n* `componentDidUpdate` - [lifecycle method](https://preactjs.com/guide/getting-started#the-component-lifecycle).\n\n#### Auto generated component's constructor\n\nIf your component use props, creating a component instance usually looks like this:\n\n```scala\ncase class Props(foo: String, bar: Int)\n\n@PreactComponent[Props, Unit]\nclass SomeComponent {\n  def render(): VNode = ???\n}\n\nval instance = SomeComponent(Props(\"test\", 15))\n```\n\nBut, its a good practice to place local `Props` and `State` case classes inside companion object of the component.\nIf you do this, `@PreactComponent` macro will generate additional `apply` method with the same signature as `Props` case class has:\n\n```scala\nobject SomeComponent {\n  case class Props(foo: String, bar: Int)\n}\n@PreactComponent[SomeComponent.Props, Unit]\nclass SomeComponent {\n  def render(): VNode = ???\n}\n\nval instance = SomeComponent(\"test\", 15) // don't need to wrap argument in Props(...)\n```\nIt makes your code a little more readable.\n\n#### Getting props from component's constructor\n\nThere is some unobvious moment with getting props from the component's constructor.\nThis is the common pattern to set up initial state from the props from the constructor. Let's try to write some code with this pattern:\n```scala\ncase class Props(foo: Int)\ncase class State(bar: String)\n\n@PreactComponent[Props, State]\nclass SomeComponent(props: Props) {\n\n  initialState(State(\n    bar = props.foo.toString\n  ))\n\n  def render(): VNode = {\n    div(props.foo.toString, state.bar)\n  }\n}\n```\nAt first glance, everything looks good. But, what about `props` method? In the component above we have a naming conflict:\n`props` object from the constructor and `props` method in the same scope. To prevent this issue we should rename\nour constructor argument, for example like this:\n```scala\n@PreactComponent[Props, State]\nclass SomeComponent(initialProps: Props) {\n\n  initialState(State(\n    bar = initialProps.foo.toString\n  ))\n\n  def render(): VNode = {\n    div(props.foo.toString, state.bar)\n  }\n}\n```\nNow we don't have naming conflict and can use both `initialProps` and `props`.\n\nGood thing to know: `@PreactComponent` macro checks this and if you got into this problem you will have\nnice compile-time error.\n\n### DSL\n\n`scala-js-preact` got two optional DSLs out of the box: symbol DSL and tags DSL. You can use any of them, or any third party DSL,\nor even create your own DSL. In the sections below you will be guided how to use default DSLs and create your own.\n\n#### Symbol DSL\n\nThis DSL is inspired by [levsha](https://github.com/fomkin/levsha) DSL. Its goal is to be minimalistic and not typesafe\nin terms of HTML tags or attributes. You can use any tags and attributes you want, like in normal HTML.\nAll under your control and responsibility.\n\nTo use this DSL add it to your dependencies:\n```scala\n\"com.github.lmnet\" %%% \"scala-js-preact-dsl-symbol\" % \"0.2.1\"\n```\n\nAnd import `preact.dsl.symbol._` into your source code:\n```scala\nimport preact.dsl.symbol._\n\n'section(\"class\" -\u003e \"todoapp\",\n  'header(\"class\" -\u003e \"header\",\n    'h1(\"todos\")\n  )\n)\n```\n\nThis DSL is very simple:\n```scala\n// Any `scala.Symbol` became tag with `apply` method:\n'div()\n\n// Tuples inside tags became attributes:\n'div(\n  \"class\" -\u003e \"foo\"\n)\n\n// Functions works too:\ndef callback(event: Event): Unit = {\n  ???\n}\n'div(\n  \"onclick\" -\u003e callback _\n)\n\n// You can pass any VNode, component or strings inside tags:\n'div(someComponentInstance, \"some text\", 'b(\"some another tag\"))\n\n// Also, there are a few helpers:\n'div(\n  if (something) {\n    Entry.Children(Seq(???)) // gives possibility to pass `Iterable[Preact.Child]` to any tag\n  } else {\n    Entry.EmptyChild // useful for conditional children\n  },\n  if (somethingElse) {\n    \"foo\" -\u003e \"bar\"\n  } else {\n    Entry.EmptyAttribute // useful for conditional attributes\n  }\n)\n```\n\n#### Tags DSL\n\nThis DSL is inspired by [scalatags](https://github.com/lihaoyi/scalatags) DSL. Its goal is to provide typesafe HTML DSL,\nand at the same time be minimalistic as possible.\n\nWhy not just create `scalatags` backend for the `scala-js-preact`? Here are a few reasons:\n1. Annoying `.render` on all fragments.\n2. `scalatags` is small, but not enough small.\nIt contains some parts that I don't want to have in the `scala-js-preact` DSL, like styles.\n3. Because of `scalatags` internals and rendering process, it will add some extra performance overhead.\n\nBut if you want you can create `scalatags` backend for the `scala-js-preact`. Contributions are welcome!\n\nTo use this DSL add it to your dependencies:\n```scala\n\"com.github.lmnet\" %%% \"scala-js-preact-dsl-tags\" % \"0.2.1\"\n```\n\nAnd import `preact.dsl.tags._` into your source code:\n```scala\nimport preact.dsl.tags._\n\nsection(`class` := \"todoapp\",\n  header(`class` := \"header\",\n    h1(\"todos\")\n  )\n)\n```\n\nThe main idea is the same as in the `scalatags`. I suggest you to familiarize with its docs at first.\n\nThe main difference between `scalatags` and `scala-js-preact-dsl-tags`:\n* You don't need to call `.render`method on your fragments.\n* `Tag`'s `apply` method returns and receives `Preact.VNode`.\n* Has `Children`, `EmptyAttribute` and `EmptyChild`, just like Symbol DSL.\n\n#### Creating your own DSL\n\nEvery Preact DSL is based on the single `Preact.raw.h` function under the hood.\nThis function creates `VNode` instances.\nIt has the following signatures (in the real source code they are looking a little different):\n```scala\ntype NodeType = js.Dynamic |        // for class components\n  js.Function0[VNode] |             // for function components without arguments\n  js.Function1[js.Dynamic, VNode] | // for function components with props argument\n  String                            // for HTML tags\n\n// for VNode with children\ndef h(\n  node: NodeType,\n  params: js.Dictionary[js.Any], // if params is empty you should pass null\n  children: Child*\n): VNode\n\n// for VNode without children\ndef h(\n  node: NodeType,\n  params: js.Dictionary[js.Any] // if params is empty you should pass null\n): VNode\n```\n\n_Important note_: in the Preact (and React) `null` is the correct child node. In your DSL you should correctly preserve\nan order of all child nodes, including `null`. You can replace `null` with something else in your high-level DSL API,\nbut on the low-level `Preact.raw.h` call you should pass `null`. Look at the `Entry.EmptyChild` in the symbol or tags\nDSLs for the example.\n\nThis is all you should know about creating DSLs for `scala-js-preact`.\nYou can check code of the built-in DSLs for the real examples.\n\nProject status\n==============\n\nThis project is in the pre-alpha state. Please, don't use it in the production!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmnet%2Fscala-js-preact","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flmnet%2Fscala-js-preact","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmnet%2Fscala-js-preact/lists"}