summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/Util/Require.js
diff options
context:
space:
mode:
authorShipwreckt <me@shipwreckt.co.uk>2025-10-31 20:02:14 +0000
committerShipwreckt <me@shipwreckt.co.uk>2025-10-31 20:02:14 +0000
commit7a52ddeba2a68388b544f529d2d92104420f77b0 (patch)
tree15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/@11ty/eleventy/src/Util/Require.js
parent53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff)
Changed from static to 11ty!
Diffstat (limited to 'node_modules/@11ty/eleventy/src/Util/Require.js')
-rw-r--r--node_modules/@11ty/eleventy/src/Util/Require.js258
1 files changed, 258 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/Util/Require.js b/node_modules/@11ty/eleventy/src/Util/Require.js
new file mode 100644
index 0000000..5f6412d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Util/Require.js
@@ -0,0 +1,258 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import module from "node:module";
+import { MessageChannel } from "node:worker_threads";
+
+import { TemplatePath } from "@11ty/eleventy-utils";
+
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+import eventBus from "../EventBus.js";
+
+class EleventyImportError extends EleventyBaseError {}
+
+const { port1, port2 } = new MessageChannel();
+
+// ESM Cache Buster is an enhancement that works in Node 18.19+
+// https://nodejs.org/docs/latest/api/module.html#moduleregisterspecifier-parenturl-options
+// Fixes https://github.com/11ty/eleventy/issues/3270
+// ENV variable for https://github.com/11ty/eleventy/issues/3371
+if ("register" in module && !process?.env?.ELEVENTY_SKIP_ESM_RESOLVER) {
+ module.register("./EsmResolver.js", import.meta.url, {
+ parentURL: import.meta.url,
+ data: {
+ port: port2,
+ },
+ transferList: [port2],
+ });
+}
+
+// important to clear the require.cache in CJS projects
+const require = module.createRequire(import.meta.url);
+
+const requestPromiseCache = new Map();
+
+function getImportErrorMessage(filePath, type) {
+ return `There was a problem importing '${path.relative(".", filePath)}' via ${type}`;
+}
+
+// Used for JSON imports, suffering from Node warning that import assertions experimental but also
+// throwing an error if you try to import() a JSON file without an import assertion.
+/**
+ *
+ * @returns {string|undefined}
+ */
+function loadContents(path, options = {}) {
+ let rawInput;
+ /** @type {string} */
+ let encoding = "utf8"; // JSON is utf8
+ if (options?.encoding || options?.encoding === null) {
+ encoding = options.encoding;
+ }
+
+ try {
+ // @ts-expect-error This is an error in the upstream types
+ rawInput = fs.readFileSync(path, encoding);
+ } catch (error) {
+ // @ts-expect-error Temporary
+ if (error?.code === "ENOENT") {
+ // if file does not exist, return nothing
+ return;
+ }
+
+ throw error;
+ }
+
+ // Can return a buffer, string, etc
+ if (typeof rawInput === "string") {
+ rawInput = rawInput.trim();
+ }
+
+ return rawInput;
+}
+
+let lastModifiedPaths = new Map();
+eventBus.on("eleventy.importCacheReset", (fileQueue) => {
+ for (let filePath of fileQueue) {
+ let absolutePath = TemplatePath.absolutePath(filePath);
+ let newDate = Date.now();
+ lastModifiedPaths.set(absolutePath, newDate);
+
+ // post to EsmResolver worker thread
+ if (port1) {
+ port1.postMessage({ path: absolutePath, newDate });
+ }
+
+ // ESM Eleventy when using `import()` on a CJS project file still adds to require.cache
+ if (absolutePath in (require?.cache || {})) {
+ delete require.cache[absolutePath];
+ }
+ }
+});
+
+// raw means we don’t normalize away the `default` export
+async function dynamicImportAbsolutePath(absolutePath, options = {}) {
+ let { type, returnRaw, cacheBust } = Object.assign(
+ {
+ type: undefined,
+ returnRaw: false,
+ cacheBust: false, // force cache bust
+ },
+ options,
+ );
+
+ // Short circuit for JSON files (that are optional and can be empty)
+ if (absolutePath.endsWith(".json") || type === "json") {
+ try {
+ // https://v8.dev/features/import-assertions#dynamic-import() is still experimental in Node 20
+ let rawInput = loadContents(absolutePath);
+ if (!rawInput) {
+ // should not error when file exists but is _empty_
+ return;
+ }
+ return JSON.parse(rawInput);
+ } catch (e) {
+ return Promise.reject(
+ new EleventyImportError(getImportErrorMessage(absolutePath, "fs.readFile(json)"), e),
+ );
+ }
+ }
+
+ // Removed a `require` short circuit from this piece originally added
+ // in https://github.com/11ty/eleventy/pull/3493 Was a bit faster but
+ // error messaging was worse for require(esm)
+
+ let urlPath;
+ try {
+ let u = new URL(`file:${absolutePath}`);
+
+ // Bust the import cache if this is the last modified file (or cache busting is forced)
+ if (cacheBust) {
+ lastModifiedPaths.set(absolutePath, Date.now());
+ }
+
+ if (cacheBust || lastModifiedPaths.has(absolutePath)) {
+ u.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath));
+ }
+
+ urlPath = u.toString();
+ } catch (e) {
+ urlPath = absolutePath;
+ }
+
+ let promise;
+ if (requestPromiseCache.has(urlPath)) {
+ promise = requestPromiseCache.get(urlPath);
+ } else {
+ promise = import(urlPath);
+ requestPromiseCache.set(urlPath, promise);
+ }
+
+ return promise.then(
+ (target) => {
+ if (returnRaw) {
+ return target;
+ }
+
+ // If the only export is `default`, elevate to top (for ESM and CJS)
+ if (Object.keys(target).length === 1 && "default" in target) {
+ return target.default;
+ }
+
+ // When using import() on a CommonJS file that exports an object sometimes it
+ // returns duplicated values in `default` key, e.g. `{ default: {key: value}, key: value }`
+
+ // A few examples:
+ // module.exports = { key: false };
+ // returns `{ default: {key: false}, key: false }` as not expected.
+ // module.exports = { key: true };
+ // module.exports = { key: null };
+ // module.exports = { key: undefined };
+ // module.exports = { key: class {} };
+
+ // A few examples where it does not duplicate:
+ // module.exports = { key: 1 };
+ // returns `{ default: {key: 1} }` as expected.
+ // module.exports = { key: "value" };
+ // module.exports = { key: {} };
+ // module.exports = { key: [] };
+
+ if (type === "cjs" && "default" in target) {
+ let match = true;
+ for (let key in target) {
+ if (key === "default") {
+ continue;
+ }
+ if (key === "module.exports") {
+ continue;
+ }
+ if (target[key] !== target.default[key]) {
+ match = false;
+ }
+ }
+
+ if (match) {
+ return target.default;
+ }
+ }
+
+ // Otherwise return { default: value, named: value }
+ // Object.assign here so we can add things to it in JavaScript.js
+ return Object.assign({}, target);
+ },
+ (error) => {
+ return Promise.reject(
+ new EleventyImportError(getImportErrorMessage(absolutePath, `import(${type})`), error),
+ );
+ },
+ );
+}
+
+function normalizeFilePathInEleventyPackage(file) {
+ // Back up relative paths from ./src/Util/Require.js
+ return path.resolve(fileURLToPath(import.meta.url), "../../../", file);
+}
+
+async function dynamicImportFromEleventyPackage(file) {
+ // points to files relative to the top level Eleventy directory
+ let filePath = normalizeFilePathInEleventyPackage(file);
+
+ // Returns promise
+ return dynamicImportAbsolutePath(filePath, { type: "esm" });
+}
+
+async function dynamicImport(localPath, type, options = {}) {
+ let absolutePath = TemplatePath.absolutePath(localPath);
+ options.type = type;
+
+ // Returns promise
+ return dynamicImportAbsolutePath(absolutePath, options);
+}
+
+/* Used to import default Eleventy configuration file, raw means we don’t normalize away the `default` export */
+async function dynamicImportRawFromEleventyPackage(file) {
+ // points to files relative to the top level Eleventy directory
+ let filePath = normalizeFilePathInEleventyPackage(file);
+
+ // Returns promise
+ return dynamicImportAbsolutePath(filePath, { type: "esm", returnRaw: true });
+}
+
+/* Used to import app configuration files, raw means we don’t normalize away the `default` export */
+async function dynamicImportRaw(localPath, type) {
+ let absolutePath = TemplatePath.absolutePath(localPath);
+
+ // Returns promise
+ return dynamicImportAbsolutePath(absolutePath, { type, returnRaw: true });
+}
+
+export {
+ loadContents as EleventyLoadContent,
+ dynamicImport as EleventyImport,
+ dynamicImportRaw as EleventyImportRaw,
+ normalizeFilePathInEleventyPackage,
+
+ // no longer used in core
+ dynamicImportFromEleventyPackage as EleventyImportFromEleventy,
+ dynamicImportRawFromEleventyPackage as EleventyImportRawFromEleventy,
+};