The code imports Node.js modules and defines two functions: getCached
, which returns a cached module if it exists and is up-to-date, and makeModule
, which creates and caches a new module object.
npm run import -- "how does node module require work"
const Module = require('module').Module
const path = require('path')
const fs = require('fs')
function getCached(filepath) {
// must have a new name for every generation otherwise cache will be returned
var mtime = fs.statSync(filepath
// TODO: find a better way to serve notebook names
.replace(/\.ipynb(\[[0-9]+\]).*$/ig, '.ipynb')).mtime.getTime();
// TODO: don't use cache of parent modules that have includes that have changed
var cachedModule = Module._cache[filepath];
if (cachedModule && mtime < cachedModule.buildTime) {
return cachedModule;
}
}
function makeModule(code, pathToCode, ctxGlobal) {
const {CONSOLE} = require('../Core')
var filepath = path.resolve(process.cwd(), pathToCode);
ctxGlobal = ctxGlobal || {}
ctxGlobal.module = getCached(filepath)
if (ctxGlobal.module) {
if (!ctxGlobal.module.loaded) {
return ((module) => new Promise(resolve => {
let inter = setInterval(() => {
if (module.loaded) {
clearInterval(inter)
resolve()
}
}, 100)
}).then(() => module.exports))(ctxGlobal.module)
}
return ctxGlobal.module.exports
}
delete require.cache[filepath]
Object.assign(ctxGlobal, {
importer: require('../Core'),
module: new Module(filepath, module),
exports: { original: true },
__dirname: path.dirname(pathToCode),
__filename: pathToCode.split('[')[0],
$: void 0,
console: CONSOLE,
global: ctxGlobal,
//require: require,
})
Object.assign(ctxGlobal.module, {
module: ctxGlobal.module,
exports: ctxGlobal.exports,
parent: module,
buildTime: (new Date()).getTime(),
filename: pathToCode,
paths: Module._nodeModulePaths(path.dirname(filepath))
})
Module._cache[filepath] = ctxGlobal.module;
// this is basically the magic of the 'rewire' module, reuse this?
const validVars = Object.keys(ctxGlobal)
.filter(k => k.match(/^(\$\$|[a-z_][a-z0-9_]*)$/i))
.join(',');
if (!pathToCode.includes('Core')) {
CONSOLE.info(`compiling ${pathToCode} with ${validVars}`);
}
// TODO: use the transpiler or kernels here
try {
ctxGlobal.module._compile(`
module.exports = (({${validVars}}) => {
${code}
return module.exports || exports || {}
})`, filepath);
// assign the actual imports internally by calling the func export
var wrapperContext = ctxGlobal.module.exports;
var result = wrapperContext(ctxGlobal);
ctxGlobal.module.loaded = true;
ctxGlobal.module.exports = result;
return result;
} catch (err) {
if (err.code === 'ERR_REQUIRE_ESM') {
const { makeESModule } = require('../Core')
CONSOLE.error('ES module required', err)
return makeESModule(code, filepath, ctxGlobal)
} else {
throw err
}
}
}
module.exports.makeModule = makeModule;
const { Module } = require('module');
const path = require('path');
const fs = require('fs');
const CONSOLE = require('../Core').CONSOLE;
class ModuleGenerator {
constructor() {
this.cache = {};
this.cacheTimers = {};
}
async getCached(filepath) {
const filename = path.basename(filepath);
const mtime = fs.statSync(filepath).mtime.getTime();
const cachedModule = this.cache[filepath];
if (cachedModule && mtime < cachedModule.buildTime) {
return cachedModule;
}
return null;
}
async makeModule(code, pathToCode, ctxGlobal) {
try {
ctxGlobal = ctxGlobal || {};
ctxGlobal.module = await this.getCached(pathToCode);
if (ctxGlobal.module) {
if (!ctxGlobal.module.loaded) {
await this.waitForModuleLoad(ctxGlobal.module);
}
return ctxGlobal.module.exports;
}
delete require.cache[pathToCode];
const module = new Module(pathToCode, module);
Object.assign(ctxGlobal, {
importer: require('../Core'),
module,
exports: { original: true },
__dirname: path.dirname(pathToCode),
__filename: path.basename(pathToCode),
$: void 0,
console: CONSOLE,
global: ctxGlobal,
});
Object.assign(module, {
module,
exports: ctxGlobal.exports,
parent: module,
buildTime: Date.now(),
filename: pathToCode,
paths: Module._nodeModulePaths(path.dirname(pathToCode)),
});
this.cache[pathToCode] = module;
const validVars = Object.keys(ctxGlobal)
.filter((k) => k.match(/^(\$\$|[a-z_][a-z0-9_]*)$/i))
.join(',');
if (!pathToCode.includes('Core')) {
CONSOLE.info(`compiling ${pathToCode} with ${validVars}`);
}
module._compile(`
module.exports = (({${validVars}}) => {
${code}
return module.exports || exports || {}
})`, pathToCode);
const wrapperContext = module.exports;
const result = await wrapperContext(ctxGlobal);
module.loaded = true;
module.exports = result;
return result;
} catch (err) {
if (err.code === 'ERR_REQUIRE_ESM') {
const { makeESModule } = require('../Core');
CONSOLE.error('ES module required', err);
return makeESModule(code, pathToCode, ctxGlobal);
} else {
throw err;
}
}
}
async waitForModuleLoad(module) {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (module.loaded) {
clearInterval(interval);
resolve();
}
}, 100);
});
}
}
module.exports.makeModule = async (code, pathToCode, ctxGlobal) => {
const generator = new ModuleGenerator();
return generator.makeModule(code, pathToCode, ctxGlobal);
};
Breakdown of the Code
The code starts by importing the following modules:
const Module = require('module').Module
const path = require('path')
const fs = require('fs')
Module
is imported from the Node.js module
module, and Module
is accessed directly to use its methods.path
is a built-in Node.js module for working with file paths.fs
is a built-in Node.js module for interacting with the file system.getCached
FunctionThe getCached
function takes a filepath
as an argument and returns a cached module if it exists and is up-to-date:
function getCached(filepath) {
//...
}
The function checks if the file at filepath
has changed by comparing its modification time with the cached module's build time. If the cached module is up-to-date, it is returned.
makeModule
FunctionThe makeModule
function takes three arguments:
code
: the code to executepathToCode
: the path to the code filectxGlobal
: the global context objectThe function returns a module object that can be executed:
function makeModule(code, pathToCode, ctxGlobal) {
//...
}
Here's a high-level overview of what the function does:
filepath
using path.resolve
.filepath
. If it does, it returns the cached module or its exports if the module is already loaded.Module
class from Node.js.imports
, exports
, and filename
.Note that the function uses Object.assign
to copy properties from one object to another, which can be done in a more modern way using object destructuring and the spread operator ({...obj}
).
The code also uses some Node.js-specific features, such as:
require.cache
to clear the cache for a specific file.Module._cache
to access the cache of loaded modules.Module._nodeModulePaths
to get the paths of node modules.Overall, the code appears to be implementing a custom module loading mechanism that uses caching to improve performance.