import lodash from "@11ty/lodash-custom"; import debugUtil from "debug"; import ComputedDataQueue from "./ComputedDataQueue.js"; import ComputedDataTemplateString from "./ComputedDataTemplateString.js"; import ComputedDataProxy from "./ComputedDataProxy.js"; const { set: lodashSet, get: lodashGet } = lodash; const debug = debugUtil("Eleventy:ComputedData"); class ComputedData { constructor(config) { this.computed = {}; this.symbolParseFunctions = {}; this.templateStringKeyLookup = {}; this.computedKeys = new Set(); this.declaredDependencies = {}; this.queue = new ComputedDataQueue(); this.config = config; } add(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) { this.computedKeys.add(key); this.declaredDependencies[key] = declaredDependencies; // bind config filters/JS functions if (typeof renderFn === "function") { let fns = {}; // TODO bug? no access to non-universal config things? if (this.config) { fns = { ...this.config.javascriptFunctions, }; } fns.tmpl = templateInstance; renderFn = renderFn.bind(fns); } lodashSet(this.computed, key, renderFn); if (symbolParseFn) { lodashSet(this.symbolParseFunctions, key, symbolParseFn); } } addTemplateString(key, renderFn, declaredDependencies = [], symbolParseFn, templateInstance) { this.add(key, renderFn, declaredDependencies, symbolParseFn, templateInstance); this.templateStringKeyLookup[key] = true; } async resolveVarOrder(data) { let proxyByTemplateString = new ComputedDataTemplateString(this.computedKeys); let proxyByProxy = new ComputedDataProxy(this.computedKeys); for (let key of this.computedKeys) { let computed = lodashGet(this.computed, key); if (typeof computed !== "function") { // add nodes for non functions (primitives like booleans, etc) // This will not handle template strings, as they are normalized to functions this.queue.addNode(key); } else { this.queue.uses(key, this.declaredDependencies[key]); let symbolParseFn = lodashGet(this.symbolParseFunctions, key); let varsUsed = []; if (symbolParseFn) { // use the parseForSymbols function in the TemplateEngine varsUsed = symbolParseFn(); } else if (symbolParseFn !== false) { // skip resolution is this is false (just use declaredDependencies) let isTemplateString = !!this.templateStringKeyLookup[key]; let proxy = isTemplateString ? proxyByTemplateString : proxyByProxy; varsUsed = await proxy.findVarsUsed(computed, data); } debug("%o accesses %o variables", key, varsUsed); let filteredVarsUsed = varsUsed.filter((varUsed) => { return ( (varUsed !== key && this.computedKeys.has(varUsed)) || varUsed.startsWith("collections.") ); }); this.queue.uses(key, filteredVarsUsed); } } } async _setupDataEntry(data, order) { debug("Computed data order of execution: %o", order); for (let key of order) { let computed = lodashGet(this.computed, key); if (typeof computed === "function") { let ret = await computed(data); lodashSet(data, key, ret); } else if (computed !== undefined) { lodashSet(data, key, computed); } } } async setupData(data, orderFilter) { await this.resolveVarOrder(data); await this.processRemainingData(data, orderFilter); } async processRemainingData(data, orderFilter) { // process all variables let order = this.queue.getOrder(); if (orderFilter && typeof orderFilter === "function") { order = order.filter(orderFilter.bind(this.queue)); } await this._setupDataEntry(data, order); this.queue.markComputed(order); } } export default ComputedData;