The makeESModule
function creates an ES module from a given code string by first checking for a cached module, then setting up a context object, creating a new ES module instance, caching and exporting the module, linking and exporting dependencies, and optionally logging the process.
npm run import -- "compile an es module"
const Module = require('module').Module
const path = require('path')
const vm = require('vm')
async function makeESModule(code, filename, context) {
const filepath = path.resolve(process.cwd(), filename);
const {CONSOLE} = require('../Core')
if (typeof Module._cache[filepath] != 'undefined') {
if (Module._cache[filepath].namespace.default) {
return Module._cache[filepath].namespace.default
}
return Module._cache[filepath].namespace
}
context = context || {}
Object.assign(context, {
importer: require('../Core'),
__dirname: path.dirname(filename),
__filename: filename.split('[')[0],
global: context,
console: CONSOLE,
})
const vmModule = new vm.SourceTextModule(code, {
identifier: filename,
context: await vm.createContext(context),
initializeImportMeta(meta) {
meta.url = filename
},
// @ts-expect-error: wrong type definition
async importModuleDynamically(specifier) {
return await import("resolveModuleSpecifier(specifier",
"dirname"))
}
})
context['__INTERNAL_IMPORTS_FROM_STRING'] = {}
Module._cache[filepath] = vmModule;
Module._cache[filepath].exports = {}
if (!filename.includes('Core')) {
CONSOLE.info(`compiling ES ${filename} with ${Object.keys(context)}`);
}
const linker = async (specifier) => {
const resolvedSpecifier = specifier //resolveModuleSpecifier(specifier, dirname)
const targetModule = await import("resolvedSpecifier")
context['__INTERNAL_IMPORTS_FROM_STRING'][specifier] = targetModule
const stringifiedSpecifier = JSON.stringify(specifier)
const exportedNames = Object.keys(targetModule)
const targetModuleContent = `${exportedNames.includes('default')
? `export default __INTERNAL_IMPORTS_FROM_STRING[${stringifiedSpecifier}].default;\n`
: ''
}export const { ${exportedNames
.filter(exportedName => exportedName !== 'default')
.join(', ')} } = __INTERNAL_IMPORTS_FROM_STRING[${stringifiedSpecifier}];`
return new vm.SourceTextModule(targetModuleContent, {
identifier: resolvedSpecifier,
context
})
}
await vmModule.link(linker)
await vmModule.evaluate()
Object.assign(Module._cache[filepath].exports, vmModule.namespace.default)
Module._cache[filepath].loaded = true
if (vmModule.namespace.default) {
return vmModule.namespace.default
}
return vmModule.namespace
}
module.exports.makeESModule = makeESModule
const path = require('path');
const vm = require('vm');
const { resolveModuleSpecifier, Core } = require('../Core');
class ESModuleCompiler {
constructor() {
this.console = Core.CONSOLE;
}
async makeESModule(code, filename, context = {}) {
const filepath = path.resolve(process.cwd(), filename);
// Check if module is already cached
if (Module._cache[filepath]) {
return Module._cache[filepath].namespace.default || Module._cache[filepath].namespace;
}
// Prepare context for execution
context = {...context, importer: Core, __dirname: path.dirname(filename), __filename: filename.split('[')[0], global: context, console: this.console };
// Create VM context and module
const vmModule = new vm.SourceTextModule(code, {
identifier: filename,
context: await vm.createContext(context),
initializeImportMeta(meta) {
meta.url = filename;
},
async importModuleDynamically(specifier) {
return await import(resolveModuleSpecifier(specifier, path.dirname(filename)));
}
});
// Cache module and prepare it for execution
Module._cache[filepath] = vmModule;
Module._cache[filepath].exports = {};
// Log compilation info
if (!filename.includes('Core')) {
this.console.info(`Compiling ES ${filename} with ${Object.keys(context)}`);
}
// Define linker function for importing modules
const linker = async (specifier) => {
// Resolve and import module
const resolvedSpecifier = specifier;
const targetModule = await import(resolveModuleSpecifier(specifier, path.dirname(filename)));
context['__INTERNAL_IMPORTS_FROM_STRING'][specifier] = targetModule;
// Create stringified specifier and exported names
const stringifiedSpecifier = JSON.stringify(specifier);
const exportedNames = Object.keys(targetModule);
const targetModuleContent = `${exportedNames.includes('default')
? `export default __INTERNAL_IMPORTS_FROM_STRING[${stringifiedSpecifier}].default;\n`
: ''
}export const { ${exportedNames
.filter(exportedName => exportedName!== 'default')
.join(', ')} } = __INTERNAL_IMPORTS_FROM_STRING[${stringifiedSpecifier}];`;
// Create new VM module for imported module
return new vm.SourceTextModule(targetModuleContent, {
identifier: resolvedSpecifier,
context
});
};
// Link and evaluate module
await vmModule.link(linker);
await vmModule.evaluate();
// Assign exports and mark module as loaded
Object.assign(Module._cache[filepath].exports, vmModule.namespace.default);
Module._cache[filepath].loaded = true;
// Return module
return vmModule.namespace.default || vmModule.namespace;
}
}
module.exports = new ESModuleCompiler();
Function Breakdown: makeESModule
The makeESModule
function is an asynchronous function that creates an ES module from a given code string. Here's a step-by-step breakdown of what it does:
The function first checks if a module with the given filepath
already exists in the Module._cache
cache. If it does, it returns the cached module.
If the module doesn't exist in the cache, the function sets up a context object with the following properties:
importer
: an instance of require('../Core')
__dirname
: the directory of the current module__filename
: the name of the current module (with the brackets removed)global
: the context object itselfconsole
: an instance of CONSOLE
(imported from ../Core
)The function creates a new vm.SourceTextModule
instance from the given code
string. It passes the following options to the constructor:
identifier
: the filename
of the modulecontext
: the context object created in step 2initializeImportMeta
: a function that sets the url
property of the import metadata to the filename
importModuleDynamically
: a function that imports modules dynamically using the resolveModuleSpecifier
functionThe function adds the created module to the Module._cache
cache and sets its exports
property to an empty object.
The function defines a linker
function that takes a specifier
as an argument. It resolves the specifier, imports the module, and adds it to the cache. It then stringifies the specifier and exports its names using the export const {... }
syntax.
If the filename
doesn't include the string "Core", the function logs a message indicating that it's compiling the ES module with the given context.