-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ES Module support #246
Comments
Thanks for raising this issue, this is an important point and something to try and address before 0.1.0.
Indeed I'm not thrilled about the current encoding. Actually I settled on it in desperation for the friendliest UX. Without it, a user would have to manually override a method in their lambda and add a IMO the biggest issue for Scala.js and ESModules is lack of GCC optimization, which makes a huge difference especially for Typelevel libs which have several layers of abstraction. |
I think you need
|
Yes, perhaps the trade-off is not worth it. But it's a much less fun experience than the JVM side :) thanks for your input.
Indeed this is already how it's working :) the design choice was really only for UX, not implementation :) feral/core/src/main/scala/feral/IOSetup.scala Lines 32 to 42 in 8edd293
|
Ah, I might've clicked through the sources too quickly and thought I came up with my own brilliant idea 😉. Perhaps making the object handler extends IOLambda[ApiGatewayProxyEventV2, ApiGatewayProxyStructuredResultV2] {
override def handler = ???
@JSExportTopLevel("handler")
def main() = handlerFn
@JSExportTopLevel("handlerIO")
def main() = program.unsafeToPromise()(runtime)
}
|
At least for CommonJS, you have to export a function with a very specific signature, a |
@hugo-vrijswijk basically something like this: feral/lambda/js/src/main/scala/feral/lambda/IOLambdaPlatform.scala Lines 26 to 36 in 93e65c1
Except we can't put |
Maybe this would work? private[lambda] trait IOLambdaPlatform[Event, Result] {
this: IOLambda[Event, Result] =>
final type HandlerFn = js.Function2[js.Any, facade.Context, js.Promise[js.Any | Unit]]
protected lazy val handlerFn: HandlerFn = ...
...
} object MyMain extends IOLambda.Simple[KinesisStreamEvent, INothing] {
override def apply(event: KinesisStreamEvent, context: Context[IO], init: Init): IO[Option[INothing]] = ???
@JSExportTopLevel("handler")
val handlerMain: HandlerFn = handlerFn
}
Note the use of This would export a function, and only expose the handler type, while keeping the actual facade types private |
I actually think it doesn't, but I could be wrong. IIRC the problem is that Scala.js exports |
Yup, won't work: //> using scala "2.13.8"
//> using platform "js"
//> using jsModuleKind "commonJS"
import scala.scalajs.js
import scala.scalajs.js.annotation._
object Lambda {
@JSExportTopLevel("fnVal")
val fnVal: js.Function1[String, String] = _.reverse
@JSExportTopLevel("fnDef")
def fnDef(x: String): String = x.reverse
def main(args: Array[String]): Unit = ()
} Object.defineProperty(exports, "fnVal", {
"get": (function() {
return $t_LLambda$__fnVal
}),
"configurable": true
});
exports.fnDef = (function(arg) {
var prep0 = $as_T(arg);
return $m_LLambda$().fnDef__T__T(prep0)
}); |
Kind of spitballing here, but we've talked about creating an sbt plugin for Feral to help with deploys, etc. What if we had a plugin that somehow generated the code that was needed? |
What about the
|
Ah, that's not a bad idea! Definitely worth exploring :)
I could be wrong, but I don't think it exports things the way AWS wants them. I remember having problem with this in the past. Regardless of how it behaves in the console, you can see the encoding is obviously different. |
This is the integration test I wrote at the time: feral/sbt-lambda/src/sbt-test/lambda-js-plugin/iolambda-simple/test-export.js Lines 1 to 4 in 8edd293
|
I did some testing with exports and running the lambda's, and it looks like exporting a Scala.js emits the function as a JS getter, which AWS Lambda can call just fine as a function (if emitted properly). This works the same when exporting as CommonJS or as an ES module. @JSExportTopLevel("fnVal")
val fnVal: js.Function1[js.Object, Unit] = x => println("fnVal" + js.JSON.stringify(x))
@JSExportTopLevel("fnDef")
def fnDef(x: js.Object): Unit = println("fnDef" + js.JSON.stringify(x)) There is 1 caveat: it only works when the val is exported as a But with the I don't know if letting the user export their own handler is still the desired way to go vs a sbt plugin that generates some code, but the option is open and working, it looks like. Upside is being able to export multiple handlers, downside I guess is it's slightly more technical to set up. It would also no longer need |
@hugo-vrijswijk thank you so much for doing that investigation! That is very good news then, much appreciated. I think we have some good options, and there are many advantages to exports as you point out. I'm happy to take a PR that starts making these changes :) |
… handler Related to typelevel#246
… handler Related to typelevel#246
Btw, I remembered when I had problem with exporting the |
I found new motivation to pursue ES6 modules: we can graduate from our janky initialization procedure by using a top-level |
I feel like I looked at that before, but is it possible to get Scala.js to emit a top-level await (or any await?). |
Oh, I forgot to link the issue I opened. |
Native ES modules are finally supported in all current LTS NodeJS versions 🎉. More and more Node libraries are moving to ES modules. ES modules can use a top-level import for CommonJS, but CommonJS cannot import ES modules with top-level imports. This means libraries still using CommonJS risk getting left behind in the Node ecosystem.
Feral currently uses a dynamic export update to export the handler function. From what I could see, this is done to load resources only once instead of every time the lambda is invoked. However, ES modules do not support dynamic export updates. All exports have to be there as
export
statements. The recommended way for the resource use-case is by using top-level awaits.To keep Node.js library interop and keep the library modern, it would be ideal for Feral to also support building as a ES Module. I'm not sure if it is possible to instruct Scala.JS to emit different code based on the module setting, or perhaps a different entrypoint should be available for ES Module users.
The text was updated successfully, but these errors were encountered: