ä½ã£ãã
esbuildã¨ã¯JSã®ãã³ãã©ã§ããããã¡ããã¡ãé«éã«åä½ããã®ãã¦ãªã§ããã
ããã¦ä»åèªåãä½è£½ãããã®ãã©ã°ã¤ã³ã使ãã¨ãesbuildãScalaã®ã³ã¼ãã®importãè¦ä»ããã¨åæã«ãã«ãã»ãã³ãã«ããã®ã§ãTSãJSã®ã³ã¼ããæ¸ãã¦ããã¨ãã«çªç¶Scalaã®ã³ã¼ããå¼ã¹ã:
// src/main/scala/Main.scala package scalamain import scala.scalajs.js import scala.scalajs.js.annotation._ object Main { // JSå´ããè¦ããããã«ããå¦ç @JSExportTopLevel("fib", moduleID = "scalamain") def fib(n: Int): Int = { if (n <= 1) n else fib(n - 1) + fib(n - 2) } }
// src/main/js/main.js import { fib } from 'scala:scalamain'; const main = async () => { console.log(fib(10)); } main();
% node dist.cjs
55
ãã®è¨äºã¯ã¯ã¦ãªã¨ã³ã¸ã㢠Advent Calendar 2024ã®10æ¥ç®ã®è¨äºã§ããæ¨æ¥ã¯id:gurriumã®ãMaestroããã£ããã§ãããä¸ã®ä¸ããããªãã¼ã«ãçºæããã¦ãã¦é¢ç½ãã§ãããã
ããããScala Advent Calendar 2024ã®è¨äºã§ãããã®ã§ãããããªãã¨ã許ãããã®ã!? ãã¡ãã®ååã¯id:xuweiã®ãCheerpJã使ã£ã¦ScalaãWebãã©ã¦ã¶ã§åãããã§ããããã©ã¦ã¶ã§JVMãã¨ãã¥ã¬ã¼ãããã¨ããè¬æè¡ããããã¨ã«ã¾ãé©ãã¾ãããWASMã§JVMãåããããããé¢ç½ãããããã¾ããã(æ¾è¨)ã
è¨å®
ãã®ãã©ã°ã¤ã³ã使ãã«ã¯ãã¾ãnpm i
ãã¦
% npm i -D esbuild-scalajs
ã¢ã¸ã¥ã¼ã«çºè¦ã«å¿ è¦ãªæ å ±ããªãã¸ã§ã¯ãã¨ãã¦ç¨æã
const opts = { "scalaVersion": "3.5.2", "scalaProjectName": "esbuild-exercise", "scalaTargetFileExtension": "js", }
ãã©ã°ã¤ã³ã¨ãã¦esbuildã«æ¸¡ãã ãã
import * as esbuild from 'esbuild' import { scalaJsPlugin } from 'esbuild-scalajs' const opts = { "scalaVersion": "3.5.2", "scalaProjectName": "esbuild-exercise", "scalaTargetFileExtension": "js", } await esbuild.build({ entryPoints: ['main.js'], bundle: true, platform: 'node', outfile: 'dist.cjs', minify: true, plugins: [scalaJsPlugin(opts)], })
ãã¨ã¯esbuildãèµ·åããã ãã§ãã:
// package.json { "type": "module", "scripts": { "build": "node esbuild.mjs" }, "devDependencies": { "esbuild": "0.24.0", "esbuild-scalajs": "^0.0.4" } }
% npm run build > @ build /home/windymelt/src/github.com/windymelt/esbuild-exercise > node esbuild.mjs 2024.12.10 23:52:35:104 main INFO dev.capslock.esbuild.ScalaJsPlugin.scalaJsPlugin:23 scalaJsPlugin 0.0.4 (Scala 3.6.2) is starting up... 2024.12.10 23:52:35:118 main INFO dev.capslock.esbuild.ScalaJsPlugin.setup:49 running sbtn [info] entering *experimental* thin client - BEEP WHIRR [info] terminate the server with `shutdown` > fastLinkJS [success] Total time: 0 s, completed Dec 10, 2024, 11:52:35 PM 2024.12.10 23:52:35:207 main INFO dev.capslock.esbuild.NodeAPI.runCommand:15 process exited 2024.12.10 23:52:35:208 main INFO dev.capslock.esbuild.ScalaJsPlugin.setup:49 running sbtn [info] entering *experimental* thin client - BEEP WHIRR [info] terminate the server with `shutdown` > fastLinkJS [success] Total time: 0 s, completed Dec 10, 2024, 11:52:35 PM 2024.12.10 23:52:35:290 main INFO dev.capslock.esbuild.NodeAPI.runCommand:15 process exited
é¢ç½ãã®ã¯ããã£ã¬ã¯ããªä¸ã«jsã¨scalaã®ã½ã¼ã¹ãæ··å¨ãã¦ããã©æ®éã«åãã¦ãããã¨ã
ä»çµã¿
esbuildã«ã¯ãç¹å®ã®ãã¡ã¤ã«ãimportã«ç¹åãããã©ã°ã¤ã³ãæ¸ãä»çµã¿ãåãã£ã¦ãããä»åã¯ãããå©ç¨ããã
ãã£ããè¨ãã¨ãname
ã¨ããstring
ã®ãã£ã¼ã«ãã¨ãsetup
ã¨ãããã«ãæ
å ±ãåãåã£ã¦å種ããã¯ãè¨å®ããããã®ã¡ã½ããã¨ã®2ã¤ãæã¤ãªãã¸ã§ã¯ãã§ããã°ããªãã§ããã©ã°ã¤ã³ã¨ãã¦å©ç¨ã§ãããå®éã«ãã¡ã¤ã«ã®è§£æ±ºãè¡ãå¦çã¯setup
ã§åãåããã«ããªãã¸ã§ã¯ãã«å¯¾ãã¦ã³ã¼ã«ããã¯ãè¨å®ãã¦ããã¨ããå½¢ã«ãªã:
// æç² def setup(build: Build): Unit = { val isProd = process.env.NODE_ENV.map(_ == "production").getOrElse(false) val scalaProjectName = opts.scalaProjectName val scalaTargetDirSuffix = if isProd then "-opt" else "-fastopt" var scalaTargetFileExtension: String = opts.scalaTargetFileExtension // if and only if cache miss is detected first, run sbtn build.onResolve( new OnResolveProps { val filter = js.RegExp("""^scala:.+""") }, (args) => { // å¥å¦çã«åãåºãã¦ãã onResolve(args, isProd, scalaProjectName, scalaTargetDirSuffix, scalaTargetFileExtension).toJSPromise }, ) }
ããããã°æ¸ãå¿ãã¦ãããããã®ãã©ã°ã¤ã³èªä½ãScala.jsã§æ¸ããã¦ããããã¬ã¼ãªã©ãå ¥ããããã§ãã³ãã«ãµã¤ãºããã«ããªã£ã¦ãã¾ã£ããã»ã»ã»
ã¨ããããbuild.onResolve
ã«æ³¨ç®ãã¦ã»ããããã«ããªãã¸ã§ã¯ãã«ãã®ãã³ãã©ãè¨å®ãã¦ããã¨ãesbuildãç¹å®ã®æ£è¦è¡¨ç¾ã«ããããããããªimport
æã«ééããã¨ãã«ã³ã¼ã«ããã¯ãããããã«ãªãããã©ã°ã¤ã³å´ã§ã¯ãã®æ
å ±ãèªã¿åããç®çã®jsãã¡ã¤ã«ã¸ã®çµ¶å¯¾ãã¹ãè¿ãã°è¯ãããã¨ã¯esbuildãåæã«ãã®ãã¡ã¤ã«ãèªã¿ã«è¡ã£ã¦ãã³ãã«ãã¦ããããæã
ãããã¹ããã¨ã¯ãé©åãªjsãã¡ã¤ã«ãæãããã¨ã ãã
ã¡ãªã¿ã«jsãã¡ã¤ã«ãæããã ãã§ãªããjsãã¡ã¤ã«ãçæããã¨ããã¾ã§ãã®ãã©ã°ã¤ã³ããããsbt(Scalaã®ãã«ããã¼ã«)ã®è»½éãªã¯ã©ã¤ã¢ã³ãã§ããsbtn
ã®ããã»ã¹ãèµ·åãããã¨ã§ãé常ã«é«éã«ãã«ããæããããã«ãããsbtåä½ãèµ·åããã¨æ°ç§ãããããsbtnã¯è£ã§ãã«ããµã¼ãã«ã¤ãªãã«è¡ãä¸ããã¤ãã£ããã¤ããªã¨ãã¦æä¾ããã¦ããã®ã§çéã§èµ·åãã¦ãããã®ã ã
ããã«ãããsbtnãESModuleã¾ãã¯CommonJSãåãåºãã¦ãããã®ã§ããã®çæå ãã¹ãäºæ¸¬ãã¦esbuildã«æ¸¡ããã¨ã§ã·ã¼ã ã¬ã¹ãªãã«ããæç«ããã®ã ã
è¦å´ããã¨ãã
sbtã®åä½ã¯æ±ºå®çã ããScala.jsèªä½ã§å°ããã¨ããã¾ããªãã£ã(æå¤ã¨export
ã¾ãããããã³ã¨ãã³ããªã³ã°ããæ段ããããjsããå¼ã¶ã®ã«ã¯å°ããªã)ã®ã ããesbuildã¯Goã§æ¸ããã¦ãã¦ããã®ãã¼ã¿ã®åã渡ãã®éç¨ã§ãªãã¸ã§ã¯ãã®ãã©ã¤ãã¼ããªãã£ã¼ã«ã(å
·ä½çã«ã¯ãSymbol
ã§å®ç¾©ããããã£ã¼ã«ããªã©)ï¼ããããå£ãã¦ãã¾ã£ã¦æ£å¸¸ã«ã³ã¼ã«ããã¯ãåä½ããªãã¨ãããã©ãã«ãè¸ãã ããã®ã¸ãã¯å½è©²äºè±¡ãè¸ã¾ãªããããªæ¹æ³ãçºæ(ã¯ã©ã¹ãã¤ã³ã¹ã¿ã³ã¹åããã®ã§ã¯ãªãã大åå¤æ°ã«ãã©ã¡ã¼ã¿ãéãããªã©)ãã¦ã¾ã¨ãã«åãããããã«ãªã£ãã
次ã«ãnpmã«ä»å人çåãã¦publishããã®ã ããã¢ã¯ã»ã¹ãã¼ã¯ã³ã§publishããæ¹æ³ãã¾ã£ããä¸æããããªãã£ããçµå±æã§npm publish
ãå©ãã¦ãã©ã¦ã¶ãéãã¦ãªãªã¼ã¹ãã¨ããããããã«ãªã£ã¦ãã¾ã£ãã
ãã¾ã¤ãããå½åã¯esbuild-plugin-scalajs
ã¨ããååã§publishããã¤ããã ã£ãã®ããpublish失æããã¿ã¤ãã³ã°ã§åå空éã ãããã¯ããããªããã¤windymeltã®ææã§ã¯ãªããã¨ããå¤ãªç¶æ
ã«ãªã£ã¦ãã¾ã£ãããããå
¨ç¶ãã®åå空éã使ããªããªã£ã¦ãã¾ã£ãããããããªãããesbuild-scalajs
ã¨ããååã§publishããªããã°ãªããªãã£ããããã¦ãscalajs-esbuild
ã¨ããå
¨ãå¥ã®ããã±ã¼ã¸ããã£ã¦ã¾ãããããï¼(ãã¡ãã¯esbuildã®ãã©ã°ã¤ã³ã§ã¯ãªããsbtã®ãã©ã°ã¤ã³ã«ãªã£ã¦ããã£ã½ã)
ã©ãã®æåã§ãpublishã¾ããã¯é¬¼éã¨ãããã¨ã ãããã
ãããã«
楽ããã£ãï¼Scala.jsã§å®ç¨çãªãã¼ã«ãä½ãããããnpmã«publishããå®éã«ä½¿ããããã«ãªã£ãããã£ã¨è¤éã§è¨ç®ãªã½ã¼ã¹ãå¿ è¦ãªå¦çã ã£ããã æè¿Scala.jsã§ä½¿ããããã«ãªã£ãWASMã«ãã«ããã¦ãè¯ãã£ãã®ã ããã¾ãä»åã¯æååãã³ãã³ããããããã§è¯ãã£ãã®ã§æ®éã«ESModuleã¨ãã¦ãã«ãããã ä»åº¦ã¯Scala Nativeã§ãªããä½ãããã§ããã
ã¯ã¦ãªã¨ã³ã¸ã㢠Advent Calendar 2024ã®ææ¥ã®æ å½ã¯ id:tomato3713 ã§ãã ããã¦Scala Advent Calendar 2024ã®ææ¥ã®æ å½ã¯ id:tanishiking24 ã§ãã
ããé±æ«ã«Scalaããããåå¼·ä¼ãããã§æ¥ã¦ãã ããã