Support modern ESM imports in Node for Max

attila's icon

attila

1月 09 2023 | 2:58 午後

Hi all, I'm working on a project involving Node for Max and struggling to get a basic non-trivial patch running.

Correct me if I'm wrong, but even with a custom @node_bin_path Node binary specified it seems like the node.script node process manager object in Max is hardcoded to import user scripts using the outdated CommonJS `require('xyz')` syntax.

In `/Applications/Max.app/Contents/Resources/C74/packages/Node for Max/source/lib/nsRunner.js`:

// Kick off things and load user script
require(process.env.SCRIPT_PATH);
loadedSuccessfully = true;

The README states that Node v16.6.0 is used internally. In Node.js v12.17.0 and above it's possible to use dynamic imports. A compromise could be:

// if package.json nearest to the user script has type: 'module'
// or the script file ends with .mjs
import(process.env.SCRIPT_PATH).then(() => {
loadedSuccessfully = true;
})

or something less hacky.

These days, a number of package dependencies are being published in ESM-only formats. The broader implication is that it's not always feasible to convert an entire script to CommonJS. A modern Node project often comes with additional complexity, especially if there's Typescript transpilation or bundling involved. Often there are packages involved with add-on Node-API binaries (`.node`) that cannot be bundled and need to be marked as runtime externals (in my current case, one that's adding WebRTC support for Node).

Is there a currently preferred workaround or consideration for this? It seems like I would have to resort to some kind of a Max for Node proxy to exchange messages with a locally running, completely separate node process.

attila's icon

attila

1月 09 2023 | 7:33 午後

To save some hours in case anyone finds this thread — I was able to get a typescript + ESM project running in Max via esbuild bundling to commonjs and an unexpected hack to mix import/require. The latter is required for interaction with the Max API.

// build script
esbuild index.ts --platform=node --bundle '--external:max-api' --format=cjs --outfile=./index.js

// index.ts
import esmOnlyDefault from 'esm-only-package'
import commonJsDefaultViaEsm from 'cjs-only-package' // ES Modules imports are static
const maxAPI = require('max-api') // CommonJS imports are dynamically resolved at runtime

loadmess's icon

loadmess

2月 14 2023 | 9:39 午前

Hi!
Thank you digging and sharing this solution with the community.
I'm interested in this but I confess I don't have enough flexibility to understand your approach.
Could you possibly add more information on how to implement this?
I'm trying to test this library:
https://www.npmjs.com/package/hdsp2

Thank you

Florian Demmer's icon

Florian Demmer

2月 14 2023 | 11:09 午前

Thanks for pointing this out and providing a workaround. It seems to in fact point towards a limitation in the way we are currently loading the process shell on the Node / JS side.

I’ve filed a ticket and we’ll be looking into this.

Thanks
Florian

Misemao's icon

Misemao

2月 22 2023 | 3:53 午後

Is the best way to get notified about the state of the support of modern esm imports subscribing to this thread?
I have a bunch of projects that I could finally integrate fully in max without having to use external scripts sending OSC to max once esm imports are possible.

josh ball's icon

josh ball

12月 14 2023 | 3:46 午後

Also very interested in this functionality if it hasn't already been handled.