Code Monkey home page Code Monkey logo

scala-js-chrome's Introduction

Chrome for Scala.js Build Status Gitter Latest version

The goal of this project is to provide an easy and typesafe way to create Chrome apps and extensions in Scala using the Scala.js project.

DISCLAIMERS:

  • As of today (2020-03-30), I decided to fork the plugin so that I can keep applying updates and releasing new versions, the main reason to do this is to migrate to scalajs 1.0.0, which support is still experimental, if you use an older scalajs version, use the original plugin instead.
  • Versions after v0.6.1 replaced the manifest encoding, while it has been tested on some browser extensions, there might be errors, if you find any while installing the extension/app, try the v0.6.1 version to see if the error disappears, in any case, please submit an issue with your manifest details, or even better, submit a pull request.

Chrome API bindings

The bindings provide access to the Chrome app and extension APIs. There are two levels for each API. One that provides the raw JavaScript bindings and a second one which wraps the raw API in a more Scala idiomatic way.

The package structure is similar to the original JavaScript API.

// original JavaScript
chrome.system.cpu.getInfo(function(info){
  if (chrome.runtime.lastError === undefined) {
    console.log(info);
  } else {
    console.log("ohoh something went wrong!");
  }
});
// raw bindings
chrome.system.cpu.bindings.CPU.getInfo((info: CPUInfo) => {
    if (chrome.runtime.bindings.Runtime.lastError.isEmpty) {
        println(info)
    } else {
        println("ohoh something went wrong!")
    }
})

// Scala idiomatic way using Future
chrome.system.cpu.CPU.getInfo.onComplete {
  case Success(info) => println(info)
  case Failure(error) => println("ohoh something went wrong!")
}

The Scala idiomatic binding provides the following general changes:

  • Futures instead of callbacks
  • Error handling using types like Future / Try instead of global error variable.
  • Using Option for things that may or may not be defined.

SBT Plugin

The job of the SBT plugin is to help with common tasks for developing Chrome apps/extensions. It also provides a way to configure your app/extension in your SBT file and automatically generate the manifest file.

  • chromePackage will create a ZIP file you can upload to the Chrome Web Store.
  • chromeUnpackedOpt (or chromeUnpackedFast) will build your projects with (or without) optimizations enabled. The output will be in target/chrome/unpacked-opt (or target/chrome/unpacked-fast) and can be loaded by Chrome as an unpacked extension/app.

Getting Started

If you like to get an extension working fast, just follow the chrome-scalajs-template instead.

Add this to your project/plugins.sbt:

addSbtPlugin("com.alexitc" % "sbt-chrome-plugin" % "0.7.5")

Add this to your project dependencies:

"com.alexitc" %%% "scala-js-chrome" % "0.7.5"

If you need the latest version, clone the repository and run sbt version on it, replace the current version with that value. You may need to add this line to resolve the snapshot version: resolvers += Resolver.sonatypeRepo("snapshots")

to use the <project-name>-f{ast,ull}opt-bundle.js generated by scalajs-bundler add the following to your build.sbt:

fastOptJsLib := Attributed.blank((webpack in (Compile, fastOptJS)).value.head)
fullOptJsLib := Attributed.blank((webpack in (Compile, fullOptJS)).value.head)

NOTE: if code seems to be executing duplicate times unintentionally, try removing these lines from the project's build.sbt

scalaJSUseMainModuleInitializer := true
scalaJSUseMainModuleInitializer in Test := false

Creating a basic Window

import chrome.app.runtime.bindings.LaunchData
import chrome.app.window.Window
import utils.ChromeApp

import scalajs.concurrent.JSExecutionContext.Implicits.queue

object ChromeAppExample extends ChromeApp {

  override def onLaunched(launchData: LaunchData): Unit = {
    println("hello world from scala!")
    Window.create("assets/html/App.html").foreach { window =>
      /**
         Access to the document of the newly created window.
         From here you can change the HTML of the window with whatever
         library you want to use.
      */
      window.contentWindow.document
    }
  }

}

For a more complete example see chrome-system-monitor and scala-js-chrome examples.

UI Libraries

There are already multiple libraries to manipulate HTML and build your UI available for Scala.js:

Known Issues

In Chrome apps and extensions there are multiple places where you can run JavaScript. Normally you split your logic into different files and load them into whatever context they need to run. Since Scala.js compiles your whole project into one big file all contexts need to load this big file with all the logic even if they only need a small subset. This can cause your app you use more memory then it need to. In some cases this can be worked around for example the a background page can manipulate the DOM of an App window so you don't need any JavaScript at all in the window itself.

scala-js-chrome's People

Contributors

alexbezhan avatar alexitc avatar antonkulaga avatar caisel avatar dylanowen avatar edmundnoble avatar emanresusername avatar guersam avatar jakehschwartz avatar lucidd avatar mineme0110 avatar mprevel avatar rinfield avatar sbrunk avatar scala-steward avatar therealcisse avatar veinhorn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

scala-js-chrome's Issues

Verify that source maps are generated correctly

While migrating to scalajs-bundler, I have changed code that touches the source maps generation (1e2e6fd), I think that the approach changes from sbt-js-dependencies but I haven't put much time into verifying that it still works.

