Skip to content

Instantly share code, notes, and snippets.

@frank-dspeed
Last active April 14, 2022 09:37
Show Gist options
  • Save frank-dspeed/0b07fa485f23b4234670663eb12f8cd5 to your computer and use it in GitHub Desktop.
Save frank-dspeed/0b07fa485f23b4234670663eb12f8cd5 to your computer and use it in GitHub Desktop.
Example .cjs wrapper for real ESM .mjs code introduction pure modules aka Stealify Lang ECMAScript Module Standard

How to use ESM code in a CJS Project

This examples illustrate how to code a wrapper around 1 or more ESM Modules the wrapper gets written in CJS

exposing only namedExports avoid default export by design to make the shift to ESM more easy

also it exposes only Functions (methods) that do handle the import of the ESM dependencies.

also at the end there is a little introduction

const readFileESMmethodAsCJS = require('./wrapper.cjs');
const { readFile } = require('./wrapper.cjs'); // readFileESMmethodAsCJS;
readFile('./README.md').then(console.log);
readFileESMmethodAsCJS.readFile('./README.md').then(console.log);
// Wrapper that abstracts the module import via namedExports and methods
// Also usefull for typelinking if needed.for TypeScript projects with the .cts extension.
const wrappedModule = import('./wrapped.mjs')
const readFile = async (path) => (await wrappedModule).readFile(path);
exports["__esModule"] = true; // Marker Prop not needed but its clever to use it for transition till all is ESM
exports.readFile;
// This is the real ESM code that we want to Expose in Electron VSCODE or other situations where we need to use
// CJS as entrypoint or simply the example how to use ESM with CJS.
// Should be a ESM Only Module we only use this as example as it is well known
import { readFile } from 'node:fs/promises'
export { readFile }

why?

because many people have problems with the fact that they get a Promise for a Module in CJS and dynamic import always returns Promise so you create a CJS file that directly returns functions to abstract the fact that the module load is async you sync return the methods that later call the async module.

that keeps code change to a minimum. You can see the wrapper.cjs as replacement for transpilation when you work else with webpack or other bundlers before.

Usage Patterns

A good alternativ for a ESM Wrapper is a async IIFE

example.cjs

(async () => {
  const esModuleNamespace = await import('./wrapped.mjs');
  const { readFile } = await import('./wrapped.mjs');
  // your code here
  esModuleNamespace.readFile === readFile
})()

A even better Alternativ with Module Injection Support for testing

example.cjs

// Note we keep the syntax as expressiv for the CJS Lexer that gets used by NodeJS and else where
exports["__esModule"] = true;

const injectedModulesDefault = [
  import('./coolModule.mjs'),
  import('node:fs/promises')
];

exports.injectedModulesDefault = injectedModulesDefault;
const getMyCjsCode = (Modules=injectedModulesDefault) {
  Promise.all(Modules).then( nameSpaces => {
    const [module1CoolModule,module2FsPromises] = nameSpaces;
    // .. use the namedModules and methods everything you like.
  });
}

exports.getMyCjsCode = getMyCjsCode;
exports.myCjsCode = getMyCjsCode();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment