Question Authors' Forum

nodejs with ESM rather than CJS

nodejs with ESM rather than CJS

by Peter Sander -
Number of replies: 5

Hello All,

Anyone tried using the newer ES Modules syntax for nodejs code (using export / import) as opposed to Common JavaScript (using module.exports / require)?

For what it's worth, a simple test results in

***Error***
(node:4531) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
__tester__.nodejs:2
import done from './cli.mjs');
^^^^^^ SyntaxError: Cannot use import statement outside a module
at wrapSafe (internal/modules/cjs/loader.js:931:16)
at Module._compile (internal/modules/cjs/loader.js:979:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1035:10)
at Module.load (internal/modules/cjs/loader.js:879:32)
at Function.Module._load (internal/modules/cjs/loader.js:724:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
at internal/main/run_main_module.js:17:47
Perhaps it would work with a new prototype question, nodejs_esm say, with main_module.js named main_module.mjs instead?

Just trying to keep up...

In reply to Peter Sander

Re: nodejs with ESM rather than CJS

by Peter Sander -

More details on reproducing the issue (I do realize that nodejs is a fringe language for CR, but ESM style is getting traction)...

Question type: nodejs

Answer and Answer box preload: 

import something from './something_esm.js';

Support files:

something_esm.js containing:

export default () => console.log('Running something');

package.json containing:

{
"type" : "module"
}

Then Check produces Got with:

***Error***
node:internal/process/esm_loader:74
internalBinding('errors').triggerUncaughtException(
^ TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".nodejs" for __tester__.nodejs
at new NodeError (node:internal/errors:278:15)
at Loader.defaultGetFormat [as _getFormat] (node:internal/modules/esm/get_format:71:15)
at Loader.getFormat (node:internal/modules/esm/loader:102:42)
at Loader.getModuleJob (node:internal/modules/esm/loader:231:31)
at async Loader.import (node:internal/modules/esm/loader:165:17)
at async Object.loadESM (node:internal/process/esm_loader:68:5) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
Needless  to say that it works as expected running on the command line:

node esm_test.js 
Running something

where esm_test.js contains the Answer code.




In reply to Peter Sander

Re: nodejs with ESM rather than CJS

by Richard Lobb -

By default CodeRunner uses the filename __tester__.<language> but in the case where the language is nodejs (which is the name Jobe gives its JavaScript language) the name __tester__.nodejs seems to break when in ESM style.

The fix is to set the (undocumented) sandbox parameter "sourcefilename" to use an extension of .js rather than .nodejs. If you have access to the nodejs prototype, edit it, open the Advanced Customisation panel, and set the Parameters field in the Sandbox section to

{"sourcefilename": "__tester__.js"}

I'll make this change to the prototype in the next release of CodeRunner.

If you don't have access to the prototype, you'll either have to define your own or customise each question individually.

Thanks for reporting the problem.

In reply to Richard Lobb

Re: nodejs with ESM rather than CJS

by Peter Sander -

Thanks for the speedy answer!

I tried the cheapest user-level customization i could think of by setting

Template params to

{"sourcefilename": "__tester__.js"}

Running with Template debugging i see that 

{{ QUESTION.parameters.sourcefilename }}

is set to 

__tester__.js

Looks like that was too cheap - it still crashes with 

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".nodejs" for __tester__.nodejs
    at new NodeError (node:internal/errors:278:15)

So i guess i'll be asking the admins to patch the prototype.


In reply to Peter Sander

Re: nodejs with ESM rather than CJS

by Richard Lobb -

You can do it as an ordinary user on a per-question basis as follows:

  1. Click the Customise Checkbox (at the very top, just under the Question type selector).
  2. Open the Advanced Customisation section.
  3. Set the Sandbox Parameters field as in the figure below.
You can't do it via the usual Template parameters field, though.

However, customising individual questions isn't recommended as a general purpose fix, because future updates to the prototype will then not affect the customised questions.

Getting an admin to patch the nodejs prototype as above is probably the easiest solution. Or make yourself a new question prototype called just 'js' rather than 'nodejs'? It's not too hard - see https://github.com/trampgeek/moodle-qtype_coderunner#user-defined-question-types. But you will then have to manage the new prototype in the future.

Screenshot of advanced customisation panel

In reply to Richard Lobb

Re: nodejs with ESM rather than CJS

by Peter Sander -

Yes of course, Sandbox and not Template parameters :-) . Works like a charm. The admins will take a couple of days to make the change and i need just a couple of questions asap so this temporary quick fix is perfect.

Thanks a bunch for bailing me out again!

Cheers,

PS.