Let's verify that, and, if necessary, fix it.

Going from browser console JS to scala-js-chrome code

TL;DR

Browser JS Console:

Object.entries(document.querySelector('.maplibregl-map'))

produces:
image
๐Ÿ‘

vs. the "same" in a chrome extension using ScalaJS:

import scala.scalajs.js
val evalResult = js.eval("Object.entries(document.querySelector('.maplibregl-map'))")
console.log(evalResult)

image

๐Ÿ‘Ž

Why does this produce an empty array? How do I recreate what I did in Chrome's developer console?


Investigation

fwiw, the produced JS is:

$m_Lorg_scalajs_dom_package$().console__Lorg_scalajs_dom_raw_Console().log("eval:");
var evalResult = (0, eval)("Object.entries(document.querySelector('.maplibregl-map'))");
$m_Lorg_scalajs_dom_package$().console__Lorg_scalajs_dom_raw_Console().log(evalResult)

If I, manually, change the generated scalajs code to:

$m_Lorg_scalajs_dom_package$().console__Lorg_scalajs_dom_raw_Console().log("eval:");
var evalResult = (0, eval)("Object.entries(document.querySelector('.maplibregl-map'))");
$m_Lorg_scalajs_dom_package$().console__Lorg_scalajs_dom_raw_Console().log(evalResult)
$m_Lorg_scalajs_dom_package$().console__Lorg_scalajs_dom_raw_Console().log(eval("Object.entries(document.querySelector('.maplibregl-map'))"))
$m_Lorg_scalajs_dom_package$().console__Lorg_scalajs_dom_raw_Console().log(Object.entries(document.querySelector('.maplibregl-map')))

I still get empty arrays:
image

It would appear as if JS run in the console has access to a wider scope, and indeed it has. It's called "top" and once I change it to the extension's scope (see Chrome docs):

image

then I, too, can no longer find those objects:

image

It seems that, in order to escape from that extension or "content" context, I have to make use of script injection aka content scripts.


Where this is coming from

I am writing a Chrome extension adding functionality to the route planner of komoot.com. The extension has progressed quite a bit already but I want to take it a step further.
However, I am stuck.

I have arrived at a snippet of JS that, when entered in Chrome's JS console (via inspect element), gives me what I require:
Object.entries(document.querySelector('.maplibregl-map')).find(([k, ]) => k.startsWith('__reactInternalInstance$'))[1].return.memoizedProps.value.map._listeners:
image

I just can't figure out how to translate that to scala-js code using this wonderful sbt plugin.

My attempts so far:

import scala.scalajs.js.Object
def selected = document.querySelector(".maplibregl-map")
console.log("selected:")
console.log(selected)

results in:
image

just as I would had I entered that personally in the browser console:
image

However, that is where the similarities end.

My Scala code (continued):

console.log(s"Object.entries of selected:")
console.log(Object.entries(selected))

logs this:
image

To me, this looks more like a Scala object, likely the result of the scala-js compilation. This makes sense, of course, since Object.entries is essentially reflection in JS.

So I tried using eval:

console.log("eval:")
console.log(scala.scalajs.js.Dynamic.global.eval("Object.entries(document.querySelector('.maplibregl-map'))"))

But this gets me an empty array:
image

Whereas in the browser, a Object.entries(document.querySelector('.maplibregl-map')) results in:
image

Again, this is likely something trivial and maybe the answer is staring right at me but I'm not seeing it. Thanks for any help!

Manifest v3?

Hi, Chrome Extension Manifest v2 (MV2) reaches its EOL in December of 2023. Any updates/roadmap about manifest?

I'm interested in this awesome plugin, but my browser warns me about manifest version when I load plugin manually (I used the template repository: https://github.com/AlexITC/chrome-scalajs-template).

Are expected changes large if I write some PRs? Writing extension in Scala is defintely awesome thing. If you tell me changes that will be needed, I can dedicate some work for this plugin.

Enable fatal warnings

Run sbt "++2.13.7" compile which throws these warnings:

[warn] /home/scala-js-chrome/bindings/src/main/scala/chrome/browserAction/bindings/BadgeBackgroundColorDetails.scala:14:74: Implicit resolves to enclosing value colorEvidence
[warn]   implicit val colorEvidence: Evidence[String | js.Array[Int], js.Any] = implicitly
[warn]                                                                          ^
[warn] /home/scala-js-chrome/bindings/src/main/scala/chrome/browserAction/bindings/IconDetails.scala:18:88: Implicit resolves to enclosing value imageEvidence
[warn]   implicit val imageEvidence: Evidence[ImageData | js.Dictionary[ImageData], js.Any] = implicitly
[warn]                                                                                        ^
[warn] /scala-js-chrome/bindings/src/main/scala/chrome/browserAction/bindings/IconDetails.scala:19:81: Implicit resolves to enclosing value pathEvidence
[warn]   implicit val pathEvidence: Evidence[String | js.Dictionary[String], js.Any] = implicitly
[warn]                                                                                 ^
[warn] three warnings found

Let's resolve the warnings and enable fatal warnings in the build.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.