Turns out, FFI only works with ECMAScript js, which most js in the wild is not.
I’m trying to create binding to makeSortable function of sorttable small library, which is distributed as a single JS file. My naive implementation of just appending an export const makeSortable = … to the end of the file fails with various … cannot be used in an ECMAScript module errors.
My second approach was creating an ECMAScript FFI.js and importing from it the plain js. You can see it below in “steps-to-reproduce” and it results in not seeing the file. Presumably I could change path to point to output/, which would probably be confusing to other programmers and lsp-servers.
I feel though I’m doing something completely wrong. How do you implement that?
Fix encoding of ./src/3rdparty/sorttable.js(it’s a known bug I reported today to the author: there are letters that ISO-8859 doesn’t handle)
Create 3 files:
src/Main.purs:
module Main where
import Prelude
import Effect (Effect)
import FFI (makeSortable)
import Undefined (undefined)
main :: Effect Unit
main = makeSortable undefined
src/FFI.purs:
module FFI where
import Prelude
import Effect (Effect)
import Web.DOM.Node (Node)
foreign import makeSortable :: Node -> Effect Unit
The bundler will bundle files from the output dir, so your FFI modules refs should be relative to output’s location. I would propose to use some path mappings for such deps to get rid of relative paths.
I see, so one solution is replacing import "./3rdparty/sorttable.js"; with import "../../src/3rdparty/sorttable.js";. Seems to work for spago bundle-app.
However, if I execute spago bundle-module --main Main --to /dev/null I for some reason get:
✘ [ERROR] Delete of a bare identifier cannot be used with the "esm" output format due to strict mode
src/3rdparty/sorttable.js:74:13:
74 │ delete sortbottomrows;
╵ ~~~~~~~~~~~~~~
✘ [ERROR] Delete of a bare identifier cannot be used with the "esm" output format due to strict mode
src/3rdparty/sorttable.js:160:16:
160 │ delete row_array;
╵ ~~~~~~~~~
✘ [ERROR] Delete of a bare identifier cannot be used with the "esm" output format due to strict mode
src/3rdparty/sorttable.js:255:11:
255 │ delete newrows;
╵ ~~~~~~~
I’m not familiar with front-end stuff. I tried doing it and found that it’s possible, even though it doesn’t look very elegant.
I didnot. test it, but it “spago bundle-app” succeed.
It seems to complete successfully due to a bug in PureScript compiler…
You see, I just experimented, and this way you can actually write anything you like as a .js file, and it will always complete successfully. purs just doesn’t parse the file.
If you open the index.js file that bundle-app or bundle-module produced this way, you’ll find the .js code you were referring to is missing, so yeah, if you’d try actually running an app assembled this way it wouldn’t work.
Now, the bundler is clearly the second one. But for what purpose purs is launched? purs --help doesn’t make it any clear what “graph” does, but knowing how compilers usually work I’d presume this creates some file that shows which files depend upon what, and then this file would be parsed by esbuild. And then, the bug may be in the file, in which case it is a purs’ problem.
The problem though, when I try to run the purs command manually (with double quotes put around the paths of course, but also tried without), I get 236 errors! So I don’t really understand how come the same command works fine from spago
The 236 errors are of style Module Prelude was not found.
Yeah, so basically as I said. This implies the bug is in purs not parsing the .js file as a dependency in Jintao’s code above, so esbuild later does not receive it.
Purs embedded js backend is not supposed to parse/transform JS in the manner you are asking. Your imports in JS foreign modules should be ready for bundling.
The compiler isn’t saying that files from imports can’t be found, instead it simply produces the wrong graph output.
So there is a bug, it’s just, whether it is in the inability to evaluate import correctly or not bailing out upon finding such import — it’s something for purs devs to decide.
Do you mean import in JS file? Purs doesn’t and should not care.
You are getting Module Prelude was not found because your purs graph command is not complete, it should contain all the globs for all packages, your purs graph .spago/aff/v8.0.0/src/**/*.purs... posted here does not contain all globs probably due to some output limitations.
Okay, so, wait, I’m confused… Given a command spago bundle-app --no-build, two commands get run: purs graph and esbuild. Later one I understand what does: the bundling. But why purs graph runs?
So, just to clarify how I see it and why I’m confused: spago is a build system and all it does is passing files to either purs or esbuild. I re-read your comments, and you’re sayingpurs graph has nothing to do with esbuild, it doesn’t create deps chain to be used by esbuild, esbuild instead handles everything on its own. Okay, but then for what purpose purs graph gets run in this command?
Okay, so, back to the original question… I just got a crazy workaround idea: how about bundling the library only with bundle-app but not with bundle-module? The way I see this, the “bundle-app” is the initial code that always loads disregarding what page a user opens. So the JS library may be bundled there; and then when a page/module gets loaded, it will use the JS code that’s already loaded from bundle-app!
Is it possible to implement with Spago? Or even more so, is it even a viable idea (because “bundle-module” uses ESM format, and I read this documentation bit as that potentially global variables may be invisible to a module, and Idk how JS/PS works on the lower-level here)?
Yeah, that part I get, it was your first comment in the thread and I tested it in the next one
Why? What I’m currently doing works fine for me, barring the FFI question (unless you’re implying that migrating to such bundler would somehow resolve the FFI problem…?)
Oh, it’s simple. It’s described in more detail in this SO question, but in short: I don’t want to have a humongous SPA (because all good websites/apps serve exactly the page a user asked for and not everything), and additionally I want to implement server-side rendering at some point. IOW, I need to serve a user the page it’s asked for; and people on SO say it has to be done with spago bundle-module.
So that’s what I’m doing: I use bundle-module to generate each page, and I use bundle-app(might probably use bundle-module too ) to generate the “navigation panel”, which uses a <script> to load the actual page the user wanted to fetch.