Repository: 11ty/eleventy
Pull request #3377 (2024-07-23 17:56:31)

Add --loader flag to force ESM/CJS mode
Resolves https://github.com/11ty/eleventy/issues/3376.

This PR adds a `loader` option for the `Eleventy` class and a `--loader` command line flag. The value must be either `"esm"` (to force ESM mode), `"cjs"` (to force CommonJS mode) or `"auto"` (default, infer it from `package.json`; the current behavior).

In particular this allows other runtimes to run Eleventy without requiring a `package.json`.


Patch:
diff --git a/cmd.cjs b/cmd.cjs
index f047773e..b51e5928 100755
--- a/cmd.cjs
+++ b/cmd.cjs
@@ -31,7 +31,7 @@ const debug = require("debug")("Eleventy:cmd");
 
 	try {
 		const argv = minimist(process.argv.slice(2), {
-			string: ["input", "output", "formats", "config", "pathprefix", "port", "to", "incremental"],
+			string: ["input", "output", "formats", "config", "pathprefix", "port", "to", "incremental", "loader"],
 			boolean: [
 				"quiet",
 				"version",
@@ -84,6 +84,7 @@ const debug = require("debug")("Eleventy:cmd");
 			pathPrefix: argv.pathprefix,
 			runMode: argv.serve ? "serve" : argv.watch ? "watch" : "build",
 			dryRun: argv.dryrun,
+			loader: argv.loader,
 		});
 
 		// reuse ErrorHandler instance in Eleventy
diff --git a/src/Eleventy.js b/src/Eleventy.js
index ebd64184..e700bad8 100644
--- a/src/Eleventy.js
+++ b/src/Eleventy.js
@@ -170,6 +170,12 @@ class Eleventy {
 		 */
 		this.buildCount = 0;
 
+		/**
+		 * @member {String} - Force ESM or CJS mode instead of detecting from package.json. Either cjs, esm, or auto.
+		 * @default "auto"
+		 */
+		this.loader = this.options.loader ?? "auto";
+
 		/**
 		 * @type {Number}
 		 * @description The timestamp of Eleventy start.
@@ -796,6 +802,9 @@ Arguments:
      --dryrun
        Don’t write any files. Useful in DEBUG mode, for example: \`DEBUG=Eleventy* npx @11ty/eleventy --dryrun\`
 
+     --loader
+       Set to "esm" to force ESM mode, "cjs" to force CommonJS mode, or "auto" (default) to infer it from package.json.
+
      --to=json
      --to=ndjson
        Change the output to JSON or NDJSON (default: \`fs\`)
@@ -1019,15 +1028,23 @@ Arguments:
 	}
 
 	get isEsm() {
-		if (this.#isEsm === undefined) {
+		if (this.#isEsm !== undefined) {
+			return this.#isEsm;
+		}
+		if (this.loader == "esm") {
+			this.#isEsm = true;
+		} else if (this.loader == "cjs") {
+			this.#isEsm = false;
+		} else if (this.loader == "auto") {
 			try {
 				this.#isEsm = this.projectPackageJson?.type === "module";
 			} catch (e) {
 				debug("Could not find a project package.json for project’s ES Modules check: %O", e);
 				this.#isEsm = false;
 			}
+		} else {
+			throw new Error("The 'loader' option must be one of 'esm', 'cjs', or 'auto'");
 		}
-
 		return this.#isEsm;
 	}
 


Test patch:
diff --git a/test/EleventyTest.js b/test/EleventyTest.js
index 6fec34aa..28d0d837 100644
--- a/test/EleventyTest.js
+++ b/test/EleventyTest.js
@@ -1571,3 +1571,19 @@ test("#3373: Throw an error when explicit config path is not found.", async (t)
   let e = await t.throwsAsync(() => elev.toJSON());
   t.is(e.message, "A configuration file was specified but not found: this-file-is-not-found.js");
 });
+
+test("Eleventy loader can force ESM mode", async (t) => {
+  let elev = new Eleventy("./README.md", "./_site", {
+    loader: "esm",
+  });
+
+  t.is(elev.isEsm, true);
+});
+
+test("Eleventy loader can force CommonJS mode", async (t) => {
+  let elev = new Eleventy("./README.md", "./_site", {
+    loader: "cjs",
+  });
+
+  t.is(elev.isEsm, false);
+});