0

Modules generally start something like this

(function(root, factory)
{
    /* globals define */
    if (typeof define === 'function' && define.amd)
    {
        // AMD. Register as an anonymous module.
        define([], factory);
    }
    else if (typeof module === 'object' && typeof exports !== 'undefined')
    {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    }
    else
    {
        // Browser globals (root is window)
        root.Papa = factory();
    }

I'm trying to implement require to handle node style CommonJS-like modules.

Find the package folder, parse package.json to learn the entrypoint and dependencies, recursively descent with a shared cache to load dependencies... that stuff works.

But having loaded the script for a module, I'm having trouble executing it in such a way as to have it populate module.exports

This will all end up running on Jint (a JS interpreter) so node isn't supplying the usual furniture, I have to build it all myself. There's no step-debug with Jint so I'm using node from VS Code and faking Jint like this.

import * as fs from "fs";

var code = fs.readFileSync("node_modules/papaparse/papaparse.js").toString();
let x = 3;
console.log(eval("x*x"))
let result = eval(`
let module = { exports: "dunno" };
${code}
console.log(module.exports);
`);

This is in a file test.js and package.json nominates this file as main and specifies a type of module. It launches, reads the module code, creates module and runs the code, which promptly complains that it Cannot set properties of undefined (setting 'Papa').

Looking at the snippet above, that tells us it's executing the last else clause, which means it's not seeing module. I thought it might be some sort of scope thing for eval which is where this came from

let x = 3;
console.log(eval("x*x"))

but that duly writes 9 to the console so the question is why module isn't in scope.

This is one of those JavaScript closure weirdnesses; can anyone guide me on how to provide the module object so that the second clause will take effect populating module.exports with the result of factory()?

I know I said it's running in the absence of Node, but I'm debugging it using Node mostly because that's what you get when you launch a js file in VS Code. As already mentioned the production target is Jint. The debug environment is as close an approximation as I can manage, and I'm refraining from using facilities that won't be available in production.

Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • look up browserify, it's the oldest (and simplest) tool for converting node style commonjs for the browser – coagmano Dec 08 '21 at 03:19
  • Have you looked at how node.js implements modules? It's quite simple code. – Bergi Dec 08 '21 at 03:22
  • "*it's executing the last else clause, which means it's not seeing `module`.*" - no, `module` is defined just fine. The trouble is `&& typeof exports !== 'undefined'`. – Bergi Dec 08 '21 at 03:23
  • @Bergi yes I see that now, it examines `exports` not `module.exports`. I haven't looked at the source code for node.js - where would I find that? – Peter Wone Dec 08 '21 at 04:04
  • @PeterWone Have a look at [this explanation](https://stackoverflow.com/a/28955050/1048572) - not sure the links are up-to-date but you still should be able the find those files in the node repository – Bergi Dec 08 '21 at 12:23
  • @Bergi - again you have my thanks. I already found the files by rummaging in my node installation, but that link is extremely helpful because it adds a narrative about how the whole thing fits together -- something I feel is lacking from most modern documentation. – Peter Wone Dec 08 '21 at 22:12

1 Answers1

0

In the debug environment I've created a package.json file that governs whether it's treated as CommonJS or ES6. Experiments show that the production environment behaves like more ES6 than commonjs. The trouble is that most packages expect that they will run either in a browser or in node. They don't consider the possibility of another headless environment. The code above decides it's browser hosted and tries to to install Papa in a DOM that isn't there.

The solution is to wrap the module like so (I'm constructing a string in C#).

          string wrapped = 
@"(function () { 
    var module = { exports: { } }; " + 
    jsSource + @"
    var strays = Object.keys(this).filter(itemName => itemName !== 'module'); 
    if (strays.length === 1)
      module.exports = this[strays[0]] 
    else
      strays.forEach(itemName => module.exports[itemName] = this[itemName]);
    return module.exports;
}).apply({});";

Wrapping it in an anonymous function and using apply to set this for the call allows the "DOM write" to succeed, and a little follow-up code looks for these strays, gathering them up into module exports before returning to normal operation.

Peter Wone
  • 17,965
  • 12
  • 82
  • 134