PureScript 0.15 + spago + Affjax

Hi,

I installed PureScript 0.15.2 and it works so far for me unless I use Affjax and do bundle the code for node.

This code works with spago run

main :: Effect Unit
main = launchAff_ do
  result <- get json "http://www.thecocktaildb.com/api/json/v1/1/random.php"
  case result of
    Left err -> liftEffect $ log $ printError err
    Right r -> liftEffect $ log $ show $ stringify <$> decodeJson r.body

but shows the following error if I spago bundle-app --platform node and run it using node .

file:///Users/mwu/tmp/affjax-test/index.js:12
  throw new Error('Dynamic require of "' + x + '" is not supported');
        ^

Error: Dynamic require of "http" is not supported
    at file:///Users/mwu/tmp/affjax-test/index.js:12:9
    at Object.<anonymous> (file:///Users/mwu/tmp/affjax-test/index.js:88:14)
    at node_modules/xhr2/lib/xhr2.js (file:///Users/mwu/tmp/affjax-test/index.js:763:8)
    at __require2 (file:///Users/mwu/tmp/affjax-test/index.js:15:51)
    at file:///Users/mwu/tmp/affjax-test/index.js:4095:27
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:385:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12)

Could you please tell me if I miss something here and how to fix it?

I use purs 0.15.2, spago 0.20.9, esbuild 0.14.42, and node 16.15.1.

Kind regards,
Markus

Can you post the full file so it’s easy to reproduce this error?

I tried to recreate this and did end up with exactly your error and I think there is already an issue for this.

To get it running you can do:

esbuild ./output/Main/index.js --bundle --platform=node > index.js

this should compile your Main into a common-js module. In order for node . to work you’ll have to add

"type": "commonjs"

to your package.json and then sadly you’ll have to add this line:

main();

to the end of the generated index.js file.

With these node . should run your code.


Alternatively you could add a file (say run.js) with this content:

import { main } from './output/Main/index.js'

main()

and then do

esbuild run.js --bundle --platform=node > index.js

which will save you the trouble of manually editing the generated file.


For reference: Here is my Main.purs:

module Main where

import Prelude

import Affjax.Node (get, printError)
import Affjax.ResponseFormat (json)
import Data.Argonaut.Core (stringify)
import Data.Argonaut.Decode (decodeJson)
import Data.Either (Either(..))
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class.Console (log, logShow)

main :: Effect Unit
main = launchAff_ do
  log "Test"
  result <- get json "https://www.google.de"
  case result of
    Left err -> log $ printError err
    Right r -> logShow $ stringify <$> decodeJson r.body

and this is the spago.dhall for it:

{ name = "my-project"
, dependencies =
  [ "aff"
  , "affjax"
  , "affjax-node"
  , "argonaut-codecs"
  , "argonaut-core"
  , "console"
  , "effect"
  , "either"
  , "prelude"
  ]
, packages = ./packages.dhall
, sources = [ "src/**/*.purs" ]
}
1 Like

I just commented in the issue as well.
So it seems that the xhr2 dependency for node is not on ES modules but still on CommonJS. Apparently esbuild does not do a good job on bundling in mixed mode (i.e. outdated CJS with modern ESM), which to be fair is also not really something I would waste my time on if I were the developer of esbuild.

So you got a couple of options to fix this, with decreasing priority:

  1. Use the more modern fetch api instead of the old affjax. Implementations of the fetch api are e.g. yoga-fetch, see the tests for examples.
    As a side note: fetch has recently been integrated into node, so from 17.5 you don’t need an external dependency anymore (currently working on getting that into yoga-fetch).
  2. Update xhr2 to ES modules (see migration guide).
  3. Do not bundle: On node you actually don’t need to bundle, and node supports a mixed mode of ESM and CJS, so it should work. All you need is an index.js with the following content:
import { main } from "./output/Main/index.js"
main()

and run it with node: node index.js
4. Use webpack or parcel to bundle mixed mode apps. In my experience webpack did a better job on bundling mixed mode apps than esbuild

1 Like

esbuild bundles the mixed modules fine for me (producing by default common-js or so it seems) - can you give me a hint of how spago calls it? Because esbuild ./output/Main/index.js --bundle --platform=node seems to work and I don’t really understand where the strange require-check is coming from)

Ok I think I found it - when I ask spago for --verbose I can see this command:

[debug] Running command: `[pathToEsbuild] --platform=node --format=esm --bundle --outfile=test.js`

Seems spago is asking for ESM

Ah right that makes sense that the esbuild fails because we are specifically asking for ESM. I guess the esbuild error message is a bit misleading in that case.
In any case, imho bundling in mixed mode for CJS is not the way to go but rather one should update the dependencies to ESM or in this case upgrade to fetch.

sure if it’s possible - personally I like to have fallbacks in case I don’t want to wait for external libs to update or don’t want to invest the time to make temporary fix/fork.

I guess affjax will move to fetch but there might be the issue on availability right?

If you have an app then this is ok, but for a library you really should upgrade to ESM.

Not really if you are on a node version <17.5 then you just need node-fetch as an external dependency. The current minimum node version supported by spago/purescript is 12 (which I guess will be changed to 14 in the next release since node 12 reached EOL) and node-fetch works on these versions.
Also note that node-fetch is now ESM only.

1 Like

thanks did not know about node-fetch (to be honest: I’m not really into node :wink: )

Thank you very much for the quick and comprehensive explaination. Without the help of the excellent PureScript tools and libs (many thanks for this work as well) I feel a bit lost in the JavaScript world. I will try out yoga-fetch now.