diff options
Diffstat (limited to 'node_modules/@11ty/eleventy/src/Engines/JavaScript.js')
| -rw-r--r-- | node_modules/@11ty/eleventy/src/Engines/JavaScript.js | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/Engines/JavaScript.js b/node_modules/@11ty/eleventy/src/Engines/JavaScript.js new file mode 100644 index 0000000..29b3b7c --- /dev/null +++ b/node_modules/@11ty/eleventy/src/Engines/JavaScript.js @@ -0,0 +1,240 @@ +import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils"; + +import TemplateEngine from "./TemplateEngine.js"; +import EleventyBaseError from "../Errors/EleventyBaseError.js"; +import getJavaScriptData from "../Util/GetJavaScriptData.js"; +import { EleventyImport } from "../Util/Require.js"; +import { augmentFunction, augmentObject } from "./Util/ContextAugmenter.js"; + +class JavaScriptTemplateNotDefined extends EleventyBaseError {} + +export default class JavaScript extends TemplateEngine { + constructor(name, templateConfig) { + super(name, templateConfig); + this.instances = {}; + + this.config.events.on("eleventy#templateModified", (inputPath, metadata = {}) => { + let { usedByDependants, relevantLayouts } = metadata; + // Remove from cached instances when modified + let instancesToDelete = [ + inputPath, + ...(usedByDependants || []), + ...(relevantLayouts || []), + ].map((entry) => TemplatePath.addLeadingDotSlash(entry)); + for (let inputPath of instancesToDelete) { + if (inputPath in this.instances) { + delete this.instances[inputPath]; + } + } + }); + } + + get cacheable() { + return false; + } + + normalize(result) { + if (Buffer.isBuffer(result)) { + return result.toString(); + } + + return result; + } + + // String, Buffer, Promise + // Function, Class + // Object + // Module + _getInstance(mod) { + let noop = function () { + return ""; + }; + + let originalModData = mod?.data; + + if (typeof mod === "object" && mod.default && this.eleventyConfig.getIsProjectUsingEsm()) { + mod = mod.default; + } + + if (typeof mod === "string" || mod instanceof Buffer || mod.then) { + return { render: () => mod }; + } else if (typeof mod === "function") { + if (mod.prototype?.data || mod.prototype?.render) { + if (!("render" in mod.prototype)) { + mod.prototype.render = noop; + } + + if (!("data" in mod.prototype) && !mod.data && originalModData) { + mod.prototype.data = originalModData; + } + + return new mod(); + } else { + return { + ...(originalModData ? { data: originalModData } : undefined), + render: mod, + }; + } + } else if ("data" in mod || "render" in mod) { + if (!mod.render) { + mod.render = noop; + } + if (!mod.data && originalModData) { + mod.data = originalModData; + } + return mod; + } + } + + async #getInstanceFromInputPath(inputPath) { + let mod; + let relativeInputPath = + this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(inputPath); + if (this.eleventyConfig.userConfig.isVirtualTemplate(relativeInputPath)) { + mod = this.eleventyConfig.userConfig.virtualTemplates[relativeInputPath].content; + } else { + let isEsm = this.eleventyConfig.getIsProjectUsingEsm(); + let cacheBust = !this.cacheable || !this.config.useTemplateCache; + mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs", { + cacheBust, + }); + } + + let inst = this._getInstance(mod); + if (inst) { + this.instances[inputPath] = inst; + } else { + throw new JavaScriptTemplateNotDefined( + `No JavaScript template returned from ${inputPath}. Did you assign module.exports (CommonJS) or export (ESM)?`, + ); + } + return inst; + } + + async getInstanceFromInputPath(inputPath) { + if (!this.instances[inputPath]) { + this.instances[inputPath] = this.#getInstanceFromInputPath(inputPath); + } + + return this.instances[inputPath]; + } + + /** + * JavaScript files defer to the module loader rather than read the files to strings + * + * @override + */ + needsToReadFileContents() { + return false; + } + + /** + * Use the module loader directly + * + * @override + */ + useJavaScriptImport() { + return true; + } + + async getExtraDataFromFile(inputPath) { + let inst = await this.getInstanceFromInputPath(inputPath); + return getJavaScriptData(inst, inputPath); + } + + getJavaScriptFunctions(inst) { + let fns = {}; + let configFns = this.config.javascriptFunctions; + + for (let key in configFns) { + // prefer pre-existing `page` javascriptFunction, if one exists + fns[key] = augmentFunction(configFns[key], { + source: inst, + overwrite: false, + }); + } + return fns; + } + + // Backwards compat + static wrapJavaScriptFunction(inst, fn) { + return augmentFunction(fn, { + source: inst, + }); + } + + addExportsToBundles(inst, url) { + let cfg = this.eleventyConfig.userConfig; + if (!("getBundleManagers" in cfg)) { + return; + } + + let managers = cfg.getBundleManagers(); + for (let name in managers) { + let mgr = managers[name]; + let key = mgr.getBundleExportKey(); + if (!key) { + continue; + } + + if (typeof inst[key] === "string") { + // export const css = ``; + mgr.addToPage(url, inst[key]); + } else if (isPlainObject(inst[key])) { + if (typeof inst[key][name] === "string") { + // Object with bundle names: + // export const bundle = { + // css: `` + // }; + mgr.addToPage(url, inst[key][name]); + } else if (isPlainObject(inst[key][name])) { + // Object with bucket names: + // export const bundle = { + // css: { + // default: `` + // } + // }; + for (let bucketName in inst[key][name]) { + mgr.addToPage(url, inst[key][name][bucketName], bucketName); + } + } + } + } + } + + async compile(str, inputPath) { + let inst; + if (str) { + // When str has a value, it's being used for permalinks in data + inst = this._getInstance(str); + } else { + // For normal templates, str will be falsy. + inst = await this.getInstanceFromInputPath(inputPath); + } + + if (inst?.render) { + return (data = {}) => { + // TODO does this do anything meaningful for non-classes? + // `inst` should have a normalized `render` function from _getInstance + + // Map exports to bundles + if (data.page?.url) { + this.addExportsToBundles(inst, data.page.url); + } + + augmentObject(inst, { + source: data, + overwrite: false, + }); + + Object.assign(inst, this.getJavaScriptFunctions(inst)); + + return this.normalize(inst.render.call(inst, data)); + }; + } + } + + static shouldSpiderJavaScriptDependencies() { + return true; + } +} |
