inital
This commit is contained in:
55
node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js
generated
vendored
Normal file
55
node_modules/@11ty/eleventy/src/Benchmark/Benchmark.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { performance } from "node:perf_hooks";
|
||||
|
||||
class Benchmark {
|
||||
constructor() {
|
||||
// TypeScript slop
|
||||
this.timeSpent = 0;
|
||||
this.timesCalled = 0;
|
||||
this.beforeTimers = [];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.timeSpent = 0;
|
||||
this.timesCalled = 0;
|
||||
this.beforeTimers = [];
|
||||
}
|
||||
|
||||
getNewTimestamp() {
|
||||
if (performance) {
|
||||
return performance.now();
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
incrementCount() {
|
||||
this.timesCalled++;
|
||||
}
|
||||
|
||||
// TODO(slightlyoff):
|
||||
// disable all of these hrtime requests when not benchmarking
|
||||
before() {
|
||||
this.timesCalled++;
|
||||
this.beforeTimers.push(this.getNewTimestamp());
|
||||
}
|
||||
|
||||
after() {
|
||||
if (!this.beforeTimers.length) {
|
||||
throw new Error("You called Benchmark after() without a before().");
|
||||
}
|
||||
|
||||
let before = this.beforeTimers.pop();
|
||||
if (!this.beforeTimers.length) {
|
||||
this.timeSpent += this.getNewTimestamp() - before;
|
||||
}
|
||||
}
|
||||
|
||||
getTimesCalled() {
|
||||
return this.timesCalled;
|
||||
}
|
||||
|
||||
getTotal() {
|
||||
return this.timeSpent;
|
||||
}
|
||||
}
|
||||
|
||||
export default Benchmark;
|
||||
135
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js
generated
vendored
Normal file
135
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkGroup.js
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
import debugUtil from "debug";
|
||||
|
||||
import ConsoleLogger from "../Util/ConsoleLogger.js";
|
||||
import isAsyncFunction from "../Util/IsAsyncFunction.js";
|
||||
import Benchmark from "./Benchmark.js";
|
||||
|
||||
const debugBenchmark = debugUtil("Eleventy:Benchmark");
|
||||
|
||||
class BenchmarkGroup {
|
||||
constructor() {
|
||||
this.benchmarks = {};
|
||||
// Warning: aggregate benchmarks automatically default to false via BenchmarkManager->getBenchmarkGroup
|
||||
this.isVerbose = true;
|
||||
this.logger = new ConsoleLogger();
|
||||
this.minimumThresholdMs = 50;
|
||||
this.minimumThresholdPercent = 8;
|
||||
}
|
||||
|
||||
setIsVerbose(isVerbose) {
|
||||
this.isVerbose = isVerbose;
|
||||
this.logger.isVerbose = isVerbose;
|
||||
}
|
||||
|
||||
reset() {
|
||||
for (var type in this.benchmarks) {
|
||||
this.benchmarks[type].reset();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO use addAsync everywhere instead
|
||||
add(type, callback) {
|
||||
let benchmark = (this.benchmarks[type] = new Benchmark());
|
||||
|
||||
/** @this {any} */
|
||||
let fn = function (...args) {
|
||||
benchmark.before();
|
||||
let ret = callback.call(this, ...args);
|
||||
benchmark.after();
|
||||
return ret;
|
||||
};
|
||||
|
||||
Object.defineProperty(fn, "__eleventyInternal", {
|
||||
value: {
|
||||
type: isAsyncFunction(callback) ? "async" : "sync",
|
||||
callback,
|
||||
},
|
||||
});
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
// callback must return a promise
|
||||
// async addAsync(type, callback) {
|
||||
// let benchmark = (this.benchmarks[type] = new Benchmark());
|
||||
|
||||
// benchmark.before();
|
||||
// // don’t await here.
|
||||
// let promise = callback.call(this);
|
||||
// promise.then(function() {
|
||||
// benchmark.after();
|
||||
// });
|
||||
// return promise;
|
||||
// }
|
||||
|
||||
setMinimumThresholdMs(minimumThresholdMs) {
|
||||
let val = parseInt(minimumThresholdMs, 10);
|
||||
if (isNaN(val)) {
|
||||
throw new Error("`setMinimumThresholdMs` expects a number argument.");
|
||||
}
|
||||
this.minimumThresholdMs = val;
|
||||
}
|
||||
|
||||
setMinimumThresholdPercent(minimumThresholdPercent) {
|
||||
let val = parseInt(minimumThresholdPercent, 10);
|
||||
if (isNaN(val)) {
|
||||
throw new Error("`setMinimumThresholdPercent` expects a number argument.");
|
||||
}
|
||||
this.minimumThresholdPercent = val;
|
||||
}
|
||||
|
||||
has(type) {
|
||||
return !!this.benchmarks[type];
|
||||
}
|
||||
|
||||
get(type) {
|
||||
if (!this.benchmarks[type]) {
|
||||
this.benchmarks[type] = new Benchmark();
|
||||
}
|
||||
return this.benchmarks[type];
|
||||
}
|
||||
|
||||
padNumber(num, length) {
|
||||
if (("" + num).length >= length) {
|
||||
return num;
|
||||
}
|
||||
|
||||
let prefix = new Array(length + 1).join(" ");
|
||||
return (prefix + num).slice(-1 * length);
|
||||
}
|
||||
|
||||
finish(label, totalTimeSpent) {
|
||||
for (var type in this.benchmarks) {
|
||||
let bench = this.benchmarks[type];
|
||||
let isAbsoluteMinimumComparison = this.minimumThresholdMs > 0;
|
||||
let totalForBenchmark = bench.getTotal();
|
||||
let percent = Math.round((totalForBenchmark * 100) / totalTimeSpent);
|
||||
let callCount = bench.getTimesCalled();
|
||||
|
||||
let output = {
|
||||
ms: this.padNumber(totalForBenchmark.toFixed(0), 6),
|
||||
percent: this.padNumber(percent, 3),
|
||||
calls: this.padNumber(callCount, 5),
|
||||
};
|
||||
let str = `Benchmark ${output.ms}ms ${output.percent}% ${output.calls}× (${label}) ${type}`;
|
||||
|
||||
if (
|
||||
isAbsoluteMinimumComparison &&
|
||||
totalForBenchmark >= this.minimumThresholdMs &&
|
||||
percent > this.minimumThresholdPercent
|
||||
) {
|
||||
this.logger.warn(str);
|
||||
}
|
||||
|
||||
// Opt out of logging if low count (1× or 2×) or 0ms / 1%
|
||||
if (
|
||||
callCount > 1 || // called more than once
|
||||
Math.round(totalForBenchmark) > 0 // more than 0.5ms
|
||||
) {
|
||||
debugBenchmark(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BenchmarkGroup;
|
||||
73
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js
generated
vendored
Normal file
73
node_modules/@11ty/eleventy/src/Benchmark/BenchmarkManager.js
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
import { performance } from "node:perf_hooks";
|
||||
|
||||
import BenchmarkGroup from "./BenchmarkGroup.js";
|
||||
|
||||
// TODO this should not be a singleton, it belongs in the config or somewhere on the Eleventy instance.
|
||||
|
||||
class BenchmarkManager {
|
||||
constructor() {
|
||||
this.benchmarkGroups = {};
|
||||
this.isVerbose = true;
|
||||
this.start = this.getNewTimestamp();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.start = this.getNewTimestamp();
|
||||
|
||||
for (var j in this.benchmarkGroups) {
|
||||
this.benchmarkGroups[j].reset();
|
||||
}
|
||||
}
|
||||
|
||||
getNewTimestamp() {
|
||||
if (performance) {
|
||||
return performance.now();
|
||||
}
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
setVerboseOutput(isVerbose) {
|
||||
this.isVerbose = !!isVerbose;
|
||||
}
|
||||
|
||||
hasBenchmarkGroup(name) {
|
||||
return name in this.benchmarkGroups;
|
||||
}
|
||||
|
||||
getBenchmarkGroup(name) {
|
||||
if (!this.benchmarkGroups[name]) {
|
||||
this.benchmarkGroups[name] = new BenchmarkGroup();
|
||||
|
||||
// Special behavior for aggregate benchmarks
|
||||
// so they don’t console.log every time
|
||||
if (name === "Aggregate") {
|
||||
this.benchmarkGroups[name].setIsVerbose(false);
|
||||
} else {
|
||||
this.benchmarkGroups[name].setIsVerbose(this.isVerbose);
|
||||
}
|
||||
}
|
||||
|
||||
return this.benchmarkGroups[name];
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.benchmarkGroups;
|
||||
}
|
||||
|
||||
get(name) {
|
||||
if (name) {
|
||||
return this.getBenchmarkGroup(name);
|
||||
}
|
||||
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
finish() {
|
||||
let totalTimeSpentBenchmarking = this.getNewTimestamp() - this.start;
|
||||
for (var j in this.benchmarkGroups) {
|
||||
this.benchmarkGroups[j].finish(j, totalTimeSpentBenchmarking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BenchmarkManager;
|
||||
121
node_modules/@11ty/eleventy/src/Data/ComputedData.js
generated
vendored
Normal file
121
node_modules/@11ty/eleventy/src/Data/ComputedData.js
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
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;
|
||||
}
|
||||
renderFn = renderFn.bind({
|
||||
...fns,
|
||||
tmpl: templateInstance,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
131
node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js
generated
vendored
Normal file
131
node_modules/@11ty/eleventy/src/Data/ComputedDataProxy.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
const { set: lodashSet, get: lodashGet } = lodash;
|
||||
|
||||
/* Calculates computed data using Proxies */
|
||||
class ComputedDataProxy {
|
||||
constructor(computedKeys) {
|
||||
if (Array.isArray(computedKeys)) {
|
||||
this.computedKeys = new Set(computedKeys);
|
||||
} else {
|
||||
this.computedKeys = computedKeys;
|
||||
}
|
||||
}
|
||||
|
||||
isArrayOrPlainObject(data) {
|
||||
return Array.isArray(data) || isPlainObject(data);
|
||||
}
|
||||
|
||||
getProxyData(data, keyRef) {
|
||||
// WARNING: SIDE EFFECTS
|
||||
// Set defaults for keys not already set on parent data
|
||||
|
||||
// TODO should make another effort to get rid of this,
|
||||
// See the ProxyWrap util for more proxy handlers that will likely fix this
|
||||
let undefinedValue = "__11TY_UNDEFINED__";
|
||||
if (this.computedKeys) {
|
||||
for (let key of this.computedKeys) {
|
||||
if (lodashGet(data, key, undefinedValue) === undefinedValue) {
|
||||
lodashSet(data, key, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let proxyData = this._getProxyData(data, keyRef);
|
||||
return proxyData;
|
||||
}
|
||||
|
||||
_getProxyForObject(dataObj, keyRef, parentKey = "") {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (obj, key) => {
|
||||
if (typeof key !== "string") {
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
let newKey = `${parentKey ? `${parentKey}.` : ""}${key}`;
|
||||
|
||||
// Issue #1137
|
||||
// Special case for Collections, always return an Array for collection keys
|
||||
// so they it works fine with Array methods like `filter`, `map`, etc
|
||||
if (newKey === "collections") {
|
||||
keyRef.add(newKey);
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, key) => {
|
||||
if (typeof key === "string") {
|
||||
keyRef.add(`collections.${key}`);
|
||||
return [];
|
||||
}
|
||||
return target[key];
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let newData = this._getProxyData(dataObj[key], keyRef, newKey);
|
||||
if (!this.isArrayOrPlainObject(newData)) {
|
||||
keyRef.add(newKey);
|
||||
}
|
||||
return newData;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_getProxyForArray(dataArr, keyRef, parentKey = "") {
|
||||
return new Proxy(new Array(dataArr.length), {
|
||||
get: (obj, key) => {
|
||||
if (Array.prototype.hasOwnProperty(key)) {
|
||||
// remove `filter`, `constructor`, `map`, etc
|
||||
keyRef.add(parentKey);
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
// Hm, this needs to be better
|
||||
if (key === "then") {
|
||||
keyRef.add(parentKey);
|
||||
return;
|
||||
}
|
||||
|
||||
let newKey = `${parentKey}[${key}]`;
|
||||
let newData = this._getProxyData(dataArr[key], keyRef, newKey);
|
||||
if (!this.isArrayOrPlainObject(newData)) {
|
||||
keyRef.add(newKey);
|
||||
}
|
||||
return newData;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_getProxyData(data, keyRef, parentKey = "") {
|
||||
if (isPlainObject(data)) {
|
||||
return this._getProxyForObject(data, keyRef, parentKey);
|
||||
} else if (Array.isArray(data)) {
|
||||
return this._getProxyForArray(data, keyRef, parentKey);
|
||||
}
|
||||
|
||||
// everything else!
|
||||
return data;
|
||||
}
|
||||
|
||||
async findVarsUsed(fn, data = {}) {
|
||||
let keyRef = new Set();
|
||||
|
||||
// careful, logging proxyData will mess with test results!
|
||||
let proxyData = this.getProxyData(data, keyRef);
|
||||
|
||||
// squelch console logs for this fake proxy data pass 😅
|
||||
// let savedLog = console.log;
|
||||
// console.log = () => {};
|
||||
await fn(proxyData);
|
||||
// console.log = savedLog;
|
||||
|
||||
return Array.from(keyRef);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedDataProxy;
|
||||
64
node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js
generated
vendored
Normal file
64
node_modules/@11ty/eleventy/src/Data/ComputedDataQueue.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import { DepGraph as DependencyGraph } from "dependency-graph";
|
||||
|
||||
/* Keeps track of the dependency graph between computed data variables
|
||||
* Removes keys from the graph when they are computed.
|
||||
*/
|
||||
class ComputedDataQueue {
|
||||
constructor() {
|
||||
this.graph = new DependencyGraph();
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return this.graph.overallOrder();
|
||||
}
|
||||
|
||||
getOrderFor(name) {
|
||||
return this.graph.dependenciesOf(name);
|
||||
}
|
||||
|
||||
getDependsOn(name) {
|
||||
return this.graph.dependantsOf(name);
|
||||
}
|
||||
|
||||
isUsesStartsWith(name, prefix) {
|
||||
if (name.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
this.graph.dependenciesOf(name).filter((entry) => {
|
||||
return entry.startsWith(prefix);
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
addNode(name) {
|
||||
if (!this.graph.hasNode(name)) {
|
||||
this.graph.addNode(name);
|
||||
}
|
||||
}
|
||||
|
||||
_uses(graph, name, varsUsed = []) {
|
||||
if (!graph.hasNode(name)) {
|
||||
graph.addNode(name);
|
||||
}
|
||||
|
||||
for (let varUsed of varsUsed) {
|
||||
if (!graph.hasNode(varUsed)) {
|
||||
graph.addNode(varUsed);
|
||||
}
|
||||
graph.addDependency(name, varUsed);
|
||||
}
|
||||
}
|
||||
|
||||
uses(name, varsUsed = []) {
|
||||
this._uses(this.graph, name, varsUsed);
|
||||
}
|
||||
|
||||
markComputed(varsComputed = []) {
|
||||
for (let varComputed of varsComputed) {
|
||||
this.graph.removeNode(varComputed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedDataQueue;
|
||||
70
node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js
generated
vendored
Normal file
70
node_modules/@11ty/eleventy/src/Data/ComputedDataTemplateString.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const { set: lodashSet } = lodash;
|
||||
const debug = debugUtil("Eleventy:ComputedDataTemplateString");
|
||||
|
||||
/* Calculates computed data in Template Strings.
|
||||
* Ideally we would use the Proxy approach but it doesn’t work
|
||||
* in some template languages that visit all available data even if
|
||||
* it isn’t used in the template (Nunjucks)
|
||||
*/
|
||||
class ComputedDataTemplateString {
|
||||
constructor(computedKeys) {
|
||||
if (Array.isArray(computedKeys)) {
|
||||
this.computedKeys = new Set(computedKeys);
|
||||
} else {
|
||||
this.computedKeys = computedKeys;
|
||||
}
|
||||
|
||||
// is this ¯\_(lisp)_/¯
|
||||
// must be strings that won’t be escaped by template languages
|
||||
this.prefix = "(((11ty(((";
|
||||
this.suffix = ")))11ty)))";
|
||||
}
|
||||
|
||||
getProxyData() {
|
||||
let proxyData = {};
|
||||
|
||||
// use these special strings as a workaround to check the rendered output
|
||||
// can’t use proxies here as some template languages trigger proxy for all
|
||||
// keys in data
|
||||
for (let key of this.computedKeys) {
|
||||
// TODO don’t allow to set eleventyComputed.page? other disallowed computed things?
|
||||
lodashSet(proxyData, key, this.prefix + key + this.suffix);
|
||||
}
|
||||
|
||||
return proxyData;
|
||||
}
|
||||
|
||||
findVarsInOutput(output = "") {
|
||||
let vars = new Set();
|
||||
let splits = output.split(this.prefix);
|
||||
for (let split of splits) {
|
||||
let varName = split.slice(0, split.indexOf(this.suffix) < 0 ? 0 : split.indexOf(this.suffix));
|
||||
if (varName) {
|
||||
vars.add(varName);
|
||||
}
|
||||
}
|
||||
return Array.from(vars);
|
||||
}
|
||||
|
||||
async findVarsUsed(fn) {
|
||||
let proxyData = this.getProxyData();
|
||||
let output;
|
||||
// Mitigation for #1061, errors with filters in the first pass shouldn’t fail the whole thing.
|
||||
try {
|
||||
output = await fn(proxyData);
|
||||
} catch (e) {
|
||||
debug("Computed Data first pass data resolution error: %o", e);
|
||||
}
|
||||
|
||||
// page.outputPath on serverless urls returns false.
|
||||
if (typeof output === "string") {
|
||||
return this.findVarsInOutput(output);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default ComputedDataTemplateString;
|
||||
737
node_modules/@11ty/eleventy/src/Data/TemplateData.js
generated
vendored
Normal file
737
node_modules/@11ty/eleventy/src/Data/TemplateData.js
generated
vendored
Normal file
@@ -0,0 +1,737 @@
|
||||
import path from "node:path";
|
||||
import semver from "semver";
|
||||
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import unique from "../Util/Objects/Unique.js";
|
||||
import TemplateGlob from "../TemplateGlob.js";
|
||||
import EleventyExtensionMap from "../EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import TemplateDataInitialGlobalData from "./TemplateDataInitialGlobalData.js";
|
||||
import { getEleventyPackageJson, getWorkingProjectPackageJson } from "../Util/ImportJsonSync.js";
|
||||
import { EleventyImport, EleventyLoadContent } from "../Util/Require.js";
|
||||
import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
|
||||
|
||||
const { set: lodashSet, get: lodashGet } = lodash;
|
||||
|
||||
const debugWarn = debugUtil("Eleventy:Warnings");
|
||||
const debug = debugUtil("Eleventy:TemplateData");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateData");
|
||||
|
||||
class TemplateDataConfigError extends EleventyBaseError {}
|
||||
class TemplateDataParseError extends EleventyBaseError {}
|
||||
|
||||
class TemplateData {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateDataConfigError("Missing `config`.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = this.eleventyConfig.getConfig();
|
||||
|
||||
this.benchmarks = {
|
||||
data: this.config.benchmarkManager.get("Data"),
|
||||
aggregate: this.config.benchmarkManager.get("Aggregate"),
|
||||
};
|
||||
|
||||
this.rawImports = {};
|
||||
this.globalData = null;
|
||||
this.templateDirectoryData = {};
|
||||
this.isEsm = false;
|
||||
|
||||
this.initialGlobalData = new TemplateDataInitialGlobalData(this.eleventyConfig);
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
// if this was set but `falsy` we would fallback to inputDir
|
||||
get dataDir() {
|
||||
return this.dirs.data;
|
||||
}
|
||||
|
||||
// This was async in 2.0 and prior but doesn’t need to be any more.
|
||||
getInputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
getDataDir() {
|
||||
return this.dataDir;
|
||||
}
|
||||
|
||||
get _fsExistsCache() {
|
||||
// It's common for data files not to exist, so we avoid going to the FS to
|
||||
// re-check if they do via a quick-and-dirty cache.
|
||||
return this.eleventyConfig.existsCache;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
setProjectUsingEsm(isEsmProject) {
|
||||
this.isEsm = !!isEsmProject;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
get environmentVariables() {
|
||||
return this._env;
|
||||
}
|
||||
|
||||
set environmentVariables(env) {
|
||||
this._env = env;
|
||||
}
|
||||
|
||||
/* Used by tests */
|
||||
_setConfig(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
getRawImports() {
|
||||
if (!this.config.keys.package) {
|
||||
debug(
|
||||
"Opted-out of package.json assignment for global data with falsy value for `keys.package` configuration.",
|
||||
);
|
||||
return this.rawImports;
|
||||
} else if (Object.keys(this.rawImports).length > 0) {
|
||||
return this.rawImports;
|
||||
}
|
||||
|
||||
try {
|
||||
let pkgJson = getWorkingProjectPackageJson();
|
||||
this.rawImports[this.config.keys.package] = pkgJson;
|
||||
|
||||
if (this.config.freezeReservedData) {
|
||||
DeepFreeze(this.rawImports);
|
||||
}
|
||||
} catch (e) {
|
||||
debug("Could not find or require package.json import for global data.");
|
||||
}
|
||||
|
||||
return this.rawImports;
|
||||
}
|
||||
|
||||
clearData() {
|
||||
this.globalData = null;
|
||||
this.configApiGlobalData = null;
|
||||
this.templateDirectoryData = {};
|
||||
}
|
||||
|
||||
_getGlobalDataGlobByExtension(extension) {
|
||||
return TemplateGlob.normalizePath(this.dataDir, `/**/*.${extension}`);
|
||||
}
|
||||
|
||||
// This is a backwards compatibility helper with the old `jsDataFileSuffix` configuration API
|
||||
getDataFileSuffixes() {
|
||||
// New API
|
||||
if (Array.isArray(this.config.dataFileSuffixes)) {
|
||||
return this.config.dataFileSuffixes;
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if (this.config.jsDataFileSuffix) {
|
||||
let suffixes = [];
|
||||
suffixes.push(this.config.jsDataFileSuffix); // e.g. filename.11tydata.json
|
||||
suffixes.push(""); // suffix-less for free with old API, e.g. filename.json
|
||||
return suffixes;
|
||||
}
|
||||
return []; // if both of these entries are set to false, use no files
|
||||
}
|
||||
|
||||
// This is used exclusively for --watch and --serve chokidar targets
|
||||
async getTemplateDataFileGlob() {
|
||||
let suffixes = this.getDataFileSuffixes();
|
||||
let globSuffixesWithLeadingDot = new Set();
|
||||
globSuffixesWithLeadingDot.add("json"); // covers .11tydata.json too
|
||||
let globSuffixesWithoutLeadingDot = new Set();
|
||||
|
||||
// Typically using [ '.11tydata', '' ] suffixes to find data files
|
||||
for (let suffix of suffixes) {
|
||||
// TODO the `suffix` truthiness check is purely for backwards compat?
|
||||
if (suffix && typeof suffix === "string") {
|
||||
if (suffix.startsWith(".")) {
|
||||
// .suffix.js
|
||||
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.mjs`);
|
||||
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.cjs`);
|
||||
globSuffixesWithLeadingDot.add(`${suffix.slice(1)}.js`);
|
||||
} else {
|
||||
// "suffix.js" without leading dot
|
||||
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.mjs`);
|
||||
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.cjs`);
|
||||
globSuffixesWithoutLeadingDot.add(`${suffix || ""}.js`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration Data Extensions e.g. yaml
|
||||
if (this.hasUserDataExtensions()) {
|
||||
for (let extension of this.getUserDataExtensions()) {
|
||||
globSuffixesWithLeadingDot.add(extension); // covers .11tydata.{extension} too
|
||||
}
|
||||
}
|
||||
|
||||
let paths = [];
|
||||
if (globSuffixesWithLeadingDot.size > 0) {
|
||||
paths.push(`${this.inputDir}**/*.{${Array.from(globSuffixesWithLeadingDot).join(",")}}`);
|
||||
}
|
||||
if (globSuffixesWithoutLeadingDot.size > 0) {
|
||||
paths.push(`${this.inputDir}**/*{${Array.from(globSuffixesWithoutLeadingDot).join(",")}}`);
|
||||
}
|
||||
|
||||
return TemplatePath.addLeadingDotSlashArray(paths);
|
||||
}
|
||||
|
||||
// For spidering dependencies
|
||||
// TODO Can we reuse getTemplateDataFileGlob instead? Maybe just filter off the .json files before scanning for dependencies
|
||||
getTemplateJavaScriptDataFileGlob() {
|
||||
let paths = [];
|
||||
let suffixes = this.getDataFileSuffixes();
|
||||
for (let suffix of suffixes) {
|
||||
if (suffix) {
|
||||
// TODO this check is purely for backwards compat and I kinda feel like it shouldn’t be here
|
||||
// paths.push(`${this.inputDir}/**/*${suffix || ""}.cjs`); // Same as above
|
||||
paths.push(`${this.inputDir}**/*${suffix || ""}.js`);
|
||||
}
|
||||
}
|
||||
|
||||
return TemplatePath.addLeadingDotSlashArray(paths);
|
||||
}
|
||||
|
||||
getGlobalDataGlob() {
|
||||
let extGlob = this.getGlobalDataExtensionPriorities().join(",");
|
||||
return [this._getGlobalDataGlobByExtension("{" + extGlob + "}")];
|
||||
}
|
||||
|
||||
getWatchPathCache() {
|
||||
return this.pathCache;
|
||||
}
|
||||
|
||||
getGlobalDataExtensionPriorities() {
|
||||
return this.getUserDataExtensions().concat(["json", "mjs", "cjs", "js"]);
|
||||
}
|
||||
|
||||
static calculateExtensionPriority(path, priorities) {
|
||||
for (let i = 0; i < priorities.length; i++) {
|
||||
let ext = priorities[i];
|
||||
if (path.endsWith(ext)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return priorities.length;
|
||||
}
|
||||
|
||||
async getGlobalDataFiles() {
|
||||
let priorities = this.getGlobalDataExtensionPriorities();
|
||||
|
||||
let fsBench = this.benchmarks.aggregate.get("Searching the file system (data)");
|
||||
fsBench.before();
|
||||
let globs = this.getGlobalDataGlob();
|
||||
let paths = await this.fileSystemSearch.search("global-data", globs);
|
||||
fsBench.after();
|
||||
|
||||
// sort paths according to extension priorities
|
||||
// here we use reverse ordering, because paths with bigger index in array will override the first ones
|
||||
// example [path/file.json, path/file.js] here js will override json
|
||||
paths = paths.sort((first, second) => {
|
||||
let p1 = TemplateData.calculateExtensionPriority(first, priorities);
|
||||
let p2 = TemplateData.calculateExtensionPriority(second, priorities);
|
||||
if (p1 < p2) {
|
||||
return -1;
|
||||
}
|
||||
if (p1 > p2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.pathCache = paths;
|
||||
return paths;
|
||||
}
|
||||
|
||||
getObjectPathForDataFile(dataFilePath) {
|
||||
let reducedPath = TemplatePath.stripLeadingSubPath(dataFilePath, this.dataDir);
|
||||
let parsed = path.parse(reducedPath);
|
||||
let folders = parsed.dir ? parsed.dir.split("/") : [];
|
||||
folders.push(parsed.name);
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
async getAllGlobalData() {
|
||||
let globalData = {};
|
||||
let files = TemplatePath.addLeadingDotSlashArray(await this.getGlobalDataFiles());
|
||||
|
||||
this.config.events.emit("eleventy.globalDataFiles", files);
|
||||
|
||||
let dataFileConflicts = {};
|
||||
|
||||
for (let j = 0, k = files.length; j < k; j++) {
|
||||
let data = await this.getDataValue(files[j]);
|
||||
let objectPathTarget = this.getObjectPathForDataFile(files[j]);
|
||||
|
||||
// Since we're joining directory paths and an array is not usable as an objectkey since two identical arrays are not double equal,
|
||||
// we can just join the array by a forbidden character ("/"" is chosen here, since it works on Linux, Mac and Windows).
|
||||
// If at some point this isn't enough anymore, it would be possible to just use JSON.stringify(objectPathTarget) since that
|
||||
// is guaranteed to work but is signifivcantly slower.
|
||||
let objectPathTargetString = objectPathTarget.join(path.sep);
|
||||
|
||||
// if two global files have the same path (but different extensions)
|
||||
// and conflict, let’s merge them.
|
||||
if (dataFileConflicts[objectPathTargetString]) {
|
||||
debugWarn(
|
||||
`merging global data from ${files[j]} with an already existing global data file (${dataFileConflicts[objectPathTargetString]}). Overriding existing keys.`,
|
||||
);
|
||||
|
||||
let oldData = lodashGet(globalData, objectPathTarget);
|
||||
data = TemplateData.mergeDeep(this.config.dataDeepMerge, oldData, data);
|
||||
}
|
||||
|
||||
dataFileConflicts[objectPathTargetString] = files[j];
|
||||
debug(`Found global data file ${files[j]} and adding as: ${objectPathTarget}`);
|
||||
lodashSet(globalData, objectPathTarget, data);
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
|
||||
async #getInitialGlobalData() {
|
||||
let globalData = await this.initialGlobalData.getData();
|
||||
|
||||
if (!("eleventy" in globalData)) {
|
||||
globalData.eleventy = {};
|
||||
}
|
||||
|
||||
// #2293 for meta[name=generator]
|
||||
const pkg = getEleventyPackageJson();
|
||||
globalData.eleventy.version = semver.coerce(pkg.version).toString();
|
||||
globalData.eleventy.generator = `Eleventy v${globalData.eleventy.version}`;
|
||||
|
||||
if (this.environmentVariables) {
|
||||
if (!("env" in globalData.eleventy)) {
|
||||
globalData.eleventy.env = {};
|
||||
}
|
||||
|
||||
Object.assign(globalData.eleventy.env, this.environmentVariables);
|
||||
}
|
||||
|
||||
if (this.dirs) {
|
||||
if (!("directories" in globalData.eleventy)) {
|
||||
globalData.eleventy.directories = {};
|
||||
}
|
||||
|
||||
Object.assign(globalData.eleventy.directories, this.dirs.getUserspaceInstance());
|
||||
}
|
||||
|
||||
// Reserved
|
||||
if (this.config.freezeReservedData) {
|
||||
DeepFreeze(globalData.eleventy);
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
|
||||
async getInitialGlobalData() {
|
||||
if (!this.configApiGlobalData) {
|
||||
this.configApiGlobalData = this.#getInitialGlobalData();
|
||||
}
|
||||
|
||||
return this.configApiGlobalData;
|
||||
}
|
||||
|
||||
async #getGlobalData() {
|
||||
let rawImports = this.getRawImports();
|
||||
let configApiGlobalData = await this.getInitialGlobalData();
|
||||
|
||||
let globalJson = await this.getAllGlobalData();
|
||||
let mergedGlobalData = Merge(globalJson, configApiGlobalData);
|
||||
|
||||
// OK: Shallow merge when combining rawImports (pkg) with global data files
|
||||
return Object.assign({}, mergedGlobalData, rawImports);
|
||||
}
|
||||
|
||||
async getGlobalData() {
|
||||
if (!this.globalData) {
|
||||
this.globalData = this.#getGlobalData();
|
||||
}
|
||||
|
||||
return this.globalData;
|
||||
}
|
||||
|
||||
/* Template and Directory data files */
|
||||
async combineLocalData(localDataPaths) {
|
||||
let localData = {};
|
||||
if (!Array.isArray(localDataPaths)) {
|
||||
localDataPaths = [localDataPaths];
|
||||
}
|
||||
|
||||
// Filter out files we know don't exist to avoid overhead for checking
|
||||
const dataPaths = await Promise.all(
|
||||
localDataPaths.map((path) => {
|
||||
if (this._fsExistsCache.exists(path)) {
|
||||
return path;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
|
||||
localDataPaths = dataPaths.filter((pathOrFalse) => {
|
||||
return pathOrFalse === false ? false : true;
|
||||
});
|
||||
|
||||
this.config.events.emit("eleventy.dataFiles", localDataPaths);
|
||||
|
||||
if (!localDataPaths.length) {
|
||||
return localData;
|
||||
}
|
||||
|
||||
let dataSource = {};
|
||||
for (let path of localDataPaths) {
|
||||
let dataForPath = await this.getDataValue(path);
|
||||
if (!isPlainObject(dataForPath)) {
|
||||
debug(
|
||||
"Warning: Template and Directory data files expect an object to be returned, instead `%o` returned `%o`",
|
||||
path,
|
||||
dataForPath,
|
||||
);
|
||||
} else {
|
||||
// clean up data for template/directory data files only.
|
||||
let cleanedDataForPath = TemplateData.cleanupData(dataForPath);
|
||||
for (let key in cleanedDataForPath) {
|
||||
if (Object.prototype.hasOwnProperty.call(dataSource, key)) {
|
||||
debugWarn(
|
||||
"Local data files have conflicting data. Overwriting '%s' with data from '%s'. Previous data location was from '%s'",
|
||||
key,
|
||||
path,
|
||||
dataSource[key],
|
||||
);
|
||||
}
|
||||
dataSource[key] = path;
|
||||
}
|
||||
TemplateData.mergeDeep(this.config.dataDeepMerge, localData, cleanedDataForPath);
|
||||
}
|
||||
}
|
||||
return localData;
|
||||
}
|
||||
|
||||
async getTemplateDirectoryData(templatePath) {
|
||||
if (!this.templateDirectoryData[templatePath]) {
|
||||
let localDataPaths = await this.getLocalDataPaths(templatePath);
|
||||
let importedData = await this.combineLocalData(localDataPaths);
|
||||
|
||||
this.templateDirectoryData[templatePath] = Object.assign({}, importedData);
|
||||
}
|
||||
return this.templateDirectoryData[templatePath];
|
||||
}
|
||||
|
||||
getUserDataExtensions() {
|
||||
if (!this.config.dataExtensions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// returning extensions in reverse order to create proper extension order
|
||||
// later added formats will override first ones
|
||||
return Array.from(this.config.dataExtensions.keys()).reverse();
|
||||
}
|
||||
|
||||
getUserDataParser(extension) {
|
||||
return this.config.dataExtensions.get(extension);
|
||||
}
|
||||
|
||||
isUserDataExtension(extension) {
|
||||
return this.config.dataExtensions && this.config.dataExtensions.has(extension);
|
||||
}
|
||||
|
||||
hasUserDataExtensions() {
|
||||
return this.config.dataExtensions && this.config.dataExtensions.size > 0;
|
||||
}
|
||||
|
||||
async _parseDataFile(path, parser, options = {}) {
|
||||
let readFile = !("read" in options) || options.read === true;
|
||||
let rawInput;
|
||||
|
||||
if (readFile) {
|
||||
rawInput = EleventyLoadContent(path, options);
|
||||
}
|
||||
|
||||
if (readFile && !rawInput) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
if (readFile) {
|
||||
return parser(rawInput, path);
|
||||
} else {
|
||||
// path as a first argument is when `read: false`
|
||||
// path as a second argument is for consistency with `read: true` API
|
||||
return parser(path, path);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new TemplateDataParseError(`Having trouble parsing data file ${path}`, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ignoreProcessing = false for global data files
|
||||
// ignoreProcessing = true for local data files
|
||||
async getDataValue(path) {
|
||||
let extension = TemplatePath.getExtension(path);
|
||||
|
||||
if (extension === "js" || extension === "cjs" || extension === "mjs") {
|
||||
// JS data file or require’d JSON (no preprocessing needed)
|
||||
let localPath = TemplatePath.absolutePath(path);
|
||||
let exists = this._fsExistsCache.exists(localPath);
|
||||
// Make sure that relative lookups benefit from cache
|
||||
this._fsExistsCache.markExists(path, exists);
|
||||
|
||||
if (!exists) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let aggregateDataBench = this.benchmarks.aggregate.get("Data File");
|
||||
aggregateDataBench.before();
|
||||
let dataBench = this.benchmarks.data.get(`\`${path}\``);
|
||||
dataBench.before();
|
||||
|
||||
let type = "cjs";
|
||||
if (extension === "mjs" || (extension === "js" && this.isEsm)) {
|
||||
type = "esm";
|
||||
}
|
||||
|
||||
// We always need to use `import()`, as `require` isn’t available in ESM.
|
||||
let returnValue = await EleventyImport(localPath, type);
|
||||
|
||||
// TODO special exception for Global data `permalink.js`
|
||||
// module.exports = (data) => `${data.page.filePathStem}/`; // Does not work
|
||||
// module.exports = () => ((data) => `${data.page.filePathStem}/`); // Works
|
||||
if (typeof returnValue === "function") {
|
||||
let configApiGlobalData = await this.getInitialGlobalData();
|
||||
returnValue = await returnValue(configApiGlobalData || {});
|
||||
}
|
||||
|
||||
dataBench.after();
|
||||
aggregateDataBench.after();
|
||||
|
||||
return returnValue;
|
||||
} else if (this.isUserDataExtension(extension)) {
|
||||
// Other extensions
|
||||
let { parser, options } = this.getUserDataParser(extension);
|
||||
|
||||
return this._parseDataFile(path, parser, options);
|
||||
} else if (extension === "json") {
|
||||
// File to string, parse with JSON (preprocess)
|
||||
const parser = (content) => JSON.parse(content);
|
||||
return this._parseDataFile(path, parser);
|
||||
} else {
|
||||
throw new TemplateDataParseError(
|
||||
`Could not find an appropriate data parser for ${path}. Do you need to add a plugin to your config file?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_pushExtensionsToPaths(paths, curpath, extensions) {
|
||||
for (let extension of extensions) {
|
||||
paths.push(curpath + "." + extension);
|
||||
}
|
||||
}
|
||||
|
||||
_addBaseToPaths(paths, base, extensions, nonEmptySuffixesOnly = false) {
|
||||
let suffixes = this.getDataFileSuffixes();
|
||||
|
||||
for (let suffix of suffixes) {
|
||||
suffix = suffix || "";
|
||||
|
||||
if (nonEmptySuffixesOnly && suffix === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// data suffix
|
||||
if (suffix) {
|
||||
paths.push(base + suffix + ".js");
|
||||
paths.push(base + suffix + ".cjs");
|
||||
paths.push(base + suffix + ".mjs");
|
||||
}
|
||||
paths.push(base + suffix + ".json"); // default: .11tydata.json
|
||||
|
||||
// inject user extensions
|
||||
this._pushExtensionsToPaths(paths, base + suffix, extensions);
|
||||
}
|
||||
}
|
||||
|
||||
async getLocalDataPaths(templatePath) {
|
||||
let paths = [];
|
||||
let parsed = path.parse(templatePath);
|
||||
let inputDir = this.inputDir;
|
||||
|
||||
debugDev("getLocalDataPaths(%o)", templatePath);
|
||||
debugDev("parsed.dir: %o", parsed.dir);
|
||||
|
||||
let userExtensions = this.getUserDataExtensions();
|
||||
|
||||
if (parsed.dir) {
|
||||
let fileNameNoExt = this.extensionMap.removeTemplateExtension(parsed.base);
|
||||
|
||||
// default dataSuffix: .11tydata, is appended in _addBaseToPaths
|
||||
debug("Using %o suffixes to find data files.", this.getDataFileSuffixes());
|
||||
|
||||
// Template data file paths
|
||||
let filePathNoExt = parsed.dir + "/" + fileNameNoExt;
|
||||
this._addBaseToPaths(paths, filePathNoExt, userExtensions);
|
||||
|
||||
// Directory data file paths
|
||||
let allDirs = TemplatePath.getAllDirs(parsed.dir);
|
||||
|
||||
debugDev("allDirs: %o", allDirs);
|
||||
for (let dir of allDirs) {
|
||||
let lastDir = TemplatePath.getLastPathSegment(dir);
|
||||
let dirPathNoExt = dir + "/" + lastDir;
|
||||
|
||||
if (inputDir) {
|
||||
debugDev("dirStr: %o; inputDir: %o", dir, inputDir);
|
||||
}
|
||||
if (!inputDir || (dir.startsWith(inputDir) && dir !== inputDir)) {
|
||||
if (this.config.dataFileDirBaseNameOverride) {
|
||||
let indexDataFile = dir + "/" + this.config.dataFileDirBaseNameOverride;
|
||||
this._addBaseToPaths(paths, indexDataFile, userExtensions, true);
|
||||
} else {
|
||||
this._addBaseToPaths(paths, dirPathNoExt, userExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0.11.0+ include root input dir files
|
||||
// if using `docs/` as input dir, looks for docs/docs.json et al
|
||||
if (inputDir) {
|
||||
let lastInputDir = TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(inputDir, TemplatePath.getLastPathSegment(inputDir)),
|
||||
);
|
||||
|
||||
// in root input dir, search for index.11tydata.json et al
|
||||
if (this.config.dataFileDirBaseNameOverride) {
|
||||
let indexDataFile =
|
||||
TemplatePath.getDirFromFilePath(lastInputDir) +
|
||||
"/" +
|
||||
this.config.dataFileDirBaseNameOverride;
|
||||
this._addBaseToPaths(paths, indexDataFile, userExtensions, true);
|
||||
} else if (lastInputDir !== "./") {
|
||||
this._addBaseToPaths(paths, lastInputDir, userExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug("getLocalDataPaths(%o): %o", templatePath, paths);
|
||||
return unique(paths).reverse();
|
||||
}
|
||||
|
||||
static mergeDeep(deepMerge, target, ...source) {
|
||||
if (!deepMerge && deepMerge !== undefined) {
|
||||
return Object.assign(target, ...source);
|
||||
} else {
|
||||
return TemplateData.merge(target, ...source);
|
||||
}
|
||||
}
|
||||
|
||||
static merge(target, ...source) {
|
||||
return Merge(target, ...source);
|
||||
}
|
||||
|
||||
/* Like cleanupData() but does not mutate */
|
||||
static getCleanedTagsImmutable(data) {
|
||||
let tags = [];
|
||||
|
||||
if (isPlainObject(data) && data.tags) {
|
||||
if (typeof data.tags === "string") {
|
||||
tags = (data.tags || "").split(",");
|
||||
} else if (Array.isArray(data.tags)) {
|
||||
tags = data.tags;
|
||||
}
|
||||
|
||||
// Deduplicate tags
|
||||
return [...new Set(tags)];
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
static cleanupData(data) {
|
||||
if (isPlainObject(data) && "tags" in data) {
|
||||
if (typeof data.tags === "string") {
|
||||
data.tags = data.tags ? [data.tags] : [];
|
||||
} else if (data.tags === null) {
|
||||
data.tags = [];
|
||||
}
|
||||
|
||||
// Deduplicate tags
|
||||
data.tags = [...new Set(data.tags)];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static getNormalizedExcludedCollections(data) {
|
||||
let excludes = [];
|
||||
let key = "eleventyExcludeFromCollections";
|
||||
|
||||
if (data?.[key] !== true) {
|
||||
if (Array.isArray(data[key])) {
|
||||
excludes = data[key];
|
||||
} else if (typeof data[key] === "string") {
|
||||
excludes = (data[key] || "").split(",");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
excludes,
|
||||
excludeAll: data?.eleventyExcludeFromCollections === true,
|
||||
};
|
||||
}
|
||||
|
||||
/* Same as getIncludedTagNames() but may also include "all" */
|
||||
static getIncludedCollectionNames(data) {
|
||||
let tags = TemplateData.getCleanedTagsImmutable(data);
|
||||
|
||||
if (tags.length > 0) {
|
||||
let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
|
||||
if (excludeAll) {
|
||||
return [];
|
||||
} else {
|
||||
return ["all", ...tags].filter((tag) => !excludes.includes(tag));
|
||||
}
|
||||
} else {
|
||||
return ["all"];
|
||||
}
|
||||
}
|
||||
|
||||
static getIncludedTagNames(data) {
|
||||
let tags = TemplateData.getCleanedTagsImmutable(data);
|
||||
|
||||
if (tags.length > 0) {
|
||||
let { excludes, excludeAll } = TemplateData.getNormalizedExcludedCollections(data);
|
||||
if (excludeAll) {
|
||||
return [];
|
||||
} else {
|
||||
return tags.filter((tag) => !excludes.includes(tag));
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateData;
|
||||
40
node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js
generated
vendored
Normal file
40
node_modules/@11ty/eleventy/src/Data/TemplateDataInitialGlobalData.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
const { set: lodashSet } = lodash;
|
||||
|
||||
class TemplateDataConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateDataInitialGlobalData {
|
||||
constructor(templateConfig) {
|
||||
if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplateDataConfigError("Missing or invalid `templateConfig` (via Render plugin).");
|
||||
}
|
||||
this.templateConfig = templateConfig;
|
||||
this.config = this.templateConfig.getConfig();
|
||||
}
|
||||
|
||||
async getData() {
|
||||
let globalData = {};
|
||||
|
||||
// via eleventyConfig.addGlobalData
|
||||
if (this.config.globalData) {
|
||||
let keys = Object.keys(this.config.globalData);
|
||||
for (let key of keys) {
|
||||
let returnValue = this.config.globalData[key];
|
||||
|
||||
// This section is problematic when used with eleventyComputed #3389
|
||||
if (typeof returnValue === "function") {
|
||||
returnValue = await returnValue();
|
||||
}
|
||||
|
||||
lodashSet(globalData, key, returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
return globalData;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateDataInitialGlobalData;
|
||||
1472
node_modules/@11ty/eleventy/src/Eleventy.js
generated
vendored
Normal file
1472
node_modules/@11ty/eleventy/src/Eleventy.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
43
node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs
generated
vendored
Normal file
43
node_modules/@11ty/eleventy/src/EleventyCommonJs.cjs
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
function canRequireModules() {
|
||||
// via --experimental-require-module or newer than Node 22 support when this flag is no longer necessary
|
||||
try {
|
||||
require("./Util/Objects/SampleModule.mjs");
|
||||
return true;
|
||||
} catch(e) {
|
||||
if(e.code === "ERR_REQUIRE_ESM") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rethrow if not an ESM require error.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if(!canRequireModules()) {
|
||||
let error = new Error(`\`require("@11ty/eleventy")\` is incompatible with Eleventy v3 and this version of Node. You have a few options:
|
||||
1. (Easiest) Change the \`require\` to use a dynamic import inside of an asynchronous CommonJS configuration
|
||||
callback, for example:
|
||||
|
||||
module.exports = async function {
|
||||
const {EleventyRenderPlugin, EleventyI18nPlugin, EleventyHtmlBasePlugin} = await import("@11ty/eleventy");
|
||||
}
|
||||
|
||||
2. (Easier) Update the JavaScript syntax in your configuration file from CommonJS to ESM (change \`require\`
|
||||
to use \`import\` and rename the file to have an \`.mjs\` file extension).
|
||||
|
||||
3. (More work) Change your project to use ESM-first by adding \`"type": "module"\` to your package.json. Any
|
||||
\`.js\` will need to be ported to use ESM syntax (or renamed to \`.cjs\`.)
|
||||
|
||||
4. (Short term workaround) Use the --experimental-require-module flag to enable this behavior. Read
|
||||
more: https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require It is possible that the
|
||||
newest version of Node has this enabled by default—you can try upgrading your version of Node.js.`);
|
||||
|
||||
error.skipOriginalStack = true;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// If we made it here require(ESM) works fine (via --experimental-require-module or newer Node.js defaults)
|
||||
let mod = require("./Eleventy.js");
|
||||
|
||||
module.exports = mod;
|
||||
284
node_modules/@11ty/eleventy/src/EleventyExtensionMap.js
generated
vendored
Normal file
284
node_modules/@11ty/eleventy/src/EleventyExtensionMap.js
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
|
||||
class EleventyExtensionMapConfigError extends EleventyBaseError {}
|
||||
|
||||
class EleventyExtensionMap {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this._spiderJsDepsCache = {};
|
||||
|
||||
/** @type {Array} */
|
||||
this.validTemplateLanguageKeys;
|
||||
}
|
||||
|
||||
setFormats(formatKeys = []) {
|
||||
// raw
|
||||
this.formatKeys = formatKeys;
|
||||
|
||||
this.unfilteredFormatKeys = formatKeys.map(function (key) {
|
||||
return key.trim().toLowerCase();
|
||||
});
|
||||
|
||||
this.validTemplateLanguageKeys = this.unfilteredFormatKeys.filter((key) =>
|
||||
this.hasExtension(key),
|
||||
);
|
||||
|
||||
this.passthroughCopyKeys = this.unfilteredFormatKeys.filter((key) => !this.hasExtension(key));
|
||||
}
|
||||
|
||||
set config(cfg) {
|
||||
if (!cfg || cfg.constructor.name !== "TemplateConfig") {
|
||||
throw new EleventyExtensionMapConfigError(
|
||||
"Missing or invalid `config` argument (via setter).",
|
||||
);
|
||||
}
|
||||
this.eleventyConfig = cfg;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
get engineManager() {
|
||||
if (!this._engineManager) {
|
||||
this._engineManager = new TemplateEngineManager(this.eleventyConfig);
|
||||
}
|
||||
|
||||
return this._engineManager;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.engineManager.reset();
|
||||
}
|
||||
|
||||
/* Used for layout path resolution */
|
||||
getFileList(path, dir) {
|
||||
if (!path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let files = [];
|
||||
this.validTemplateLanguageKeys.forEach((key) => {
|
||||
this.getExtensionsFromKey(key).forEach(function (extension) {
|
||||
files.push((dir ? dir + "/" : "") + path + "." + extension);
|
||||
});
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
// Warning: this would false positive on an include, but is only used
|
||||
// on paths found from the file system glob search.
|
||||
// TODO: Method name might just need to be renamed to something more accurate.
|
||||
isFullTemplateFilePath(path) {
|
||||
for (let extension of this.validTemplateLanguageKeys) {
|
||||
if (path.endsWith(`.${extension}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getCustomExtensionEntry(extension) {
|
||||
if (!this.config.extensionMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let entry of this.config.extensionMap) {
|
||||
if (entry.extension === extension) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValidExtensionsForPath(path) {
|
||||
let extensions = new Set();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (path.endsWith(`.${extension}`)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
// if multiple extensions are valid, sort from longest to shortest
|
||||
// e.g. .11ty.js and .js
|
||||
let sorted = Array.from(extensions)
|
||||
.filter((extension) => this.validTemplateLanguageKeys.includes(extension))
|
||||
.sort((a, b) => b.length - a.length);
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
async shouldSpiderJavaScriptDependencies(path) {
|
||||
let extensions = this.getValidExtensionsForPath(path);
|
||||
for (let extension of extensions) {
|
||||
if (extension in this._spiderJsDepsCache) {
|
||||
return this._spiderJsDepsCache[extension];
|
||||
}
|
||||
|
||||
let cls = await this.engineManager.getEngineClassByExtension(extension);
|
||||
if (cls) {
|
||||
let entry = this.getCustomExtensionEntry(extension);
|
||||
let shouldSpider = cls.shouldSpiderJavaScriptDependencies(entry);
|
||||
this._spiderJsDepsCache[extension] = shouldSpider;
|
||||
return shouldSpider;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getPassthroughCopyGlobs(inputDir) {
|
||||
return this._getGlobs(this.passthroughCopyKeys, inputDir);
|
||||
}
|
||||
|
||||
getValidGlobs(inputDir) {
|
||||
return this._getGlobs(this.validTemplateLanguageKeys, inputDir);
|
||||
}
|
||||
|
||||
getGlobs(inputDir) {
|
||||
return this._getGlobs(this.unfilteredFormatKeys, inputDir);
|
||||
}
|
||||
|
||||
_getGlobs(formatKeys, inputDir) {
|
||||
let dir = TemplatePath.convertToRecursiveGlobSync(inputDir);
|
||||
let extensions = new Set();
|
||||
|
||||
for (let key of formatKeys) {
|
||||
if (this.hasExtension(key)) {
|
||||
for (let extension of this.getExtensionsFromKey(key)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
} else {
|
||||
extensions.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions.size === 1) {
|
||||
return [`${dir}/*.${Array.from(extensions)[0]}`];
|
||||
} else if (extensions.size > 1) {
|
||||
return [
|
||||
// extra curly brackets /*.{cjs,txt}
|
||||
`${dir}/*.{${Array.from(extensions).join(",")}}`,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
hasExtension(key) {
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (
|
||||
this.extensionToKeyMap[extension].key === key ||
|
||||
this.extensionToKeyMap[extension].aliasKey === key
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getExtensionsFromKey(key) {
|
||||
let extensions = new Set();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (this.extensionToKeyMap[extension].aliasKey) {
|
||||
// only add aliased extension if explicitly referenced in formats
|
||||
// overrides will not have an aliasKey (md => md)
|
||||
if (this.extensionToKeyMap[extension].aliasKey === key) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
} else if (this.extensionToKeyMap[extension].key === key) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(extensions);
|
||||
}
|
||||
|
||||
// Only `addExtension` configuration API extensions
|
||||
getExtensionEntriesFromKey(key) {
|
||||
let entries = new Set();
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
if (entry.key === key) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(entries);
|
||||
}
|
||||
|
||||
// Determines whether a path is a passthrough copy file or a template (via TemplateWriter)
|
||||
hasEngine(pathOrKey) {
|
||||
return !!this.getKey(pathOrKey);
|
||||
}
|
||||
|
||||
getKey(pathOrKey) {
|
||||
pathOrKey = (pathOrKey || "").toLowerCase();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (pathOrKey === extension || pathOrKey.endsWith("." + extension)) {
|
||||
let key =
|
||||
this.extensionToKeyMap[extension].aliasKey || this.extensionToKeyMap[extension].key;
|
||||
// must be a valid format key passed (e.g. via --formats)
|
||||
if (this.validTemplateLanguageKeys.includes(key)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionEntry(pathOrKey) {
|
||||
pathOrKey = (pathOrKey || "").toLowerCase();
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (pathOrKey === extension || pathOrKey.endsWith("." + extension)) {
|
||||
return this.extensionToKeyMap[extension];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeTemplateExtension(path) {
|
||||
for (let extension in this.extensionToKeyMap) {
|
||||
if (path === extension || path.endsWith("." + extension)) {
|
||||
return path.slice(
|
||||
0,
|
||||
path.length - 1 - extension.length < 0 ? 0 : path.length - 1 - extension.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// keys are file extensions
|
||||
// values are template language keys
|
||||
get extensionToKeyMap() {
|
||||
if (!this._extensionToKeyMap) {
|
||||
this._extensionToKeyMap = {
|
||||
md: { key: "md", extension: "md" },
|
||||
html: { key: "html", extension: "html" },
|
||||
njk: { key: "njk", extension: "njk" },
|
||||
liquid: { key: "liquid", extension: "liquid" },
|
||||
"11ty.js": { key: "11ty.js", extension: "11ty.js" },
|
||||
"11ty.cjs": { key: "11ty.js", extension: "11ty.cjs" },
|
||||
"11ty.mjs": { key: "11ty.js", extension: "11ty.mjs" },
|
||||
};
|
||||
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
// extension and key are only different when aliasing.
|
||||
this._extensionToKeyMap[entry.extension] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._extensionToKeyMap;
|
||||
}
|
||||
|
||||
getReadableFileExtensions() {
|
||||
return Object.keys(this.extensionToKeyMap).join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyExtensionMap;
|
||||
517
node_modules/@11ty/eleventy/src/EleventyFiles.js
generated
vendored
Normal file
517
node_modules/@11ty/eleventy/src/EleventyFiles.js
generated
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import TemplateGlob from "./TemplateGlob.js";
|
||||
import TemplatePassthroughManager from "./TemplatePassthroughManager.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EleventyFiles");
|
||||
|
||||
class EleventyFilesError extends EleventyBaseError {}
|
||||
|
||||
class EleventyFiles {
|
||||
constructor(formats, eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new EleventyFilesError("Missing `eleventyConfig`` argument.");
|
||||
}
|
||||
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = eleventyConfig.getConfig();
|
||||
this.aggregateBench = this.config.benchmarkManager.get("Aggregate");
|
||||
|
||||
this.formats = formats;
|
||||
this.eleventyIgnoreContent = false;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
get includesDir() {
|
||||
return this.dirs.includes;
|
||||
}
|
||||
|
||||
get layoutsDir() {
|
||||
return this.dirs.layouts;
|
||||
}
|
||||
|
||||
get dataDir() {
|
||||
return this.dirs.data;
|
||||
}
|
||||
|
||||
// Backwards compat
|
||||
getDataDir() {
|
||||
return this.dataDir;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.dirs.inputFile || this.dirs.inputGlob) {
|
||||
this.templateGlobs = TemplateGlob.map([this.dirs.inputFile || this.dirs.inputGlob]);
|
||||
} else {
|
||||
// Input is a directory
|
||||
this.templateGlobs = this.extensionMap.getGlobs(this.inputDir);
|
||||
}
|
||||
|
||||
this.initPassthroughManager();
|
||||
this.setupGlobs();
|
||||
}
|
||||
|
||||
get validTemplateGlobs() {
|
||||
if (!this._validTemplateGlobs) {
|
||||
let globs;
|
||||
// Input is a file
|
||||
if (this.inputFile) {
|
||||
globs = this.templateGlobs;
|
||||
} else {
|
||||
// input is a directory
|
||||
globs = this.extensionMap.getValidGlobs(this.inputDir);
|
||||
}
|
||||
this._validTemplateGlobs = globs;
|
||||
}
|
||||
return this._validTemplateGlobs;
|
||||
}
|
||||
|
||||
get passthroughGlobs() {
|
||||
let paths = new Set();
|
||||
// stuff added in addPassthroughCopy()
|
||||
for (let path of this.passthroughManager.getConfigPathGlobs()) {
|
||||
paths.add(path);
|
||||
}
|
||||
// non-template language extensions
|
||||
for (let path of this.extensionMap.getPassthroughCopyGlobs(this.inputDir)) {
|
||||
paths.add(path);
|
||||
}
|
||||
return Array.from(paths);
|
||||
}
|
||||
|
||||
restart() {
|
||||
this.passthroughManager.reset();
|
||||
this.setupGlobs();
|
||||
this._glob = null;
|
||||
}
|
||||
|
||||
/* For testing */
|
||||
_setConfig(config) {
|
||||
if (!config.ignores) {
|
||||
config.ignores = new Set();
|
||||
config.ignores.add("**/node_modules/**");
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/* Set command root for local project paths */
|
||||
// This is only used by tests
|
||||
_setLocalPathRoot(dir) {
|
||||
this.localPathRoot = dir;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
// for tests
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats(this.formats);
|
||||
this._extensionMap.config = this.eleventyConfig;
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.runMode = runMode;
|
||||
}
|
||||
|
||||
initPassthroughManager() {
|
||||
let mgr = new TemplatePassthroughManager(this.eleventyConfig);
|
||||
mgr.setRunMode(this.runMode);
|
||||
mgr.extensionMap = this.extensionMap;
|
||||
mgr.setFileSystemSearch(this.fileSystemSearch);
|
||||
this.passthroughManager = mgr;
|
||||
}
|
||||
|
||||
getPassthroughManager() {
|
||||
return this.passthroughManager;
|
||||
}
|
||||
|
||||
setPassthroughManager(mgr) {
|
||||
mgr.extensionMap = this.extensionMap;
|
||||
this.passthroughManager = mgr;
|
||||
}
|
||||
|
||||
set templateData(templateData) {
|
||||
this._templateData = templateData;
|
||||
}
|
||||
|
||||
get templateData() {
|
||||
if (!this._templateData) {
|
||||
this._templateData = new TemplateData(this.eleventyConfig);
|
||||
}
|
||||
|
||||
return this._templateData;
|
||||
}
|
||||
|
||||
setupGlobs() {
|
||||
this.fileIgnores = this.getIgnores();
|
||||
this.extraIgnores = this._getIncludesAndDataDirs();
|
||||
this.uniqueIgnores = this.getIgnoreGlobs();
|
||||
|
||||
// Conditional added for tests that don’t have a config
|
||||
if (this.config?.events) {
|
||||
this.config.events.emit("eleventy.ignores", this.uniqueIgnores);
|
||||
}
|
||||
|
||||
this.normalizedTemplateGlobs = this.templateGlobs;
|
||||
}
|
||||
|
||||
getIgnoreGlobs() {
|
||||
let uniqueIgnores = new Set();
|
||||
for (let ignore of this.fileIgnores) {
|
||||
uniqueIgnores.add(ignore);
|
||||
}
|
||||
for (let ignore of this.extraIgnores) {
|
||||
uniqueIgnores.add(ignore);
|
||||
}
|
||||
// Placing the config ignores last here is important to the tests
|
||||
for (let ignore of this.config.ignores) {
|
||||
uniqueIgnores.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
|
||||
}
|
||||
return Array.from(uniqueIgnores);
|
||||
}
|
||||
|
||||
static getFileIgnores(ignoreFiles) {
|
||||
if (!Array.isArray(ignoreFiles)) {
|
||||
ignoreFiles = [ignoreFiles];
|
||||
}
|
||||
|
||||
let ignores = [];
|
||||
for (let ignorePath of ignoreFiles) {
|
||||
ignorePath = TemplatePath.normalize(ignorePath);
|
||||
|
||||
let dir = TemplatePath.getDirFromFilePath(ignorePath);
|
||||
|
||||
if (fs.existsSync(ignorePath) && fs.statSync(ignorePath).size > 0) {
|
||||
let ignoreContent = fs.readFileSync(ignorePath, "utf8");
|
||||
|
||||
ignores = ignores.concat(EleventyFiles.normalizeIgnoreContent(dir, ignoreContent));
|
||||
}
|
||||
}
|
||||
|
||||
ignores.forEach((path) => debug(`${ignoreFiles} ignoring: ${path}`));
|
||||
|
||||
return ignores;
|
||||
}
|
||||
|
||||
static normalizeIgnoreContent(dir, ignoreContent) {
|
||||
let ignores = [];
|
||||
|
||||
if (ignoreContent) {
|
||||
ignores = ignoreContent
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
return line.trim();
|
||||
})
|
||||
.filter((line) => {
|
||||
if (line.charAt(0) === "!") {
|
||||
debug(
|
||||
">>> When processing .gitignore/.eleventyignore, Eleventy does not currently support negative patterns but encountered one:",
|
||||
);
|
||||
debug(">>>", line);
|
||||
debug("Follow along at https://github.com/11ty/eleventy/issues/693 to track support.");
|
||||
}
|
||||
|
||||
// empty lines or comments get filtered out
|
||||
return line.length > 0 && line.charAt(0) !== "#" && line.charAt(0) !== "!";
|
||||
})
|
||||
.map((line) => {
|
||||
let path = TemplateGlob.normalizePath(dir, "/", line);
|
||||
path = TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(path));
|
||||
|
||||
try {
|
||||
// Note these folders must exist to get /** suffix
|
||||
let stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
return path + "/**";
|
||||
}
|
||||
return path;
|
||||
} catch (e) {
|
||||
return path;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ignores;
|
||||
}
|
||||
|
||||
setEleventyIgnoreContent(content) {
|
||||
this.eleventyIgnoreContent = content;
|
||||
}
|
||||
|
||||
getIgnores() {
|
||||
let files = new Set();
|
||||
|
||||
for (let ignore of EleventyFiles.getFileIgnores(this.getIgnoreFiles())) {
|
||||
files.add(ignore);
|
||||
}
|
||||
|
||||
// testing API
|
||||
if (this.eleventyIgnoreContent !== false) {
|
||||
files.add(this.eleventyIgnoreContent);
|
||||
}
|
||||
|
||||
// ignore output dir (unless this excludes all input)
|
||||
// input: . and output: . (skip)
|
||||
// input: ./content and output . (skip)
|
||||
// input: . and output: ./_site (add)
|
||||
if (!this.inputDir.startsWith(this.outputDir)) {
|
||||
// both are already normalized in 3.0
|
||||
files.add(TemplateGlob.map(this.outputDir + "/**"));
|
||||
}
|
||||
|
||||
return Array.from(files);
|
||||
}
|
||||
|
||||
getIgnoreFiles() {
|
||||
let ignoreFiles = new Set();
|
||||
let rootDirectory = this.localPathRoot || ".";
|
||||
|
||||
if (this.config.useGitIgnore) {
|
||||
ignoreFiles.add(TemplatePath.join(rootDirectory, ".gitignore"));
|
||||
}
|
||||
|
||||
if (this.eleventyIgnoreContent === false) {
|
||||
let absoluteInputDir = TemplatePath.absolutePath(this.inputDir);
|
||||
ignoreFiles.add(TemplatePath.join(rootDirectory, ".eleventyignore"));
|
||||
if (rootDirectory !== absoluteInputDir) {
|
||||
ignoreFiles.add(TemplatePath.join(this.inputDir, ".eleventyignore"));
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(ignoreFiles);
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getLayoutsDir() {
|
||||
return this.layoutsDir;
|
||||
}
|
||||
|
||||
getFileGlobs() {
|
||||
return this.normalizedTemplateGlobs;
|
||||
}
|
||||
|
||||
getRawFiles() {
|
||||
return this.templateGlobs;
|
||||
}
|
||||
|
||||
async getWatchPathCache() {
|
||||
// Issue #1325: make sure passthrough copy files are not included here
|
||||
if (!this.pathCache) {
|
||||
throw new Error("Watching requires `.getFiles()` to be called first in EleventyFiles");
|
||||
}
|
||||
|
||||
let ret = [];
|
||||
// Filter out the passthrough copy paths.
|
||||
for (let path of this.pathCache) {
|
||||
if (
|
||||
this.extensionMap.isFullTemplateFilePath(path) &&
|
||||
(await this.extensionMap.shouldSpiderJavaScriptDependencies(path))
|
||||
) {
|
||||
ret.push(path);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
_globSearch() {
|
||||
let globs = this.getFileGlobs();
|
||||
|
||||
// returns a promise
|
||||
debug("Searching for: %o", globs);
|
||||
return this.fileSystemSearch.search("templates", globs, {
|
||||
ignore: this.uniqueIgnores,
|
||||
});
|
||||
}
|
||||
|
||||
getPathsWithVirtualTemplates(paths) {
|
||||
// Support for virtual templates added in 3.0
|
||||
if (this.config.virtualTemplates && isPlainObject(this.config.virtualTemplates)) {
|
||||
let virtualTemplates = Object.keys(this.config.virtualTemplates)
|
||||
.filter((path) => {
|
||||
// Filter out includes/layouts
|
||||
return this.dirs.isTemplateFile(path);
|
||||
})
|
||||
.map((path) => {
|
||||
let fullVirtualPath = this.dirs.getInputPath(path);
|
||||
if (!this.extensionMap.getKey(fullVirtualPath)) {
|
||||
this.eleventyConfig.logger.warn(
|
||||
`The virtual template at ${fullVirtualPath} is using a template format that’s not valid for your project. Your project is using: "${this.formats}". Read more about formats: https://v3.11ty.dev/docs/config/#template-formats`,
|
||||
);
|
||||
}
|
||||
return fullVirtualPath;
|
||||
});
|
||||
|
||||
paths = paths.concat(virtualTemplates);
|
||||
|
||||
// Virtual templates can not live at the same place as files on the file system!
|
||||
if (paths.length !== new Set(paths).size) {
|
||||
let conflicts = {};
|
||||
for (let path of paths) {
|
||||
if (conflicts[path]) {
|
||||
throw new Error(
|
||||
`A virtual template had the same path as a file on the file system: "${path}"`,
|
||||
);
|
||||
}
|
||||
|
||||
conflicts[path] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
async getFiles() {
|
||||
let bench = this.aggregateBench.get("Searching the file system (templates)");
|
||||
bench.before();
|
||||
let globResults = await this._globSearch();
|
||||
let paths = TemplatePath.addLeadingDotSlashArray(globResults);
|
||||
bench.after();
|
||||
|
||||
// Note 2.0.0-canary.19 removed a `filter` option for custom template syntax here that was unpublished and unused.
|
||||
|
||||
paths = this.getPathsWithVirtualTemplates(paths);
|
||||
|
||||
this.pathCache = paths;
|
||||
return paths;
|
||||
}
|
||||
|
||||
getFileShape(paths, filePath) {
|
||||
if (!filePath) {
|
||||
return;
|
||||
}
|
||||
if (this.isPassthroughCopyFile(paths, filePath)) {
|
||||
return "copy";
|
||||
}
|
||||
if (this.isFullTemplateFile(paths, filePath)) {
|
||||
return "template";
|
||||
}
|
||||
// include/layout/unknown
|
||||
}
|
||||
|
||||
isPassthroughCopyFile(paths, filePath) {
|
||||
return this.passthroughManager.isPassthroughCopyFile(paths, filePath);
|
||||
}
|
||||
|
||||
// Assumption here that filePath is not a passthrough copy file
|
||||
isFullTemplateFile(paths, filePath) {
|
||||
if (!filePath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let path of paths) {
|
||||
if (path === filePath) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
getGlobWatcherFiles() {
|
||||
// TODO improvement: tie the includes and data to specific file extensions (currently using `**`)
|
||||
let directoryGlobs = this._getIncludesAndDataDirs();
|
||||
|
||||
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
||||
return this.validTemplateGlobs.concat(directoryGlobs);
|
||||
}
|
||||
|
||||
// Revert to old passthroughcopy copy files behavior
|
||||
return this.validTemplateGlobs.concat(this.passthroughGlobs).concat(directoryGlobs);
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
getGlobWatcherFilesForPassthroughCopy() {
|
||||
return this.passthroughGlobs;
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
async getGlobWatcherTemplateDataFiles() {
|
||||
let templateData = this.templateData;
|
||||
return await templateData.getTemplateDataFileGlob();
|
||||
}
|
||||
|
||||
/* For `eleventy --watch` */
|
||||
// TODO this isn’t great but reduces complexity avoiding using TemplateData:getLocalDataPaths for each template in the cache
|
||||
async getWatcherTemplateJavaScriptDataFiles() {
|
||||
let globs = this.templateData.getTemplateJavaScriptDataFileGlob();
|
||||
let bench = this.aggregateBench.get("Searching the file system (watching)");
|
||||
bench.before();
|
||||
let results = TemplatePath.addLeadingDotSlashArray(
|
||||
await this.fileSystemSearch.search("js-dependencies", globs, {
|
||||
ignore: ["**/node_modules/**"],
|
||||
}),
|
||||
);
|
||||
bench.after();
|
||||
return results;
|
||||
}
|
||||
|
||||
/* Ignored by `eleventy --watch` */
|
||||
getGlobWatcherIgnores() {
|
||||
// convert to format without ! since they are passed in as a separate argument to glob watcher
|
||||
let entries = new Set(
|
||||
this.fileIgnores.map((ignore) => TemplatePath.stripLeadingDotSlash(ignore)),
|
||||
);
|
||||
|
||||
for (let ignore of this.config.watchIgnores) {
|
||||
entries.add(TemplateGlob.normalizePath(this.localPathRoot || ".", ignore));
|
||||
}
|
||||
|
||||
// de-duplicated
|
||||
return Array.from(entries);
|
||||
}
|
||||
|
||||
_getIncludesAndDataDirs() {
|
||||
let rawPaths = new Set();
|
||||
rawPaths.add(this.includesDir);
|
||||
if (this.layoutsDir) {
|
||||
rawPaths.add(this.layoutsDir);
|
||||
}
|
||||
rawPaths.add(this.dataDir);
|
||||
|
||||
return Array.from(rawPaths)
|
||||
.filter((entry) => {
|
||||
// never ignore the input directory (even if config file returns "" for these)
|
||||
return entry && entry !== this.inputDir;
|
||||
})
|
||||
.map((entry) => {
|
||||
return TemplateGlob.map(entry + "**");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyFiles;
|
||||
305
node_modules/@11ty/eleventy/src/EleventyServe.js
generated
vendored
Normal file
305
node_modules/@11ty/eleventy/src/EleventyServe.js
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
import assert from "node:assert";
|
||||
|
||||
import debugUtil from "debug";
|
||||
import { Merge, DeepCopy, TemplatePath } from "@11ty/eleventy-utils";
|
||||
import EleventyDevServer from "@11ty/eleventy-dev-server";
|
||||
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import ConsoleLogger from "./Util/ConsoleLogger.js";
|
||||
import PathPrefixer from "./Util/PathPrefixer.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
import { getModulePackageJson } from "./Util/ImportJsonSync.js";
|
||||
import { EleventyImport } from "./Util/Require.js";
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EleventyServe");
|
||||
|
||||
class EleventyServeConfigError extends EleventyBaseError {}
|
||||
|
||||
const DEFAULT_SERVER_OPTIONS = {
|
||||
module: "@11ty/eleventy-dev-server",
|
||||
port: 8080,
|
||||
// pathPrefix: "/",
|
||||
// setup: function() {},
|
||||
// logger: { info: function() {}, error: function() {} }
|
||||
};
|
||||
|
||||
class EleventyServe {
|
||||
constructor() {
|
||||
this.logger = new ConsoleLogger();
|
||||
this._initOptionsFetched = false;
|
||||
this._aliases = undefined;
|
||||
this._watchedFiles = new Set();
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this.eleventyConfig) {
|
||||
throw new EleventyServeConfigError(
|
||||
"You need to set the eleventyConfig property on EleventyServe.",
|
||||
);
|
||||
}
|
||||
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
set config(config) {
|
||||
throw new Error("It’s not allowed to set config on EleventyServe. Set eleventyConfig instead.");
|
||||
}
|
||||
|
||||
setAliases(aliases) {
|
||||
this._aliases = aliases;
|
||||
|
||||
if (this._server && "setAliases" in this._server) {
|
||||
this._server.setAliases(aliases);
|
||||
}
|
||||
}
|
||||
|
||||
get eleventyConfig() {
|
||||
if (!this._eleventyConfig) {
|
||||
throw new EleventyServeConfigError(
|
||||
"You need to set the eleventyConfig property on EleventyServe.",
|
||||
);
|
||||
}
|
||||
|
||||
return this._eleventyConfig;
|
||||
}
|
||||
|
||||
set eleventyConfig(config) {
|
||||
this._eleventyConfig = config;
|
||||
if (checkPassthroughCopyBehavior(this._eleventyConfig.userConfig, "serve")) {
|
||||
this._eleventyConfig.userConfig.events.on("eleventy.passthrough", ({ map }) => {
|
||||
// for-free passthrough copy
|
||||
this.setAliases(map);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO directorynorm
|
||||
setOutputDir(outputDir) {
|
||||
// TODO check if this is different and if so, restart server (if already running)
|
||||
// This applies if you change the output directory in your config file during watch/serve
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
async getServerModule(name) {
|
||||
try {
|
||||
if (!name || name === DEFAULT_SERVER_OPTIONS.module) {
|
||||
return EleventyDevServer;
|
||||
}
|
||||
|
||||
// Look for peer dep in local project
|
||||
let projectNodeModulesPath = TemplatePath.absolutePath("./node_modules/");
|
||||
let serverPath = TemplatePath.absolutePath(projectNodeModulesPath, name);
|
||||
// No references outside of the project node_modules are allowed
|
||||
if (!serverPath.startsWith(projectNodeModulesPath)) {
|
||||
throw new Error("Invalid node_modules name for Eleventy server instance, received:" + name);
|
||||
}
|
||||
|
||||
let serverPackageJson = getModulePackageJson(serverPath);
|
||||
// Normalize with `main` entry from
|
||||
if (TemplatePath.isDirectorySync(serverPath)) {
|
||||
if (serverPackageJson.main) {
|
||||
serverPath = TemplatePath.absolutePath(
|
||||
projectNodeModulesPath,
|
||||
name,
|
||||
serverPackageJson.main,
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Eleventy server ${name} is missing a \`main\` entry in its package.json file. Traversed up from ${serverPath}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let module = await EleventyImport(serverPath);
|
||||
|
||||
if (!("getServer" in module)) {
|
||||
throw new Error(
|
||||
`Eleventy server module requires a \`getServer\` static method. Could not find one on module: \`${name}\``,
|
||||
);
|
||||
}
|
||||
|
||||
if (serverPackageJson["11ty"]?.compatibility) {
|
||||
try {
|
||||
this.eleventyConfig.userConfig.versionCheck(serverPackageJson["11ty"].compatibility);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Warning: \`${name}\` Plugin Compatibility: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return module;
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
"There was an error with your custom Eleventy server. We’re using the default server instead.\n" +
|
||||
e.message,
|
||||
);
|
||||
debug("Eleventy server error %o", e);
|
||||
return EleventyDevServer;
|
||||
}
|
||||
}
|
||||
|
||||
get options() {
|
||||
if (this._options) {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
this._options = Object.assign(
|
||||
{
|
||||
pathPrefix: PathPrefixer.normalizePathPrefix(this.config.pathPrefix),
|
||||
logger: this.logger,
|
||||
},
|
||||
DEFAULT_SERVER_OPTIONS,
|
||||
this.config.serverOptions,
|
||||
);
|
||||
|
||||
this._savedConfigOptions = DeepCopy({}, this.config.serverOptions);
|
||||
|
||||
if (!this._initOptionsFetched && this.getSetupCallback()) {
|
||||
throw new Error(
|
||||
"Init options have not yet been fetched in the setup callback. This probably means that `init()` has not yet been called.",
|
||||
);
|
||||
}
|
||||
|
||||
return this._options;
|
||||
}
|
||||
|
||||
get server() {
|
||||
if (!this._server) {
|
||||
throw new Error("Missing server instance. Did you call .initServerInstance?");
|
||||
}
|
||||
|
||||
return this._server;
|
||||
}
|
||||
|
||||
async initServerInstance() {
|
||||
if (this._server) {
|
||||
return;
|
||||
}
|
||||
|
||||
let serverModule = await this.getServerModule(this.options.module);
|
||||
|
||||
// Static method `getServer` was already checked in `getServerModule`
|
||||
this._server = serverModule.getServer("eleventy-server", this.outputDir, this.options);
|
||||
|
||||
this.setAliases(this._aliases);
|
||||
|
||||
if (this._globsNeedWatching) {
|
||||
this._server.watchFiles(this._watchedFiles);
|
||||
this._globsNeedWatching = false;
|
||||
}
|
||||
}
|
||||
|
||||
getSetupCallback() {
|
||||
let setupCallback = this.config.serverOptions.setup;
|
||||
if (setupCallback && typeof setupCallback === "function") {
|
||||
return setupCallback;
|
||||
}
|
||||
}
|
||||
|
||||
async #init() {
|
||||
let setupCallback = this.getSetupCallback();
|
||||
if (setupCallback) {
|
||||
let opts = await setupCallback();
|
||||
this._initOptionsFetched = true;
|
||||
|
||||
if (opts) {
|
||||
Merge(this.options, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this._initPromise) {
|
||||
this._initPromise = this.#init();
|
||||
}
|
||||
|
||||
return this._initPromise;
|
||||
}
|
||||
|
||||
// Port comes in here from --port on the command line
|
||||
async serve(port) {
|
||||
this._commandLinePort = port;
|
||||
|
||||
await this.init();
|
||||
await this.initServerInstance();
|
||||
|
||||
this.server.serve(port || this.options.port);
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._server) {
|
||||
await this._server.close();
|
||||
|
||||
this._server = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async sendError({ error }) {
|
||||
if (this._server) {
|
||||
await this.server.sendError({
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Restart the server entirely
|
||||
// We don’t want to use a native `restart` method (e.g. restart() in Vite) so that
|
||||
// we can correctly handle a `module` property change (changing the server type)
|
||||
async restart() {
|
||||
// Blow away cached options
|
||||
delete this._options;
|
||||
|
||||
await this.close();
|
||||
|
||||
// saved --port in `serve()`
|
||||
await this.serve(this._commandLinePort);
|
||||
|
||||
// rewatch the saved watched files (passthrough copy)
|
||||
if ("watchFiles" in this.server) {
|
||||
this.server.watchFiles(this._watchedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
// checkPassthroughCopyBehavior check is called upstream in Eleventy.js
|
||||
// TODO globs are not removed from watcher
|
||||
watchPassthroughCopy(globs) {
|
||||
this._watchedFiles = globs;
|
||||
|
||||
if (this._server && "watchFiles" in this.server) {
|
||||
this.server.watchFiles(globs);
|
||||
this._globsNeedWatching = false;
|
||||
} else {
|
||||
this._globsNeedWatching = true;
|
||||
}
|
||||
}
|
||||
|
||||
isEmulatedPassthroughCopyMatch(filepath) {
|
||||
return isGlobMatch(filepath, this._watchedFiles);
|
||||
}
|
||||
|
||||
hasOptionsChanged() {
|
||||
try {
|
||||
assert.deepStrictEqual(this.config.serverOptions, this._savedConfigOptions);
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Live reload the server
|
||||
async reload(reloadEvent = {}) {
|
||||
if (!this._server) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart the server if the options have changed
|
||||
if (this.hasOptionsChanged()) {
|
||||
debug("Server options changed, we’re restarting the server");
|
||||
await this.restart();
|
||||
} else {
|
||||
await this.server.reload(reloadEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyServe;
|
||||
131
node_modules/@11ty/eleventy/src/EleventyWatch.js
generated
vendored
Executable file
131
node_modules/@11ty/eleventy/src/EleventyWatch.js
generated
vendored
Executable file
@@ -0,0 +1,131 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import PathNormalizer from "./Util/PathNormalizer.js";
|
||||
|
||||
/* Decides when to watch and in what mode to watch
|
||||
* Incremental builds don’t batch changes, they queue.
|
||||
* Nonincremental builds batch.
|
||||
*/
|
||||
|
||||
class EleventyWatch {
|
||||
constructor() {
|
||||
this.incremental = false;
|
||||
this.isActive = false;
|
||||
this.activeQueue = [];
|
||||
}
|
||||
|
||||
isBuildRunning() {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
setBuildRunning() {
|
||||
this.isActive = true;
|
||||
|
||||
// pop waiting queue into the active queue
|
||||
this.activeQueue = this.popNextActiveQueue();
|
||||
}
|
||||
|
||||
setBuildFinished() {
|
||||
this.isActive = false;
|
||||
this.activeQueue = [];
|
||||
}
|
||||
|
||||
getIncrementalFile() {
|
||||
if (this.incremental) {
|
||||
return this.activeQueue.length ? this.activeQueue[0] : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Returns the changed files currently being operated on in the current `watch` build
|
||||
* Works with or without incremental (though in incremental only one file per time will be processed)
|
||||
*/
|
||||
getActiveQueue() {
|
||||
if (!this.isActive) {
|
||||
return [];
|
||||
} else if (this.incremental && this.activeQueue.length === 0) {
|
||||
return [];
|
||||
} else if (this.incremental) {
|
||||
return [this.activeQueue[0]];
|
||||
}
|
||||
|
||||
return this.activeQueue;
|
||||
}
|
||||
|
||||
_queueMatches(file) {
|
||||
let filterCallback;
|
||||
if (typeof file === "function") {
|
||||
filterCallback = file;
|
||||
} else {
|
||||
filterCallback = (path) => path === file;
|
||||
}
|
||||
|
||||
return this.activeQueue.filter(filterCallback);
|
||||
}
|
||||
|
||||
hasAllQueueFiles(file) {
|
||||
return (
|
||||
this.activeQueue.length > 0 && this.activeQueue.length === this._queueMatches(file).length
|
||||
);
|
||||
}
|
||||
|
||||
hasQueuedFile(file) {
|
||||
if (file) {
|
||||
return this._queueMatches(file).length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasQueuedFiles(files) {
|
||||
for (const file of files) {
|
||||
if (this.hasQueuedFile(file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get pendingQueue() {
|
||||
if (!this._queue) {
|
||||
this._queue = [];
|
||||
}
|
||||
return this._queue;
|
||||
}
|
||||
|
||||
set pendingQueue(value) {
|
||||
this._queue = value;
|
||||
}
|
||||
|
||||
addToPendingQueue(path) {
|
||||
if (path) {
|
||||
path = PathNormalizer.normalizeSeperator(TemplatePath.addLeadingDotSlash(path));
|
||||
this.pendingQueue.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
getPendingQueueSize() {
|
||||
return this.pendingQueue.length;
|
||||
}
|
||||
|
||||
getPendingQueue() {
|
||||
return this.pendingQueue;
|
||||
}
|
||||
|
||||
getActiveQueueSize() {
|
||||
return this.activeQueue.length;
|
||||
}
|
||||
|
||||
// returns array
|
||||
popNextActiveQueue() {
|
||||
if (this.incremental) {
|
||||
return this.pendingQueue.length ? [this.pendingQueue.shift()] : [];
|
||||
}
|
||||
|
||||
let ret = this.pendingQueue.slice();
|
||||
this.pendingQueue = [];
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyWatch;
|
||||
164
node_modules/@11ty/eleventy/src/EleventyWatchTargets.js
generated
vendored
Normal file
164
node_modules/@11ty/eleventy/src/EleventyWatchTargets.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import { DepGraph } from "dependency-graph";
|
||||
|
||||
import JavaScriptDependencies from "./Util/JavaScriptDependencies.js";
|
||||
import eventBus from "./EventBus.js";
|
||||
|
||||
class EleventyWatchTargets {
|
||||
#templateConfig;
|
||||
|
||||
constructor(templateConfig) {
|
||||
this.targets = new Set();
|
||||
this.dependencies = new Set();
|
||||
this.newTargets = new Set();
|
||||
this.isEsm = false;
|
||||
|
||||
this.graph = new DepGraph();
|
||||
this.#templateConfig = templateConfig;
|
||||
}
|
||||
|
||||
setProjectUsingEsm(isEsmProject) {
|
||||
this.isEsm = !!isEsmProject;
|
||||
}
|
||||
|
||||
isJavaScriptDependency(path) {
|
||||
return this.dependencies.has(path);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.newTargets = new Set();
|
||||
}
|
||||
|
||||
isWatched(target) {
|
||||
return this.targets.has(target);
|
||||
}
|
||||
|
||||
addToDependencyGraph(parent, deps) {
|
||||
if (!this.graph.hasNode(parent)) {
|
||||
this.graph.addNode(parent);
|
||||
}
|
||||
for (let dep of deps) {
|
||||
if (!this.graph.hasNode(dep)) {
|
||||
this.graph.addNode(dep);
|
||||
}
|
||||
this.graph.addDependency(parent, dep);
|
||||
}
|
||||
}
|
||||
|
||||
uses(parent, dep) {
|
||||
return this.getDependenciesOf(parent).includes(dep);
|
||||
}
|
||||
|
||||
getDependenciesOf(parent) {
|
||||
if (!this.graph.hasNode(parent)) {
|
||||
return [];
|
||||
}
|
||||
return this.graph.dependenciesOf(parent);
|
||||
}
|
||||
|
||||
getDependantsOf(child) {
|
||||
if (!this.graph.hasNode(child)) {
|
||||
return [];
|
||||
}
|
||||
return this.graph.dependantsOf(child);
|
||||
}
|
||||
|
||||
addRaw(targets, isDependency) {
|
||||
for (let target of targets) {
|
||||
let path = TemplatePath.addLeadingDotSlash(target);
|
||||
if (!this.isWatched(path)) {
|
||||
this.newTargets.add(path);
|
||||
}
|
||||
|
||||
this.targets.add(path);
|
||||
|
||||
if (isDependency) {
|
||||
this.dependencies.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static normalize(targets) {
|
||||
if (!targets) {
|
||||
return [];
|
||||
} else if (Array.isArray(targets)) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
return [targets];
|
||||
}
|
||||
|
||||
// add only a target
|
||||
add(targets) {
|
||||
this.addRaw(EleventyWatchTargets.normalize(targets));
|
||||
}
|
||||
|
||||
static normalizeToGlobs(targets) {
|
||||
return EleventyWatchTargets.normalize(targets).map((entry) =>
|
||||
TemplatePath.convertToRecursiveGlobSync(entry),
|
||||
);
|
||||
}
|
||||
|
||||
addAndMakeGlob(targets) {
|
||||
this.addRaw(EleventyWatchTargets.normalizeToGlobs(targets));
|
||||
}
|
||||
|
||||
// add only a target’s dependencies
|
||||
async addDependencies(targets, filterCallback) {
|
||||
if (this.#templateConfig && !this.#templateConfig.shouldSpiderJavaScriptDependencies()) {
|
||||
return;
|
||||
}
|
||||
|
||||
targets = EleventyWatchTargets.normalize(targets);
|
||||
let deps = await JavaScriptDependencies.getDependencies(targets, this.isEsm);
|
||||
if (filterCallback) {
|
||||
deps = deps.filter(filterCallback);
|
||||
}
|
||||
|
||||
for (let target of targets) {
|
||||
this.addToDependencyGraph(target, deps);
|
||||
}
|
||||
this.addRaw(deps, true);
|
||||
}
|
||||
|
||||
setWriter(templateWriter) {
|
||||
this.writer = templateWriter;
|
||||
}
|
||||
|
||||
clearImportCacheFor(filePathArray) {
|
||||
let paths = new Set();
|
||||
for (const filePath of filePathArray) {
|
||||
paths.add(filePath);
|
||||
|
||||
// Delete from require cache so that updates to the module are re-required
|
||||
let importsTheChangedFile = this.getDependantsOf(filePath);
|
||||
for (let dep of importsTheChangedFile) {
|
||||
paths.add(dep);
|
||||
}
|
||||
|
||||
let isImportedInTheChangedFile = this.getDependenciesOf(filePath);
|
||||
for (let dep of isImportedInTheChangedFile) {
|
||||
paths.add(dep);
|
||||
}
|
||||
|
||||
// Use GlobalDependencyMap
|
||||
if (this.#templateConfig) {
|
||||
for (let dep of this.#templateConfig.usesGraph.getDependantsFor(filePath)) {
|
||||
paths.add(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.emit("eleventy.importCacheReset", paths);
|
||||
}
|
||||
|
||||
getNewTargetsSinceLastReset() {
|
||||
return Array.from(this.newTargets);
|
||||
}
|
||||
|
||||
getTargets() {
|
||||
return Array.from(this.targets);
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyWatchTargets;
|
||||
338
node_modules/@11ty/eleventy/src/Engines/Custom.js
generated
vendored
Normal file
338
node_modules/@11ty/eleventy/src/Engines/Custom.js
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import getJavaScriptData from "../Util/GetJavaScriptData.js";
|
||||
import eventBus from "../EventBus.js";
|
||||
|
||||
let lastModifiedFile = undefined;
|
||||
eventBus.on("eleventy.resourceModified", (path) => {
|
||||
lastModifiedFile = path;
|
||||
});
|
||||
|
||||
class CustomEngine extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.entry = this.getExtensionMapEntry();
|
||||
this.needsInit = "init" in this.entry && typeof this.entry.init === "function";
|
||||
|
||||
this._defaultEngine = undefined;
|
||||
|
||||
// Enable cacheability for this template
|
||||
if (this.entry?.compileOptions?.cache) {
|
||||
this.cacheable = this.entry.compileOptions.cache;
|
||||
} else if (this.needsToReadFileContents()) {
|
||||
this.cacheable = true;
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionMapEntry() {
|
||||
if ("extensionMap" in this.config) {
|
||||
let name = this.name.toLowerCase();
|
||||
// Iterates over only the user config `addExtension` entries
|
||||
for (let entry of this.config.extensionMap) {
|
||||
let entryKey = (entry.aliasKey || entry.key || "").toLowerCase();
|
||||
if (entryKey === name) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw Error(
|
||||
`Could not find a custom extension for ${this.name}. Did you add it to your config file?`,
|
||||
);
|
||||
}
|
||||
|
||||
setDefaultEngine(defaultEngine) {
|
||||
this._defaultEngine = defaultEngine;
|
||||
}
|
||||
|
||||
async getInstanceFromInputPath(inputPath) {
|
||||
if (
|
||||
"getInstanceFromInputPath" in this.entry &&
|
||||
typeof this.entry.getInstanceFromInputPath === "function"
|
||||
) {
|
||||
// returns Promise
|
||||
return this.entry.getInstanceFromInputPath(inputPath);
|
||||
}
|
||||
|
||||
// aliased upstream type
|
||||
if (
|
||||
this._defaultEngine &&
|
||||
"getInstanceFromInputPath" in this._defaultEngine &&
|
||||
typeof this._defaultEngine.getInstanceFromInputPath === "function"
|
||||
) {
|
||||
// returns Promise
|
||||
return this._defaultEngine.getInstanceFromInputPath(inputPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to use the module loader directly
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
useJavaScriptImport() {
|
||||
if ("useJavaScriptImport" in this.entry) {
|
||||
return this.entry.useJavaScriptImport;
|
||||
}
|
||||
|
||||
if (
|
||||
this._defaultEngine &&
|
||||
"useJavaScriptImport" in this._defaultEngine &&
|
||||
typeof this._defaultEngine.useJavaScriptImport === "function"
|
||||
) {
|
||||
return this._defaultEngine.useJavaScriptImport();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
needsToReadFileContents() {
|
||||
if ("read" in this.entry) {
|
||||
return this.entry.read;
|
||||
}
|
||||
|
||||
// Handle aliases to `11ty.js` templates, avoid reading files in the alias, see #2279
|
||||
// Here, we are short circuiting fallback to defaultRenderer, does not account for compile
|
||||
// functions that call defaultRenderer explicitly
|
||||
if (this._defaultEngine && "needsToReadFileContents" in this._defaultEngine) {
|
||||
return this._defaultEngine.needsToReadFileContents();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we init from multiple places, wait for the first init to finish before continuing on.
|
||||
async _runningInit() {
|
||||
if (this.needsInit) {
|
||||
if (!this._initing) {
|
||||
this._initBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Init`);
|
||||
this._initBench.before();
|
||||
this._initing = this.entry.init.bind({
|
||||
config: this.config,
|
||||
bench: this.benchmarks.aggregate,
|
||||
})();
|
||||
}
|
||||
await this._initing;
|
||||
this.needsInit = false;
|
||||
|
||||
if (this._initBench) {
|
||||
this._initBench.after();
|
||||
this._initBench = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getExtraDataFromFile(inputPath) {
|
||||
if (this.entry.getData === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("getData" in this.entry)) {
|
||||
// Handle aliases to `11ty.js` templates, use upstream default engine data fetch, see #2279
|
||||
if (this._defaultEngine && "getExtraDataFromFile" in this._defaultEngine) {
|
||||
return this._defaultEngine.getExtraDataFromFile(inputPath);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this._runningInit();
|
||||
|
||||
if (typeof this.entry.getData === "function") {
|
||||
let dataBench = this.benchmarks.aggregate.get(
|
||||
`Engine (${this.name}) Get Data From File (Function)`,
|
||||
);
|
||||
dataBench.before();
|
||||
let data = this.entry.getData(inputPath);
|
||||
dataBench.after();
|
||||
return data;
|
||||
}
|
||||
|
||||
let keys = new Set();
|
||||
if (this.entry.getData === true) {
|
||||
keys.add("data");
|
||||
} else if (Array.isArray(this.entry.getData)) {
|
||||
for (let key of this.entry.getData) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
let dataBench = this.benchmarks.aggregate.get(`Engine (${this.name}) Get Data From File`);
|
||||
dataBench.before();
|
||||
|
||||
let inst = await this.getInstanceFromInputPath(inputPath);
|
||||
|
||||
if (inst === false) {
|
||||
dataBench.after();
|
||||
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
`\`getInstanceFromInputPath\` callback missing from '${this.name}' template engine plugin. It is required when \`getData\` is in use. You can set \`getData: false\` to opt-out of this.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// override keys set at the plugin level in the individual template
|
||||
if (inst.eleventyDataKey) {
|
||||
keys = new Set(inst.eleventyDataKey);
|
||||
}
|
||||
|
||||
let mixins;
|
||||
if (this.config) {
|
||||
// Object.assign usage: see TemplateRenderCustomTest.js: `JavaScript functions should not be mutable but not *that* mutable`
|
||||
mixins = Object.assign({}, this.config.javascriptFunctions);
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let key of keys) {
|
||||
promises.push(
|
||||
getJavaScriptData(inst, inputPath, key, {
|
||||
mixins,
|
||||
isObjectRequired: key === "data",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let results = await Promise.all(promises);
|
||||
let data = {};
|
||||
for (let result of results) {
|
||||
Object.assign(data, result);
|
||||
}
|
||||
dataBench.after();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async compile(str, inputPath, ...args) {
|
||||
await this._runningInit();
|
||||
let defaultCompilationFn;
|
||||
if (this._defaultEngine) {
|
||||
defaultCompilationFn = async (data) => {
|
||||
const renderFn = await this._defaultEngine.compile(str, inputPath, ...args);
|
||||
return renderFn(data);
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to default compiler if the user does not provide their own
|
||||
if (!this.entry.compile) {
|
||||
if (defaultCompilationFn) {
|
||||
return defaultCompilationFn;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Missing \`compile\` property for custom template syntax definition eleventyConfig.addExtension("${this.name}"). This is not necessary when aliasing to an existing template syntax.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO generalize this (look at JavaScript.js)
|
||||
let fn = this.entry.compile.bind({
|
||||
config: this.config,
|
||||
addDependencies: (from, toArray = []) => {
|
||||
this.config.uses.addDependency(from, toArray);
|
||||
},
|
||||
defaultRenderer: defaultCompilationFn, // bind defaultRenderer to compile function
|
||||
})(str, inputPath);
|
||||
|
||||
// Support `undefined` to skip compile/render
|
||||
if (fn) {
|
||||
// Bind defaultRenderer to render function
|
||||
if ("then" in fn && typeof fn.then === "function") {
|
||||
// Promise, wait to bind
|
||||
return fn.then((fn) => {
|
||||
if (typeof fn === "function") {
|
||||
return fn.bind({
|
||||
defaultRenderer: defaultCompilationFn,
|
||||
});
|
||||
}
|
||||
return fn;
|
||||
});
|
||||
} else if ("bind" in fn && typeof fn.bind === "function") {
|
||||
return fn.bind({
|
||||
defaultRenderer: defaultCompilationFn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
get defaultTemplateFileExtension() {
|
||||
return this.entry.outputFileExtension ?? "html";
|
||||
}
|
||||
|
||||
// Whether or not to wrap in Eleventy layouts
|
||||
useLayouts() {
|
||||
// TODO future change fallback to `this.defaultTemplateFileExtension === "html"`
|
||||
return this.entry.useLayouts ?? true;
|
||||
}
|
||||
|
||||
hasDependencies(inputPath) {
|
||||
if (this.config.uses.getDependencies(inputPath) === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isFileRelevantTo(inputPath, comparisonFile, includeLayouts) {
|
||||
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile, includeLayouts);
|
||||
}
|
||||
|
||||
getCompileCacheKey(str, inputPath) {
|
||||
// Return this separately so we know whether or not to use the cached version
|
||||
// but still return a key to cache this new render for next time
|
||||
let useCache = !this.isFileRelevantTo(inputPath, lastModifiedFile, false);
|
||||
|
||||
if (this.entry.compileOptions && "getCacheKey" in this.entry.compileOptions) {
|
||||
if (typeof this.entry.compileOptions.getCacheKey !== "function") {
|
||||
throw new Error(
|
||||
`\`compileOptions.getCacheKey\` must be a function in addExtension for the ${this.name} type`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
useCache,
|
||||
key: this.entry.compileOptions.getCacheKey(str, inputPath),
|
||||
};
|
||||
}
|
||||
|
||||
let { key } = super.getCompileCacheKey(str, inputPath);
|
||||
return {
|
||||
useCache,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
permalinkNeedsCompilation(/*str*/) {
|
||||
if (this.entry.compileOptions && "permalink" in this.entry.compileOptions) {
|
||||
let p = this.entry.compileOptions.permalink;
|
||||
if (p === "raw") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// permalink: false is aliased to permalink: () => false
|
||||
if (p === false) {
|
||||
return () => false;
|
||||
}
|
||||
|
||||
return this.entry.compileOptions.permalink;
|
||||
}
|
||||
|
||||
// Breaking: default changed from `true` to `false` in 3.0.0-alpha.13
|
||||
return false;
|
||||
}
|
||||
|
||||
static shouldSpiderJavaScriptDependencies(entry) {
|
||||
if (entry.compileOptions && "spiderJavaScriptDependencies" in entry.compileOptions) {
|
||||
return entry.compileOptions.spiderJavaScriptDependencies;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomEngine;
|
||||
34
node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js
generated
vendored
Normal file
34
node_modules/@11ty/eleventy/src/Engines/FrontMatter/JavaScript.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { RetrieveGlobals } from "node-retrieve-globals";
|
||||
|
||||
// `javascript` Front Matter Type
|
||||
export default function (frontMatterCode, context = {}) {
|
||||
let { filePath } = context;
|
||||
|
||||
// context.language would be nice as a guard, but was unreliable
|
||||
if (frontMatterCode.trimStart().startsWith("{")) {
|
||||
return context.engines.jsLegacy.parse(frontMatterCode, context);
|
||||
}
|
||||
|
||||
let vm = new RetrieveGlobals(frontMatterCode, {
|
||||
filePath,
|
||||
// ignored if vm.Module is stable (or --experimental-vm-modules)
|
||||
transformEsmImports: true,
|
||||
});
|
||||
|
||||
// Future warning until vm.Module is stable:
|
||||
// If the frontMatterCode uses `import` this uses the `experimentalModuleApi`
|
||||
// option in node-retrieve-globals to workaround https://github.com/zachleat/node-retrieve-globals/issues/2
|
||||
let data = {
|
||||
page: {
|
||||
// Theoretically fileSlug and filePathStem could be added here but require extensionMap
|
||||
inputPath: filePath,
|
||||
},
|
||||
};
|
||||
|
||||
// this is async, but it’s handled in Eleventy upstream.
|
||||
return vm.getGlobalContext(data, {
|
||||
reuseGlobal: true,
|
||||
dynamicImport: true,
|
||||
// addRequire: true,
|
||||
});
|
||||
}
|
||||
28
node_modules/@11ty/eleventy/src/Engines/Html.js
generated
vendored
Normal file
28
node_modules/@11ty/eleventy/src/Engines/Html.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
|
||||
class Html extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
async compile(str, inputPath, preTemplateEngine) {
|
||||
if (preTemplateEngine) {
|
||||
let engine = await this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
|
||||
let fnReady = engine.compile(str, inputPath);
|
||||
|
||||
return async function (data) {
|
||||
let fn = await fnReady;
|
||||
|
||||
return fn(data);
|
||||
};
|
||||
}
|
||||
|
||||
return function () {
|
||||
// do nothing with data if parseHtmlWith is falsy
|
||||
return str;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Html;
|
||||
237
node_modules/@11ty/eleventy/src/Engines/JavaScript.js
generated
vendored
Normal file
237
node_modules/@11ty/eleventy/src/Engines/JavaScript.js
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
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 EventBusUtil from "../Util/EventBusUtil.js";
|
||||
import { EleventyImport } from "../Util/Require.js";
|
||||
import { augmentFunction, augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
class JavaScriptTemplateNotDefined extends EleventyBaseError {}
|
||||
|
||||
class JavaScript extends TemplateEngine {
|
||||
constructor(name, templateConfig) {
|
||||
super(name, templateConfig);
|
||||
this.instances = {};
|
||||
|
||||
this.cacheable = false;
|
||||
EventBusUtil.soloOn("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];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
mod = await EleventyImport(inputPath, isEsm ? "esm" : "cjs");
|
||||
}
|
||||
|
||||
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 function (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));
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
static shouldSpiderJavaScriptDependencies() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default JavaScript;
|
||||
326
node_modules/@11ty/eleventy/src/Engines/Liquid.js
generated
vendored
Normal file
326
node_modules/@11ty/eleventy/src/Engines/Liquid.js
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
import moo from "moo";
|
||||
import { Tokenizer, TokenKind, evalToken, Liquid as LiquidJs } from "liquidjs";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
// import debugUtil from "debug";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import { augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
// const debug = debugUtil("Eleventy:Liquid");
|
||||
|
||||
class Liquid extends TemplateEngine {
|
||||
static argumentLexerOptions = {
|
||||
number: /[0-9]+\.*[0-9]*/,
|
||||
doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/,
|
||||
singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/,
|
||||
keyword: /[a-zA-Z0-9.\-_]+/,
|
||||
"ignore:whitespace": /[, \t]+/, // includes comma separator
|
||||
};
|
||||
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.liquidOptions = this.config.liquidOptions || {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.liquid);
|
||||
|
||||
this.argLexer = moo.compile(Liquid.argumentLexerOptions);
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
setLibrary(override) {
|
||||
// warning, the include syntax supported here does not exactly match what Jekyll uses.
|
||||
this.liquidLib = override || new LiquidJs(this.getLiquidOptions());
|
||||
this.setEngineLib(this.liquidLib);
|
||||
|
||||
this.addFilters(this.config.liquidFilters);
|
||||
|
||||
// TODO these all go to the same place (addTag), add warnings for overwrites
|
||||
this.addCustomTags(this.config.liquidTags);
|
||||
this.addAllShortcodes(this.config.liquidShortcodes);
|
||||
this.addAllPairedShortcodes(this.config.liquidPairedShortcodes);
|
||||
}
|
||||
|
||||
getLiquidOptions() {
|
||||
let defaults = {
|
||||
root: [this.dirs.includes, this.dirs.input], // supplemented in compile with inputPath below
|
||||
extname: ".liquid",
|
||||
strictFilters: true,
|
||||
// TODO?
|
||||
// cache: true,
|
||||
};
|
||||
|
||||
let options = Object.assign(defaults, this.liquidOptions || {});
|
||||
// debug("Liquid constructor options: %o", options);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static wrapFilter(name, fn) {
|
||||
/**
|
||||
* @this {object}
|
||||
*/
|
||||
return function (...args) {
|
||||
// Set this.eleventy and this.page
|
||||
if (typeof this.context?.get === "function") {
|
||||
augmentObject(this, {
|
||||
source: this.context,
|
||||
getter: (key, context) => context.get([key]),
|
||||
|
||||
lazy: this.context.strictVariables,
|
||||
});
|
||||
}
|
||||
|
||||
// We *don’t* wrap this in an EleventyFilterError because Liquid has a better error message with line/column information in the template
|
||||
return fn.call(this, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Shortcodes
|
||||
static normalizeScope(context) {
|
||||
let obj = {};
|
||||
if (context) {
|
||||
obj.ctx = context; // Full context available on `ctx`
|
||||
|
||||
// Set this.eleventy and this.page
|
||||
augmentObject(obj, {
|
||||
source: context,
|
||||
getter: (key, context) => context.get([key]),
|
||||
lazy: context.strictVariables,
|
||||
});
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
addCustomTags(tags) {
|
||||
for (let name in tags) {
|
||||
this.addTag(name, tags[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addFilters(filters) {
|
||||
for (let name in filters) {
|
||||
this.addFilter(name, filters[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addFilter(name, filter) {
|
||||
this.liquidLib.registerFilter(name, Liquid.wrapFilter(name, filter));
|
||||
}
|
||||
|
||||
addTag(name, tagFn) {
|
||||
let tagObj;
|
||||
if (typeof tagFn === "function") {
|
||||
tagObj = tagFn(this.liquidLib);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: …, render: … } })",
|
||||
);
|
||||
}
|
||||
this.liquidLib.registerTag(name, tagObj);
|
||||
}
|
||||
|
||||
addAllShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addShortcode(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addAllPairedShortcodes(shortcodes) {
|
||||
for (let name in shortcodes) {
|
||||
this.addPairedShortcode(name, shortcodes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
static parseArguments(lexer, str) {
|
||||
let argArray = [];
|
||||
|
||||
if (!lexer) {
|
||||
lexer = moo.compile(Liquid.argumentLexerOptions);
|
||||
}
|
||||
|
||||
if (typeof str === "string") {
|
||||
lexer.reset(str);
|
||||
|
||||
let arg = lexer.next();
|
||||
while (arg) {
|
||||
/*{
|
||||
type: 'doubleQuoteString',
|
||||
value: '"test 2"',
|
||||
text: '"test 2"',
|
||||
toString: [Function: tokenToString],
|
||||
offset: 0,
|
||||
lineBreaks: 0,
|
||||
line: 1,
|
||||
col: 1 }*/
|
||||
if (arg.type.indexOf("ignore:") === -1) {
|
||||
// Push the promise into an array instead of awaiting it here.
|
||||
// This forces the promises to run in order with the correct scope value for each arg.
|
||||
// Otherwise they run out of order and can lead to undefined values for arguments in layout template shortcodes.
|
||||
// console.log( arg.value, scope, engine );
|
||||
argArray.push(arg.value);
|
||||
}
|
||||
arg = lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
return argArray;
|
||||
}
|
||||
|
||||
static parseArgumentsBuiltin(args) {
|
||||
let tokenizer = new Tokenizer(args);
|
||||
let parsedArgs = [];
|
||||
|
||||
let value = tokenizer.readValue();
|
||||
while (value) {
|
||||
parsedArgs.push(value);
|
||||
tokenizer.skipBlank();
|
||||
if (tokenizer.peek() === ",") {
|
||||
tokenizer.advance();
|
||||
}
|
||||
value = tokenizer.readValue();
|
||||
}
|
||||
tokenizer.end();
|
||||
|
||||
return parsedArgs;
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function (liquidEngine) {
|
||||
return {
|
||||
parse(tagToken) {
|
||||
this.name = tagToken.name;
|
||||
if (_t.config.liquidParameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
},
|
||||
render: function* (ctx) {
|
||||
let argArray = [];
|
||||
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn) {
|
||||
let _t = this;
|
||||
this.addTag(shortcodeName, function (liquidEngine) {
|
||||
return {
|
||||
parse(tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
|
||||
if (_t.config.liquidParameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
|
||||
this.templates = [];
|
||||
|
||||
var stream = liquidEngine.parser
|
||||
.parseStream(remainTokens)
|
||||
.on("template", (tpl) => this.templates.push(tpl))
|
||||
.on("tag:end" + shortcodeName, () => stream.stop())
|
||||
.on("end", () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
render: function* (ctx /*, emitter*/) {
|
||||
let argArray = [];
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
const html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);
|
||||
|
||||
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), html, ...argArray);
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
parseForSymbols(str) {
|
||||
let tokenizer = new Tokenizer(str);
|
||||
/** @type {Array} */
|
||||
let tokens = tokenizer.readTopLevelTokens();
|
||||
let symbols = tokens
|
||||
.filter((token) => token.kind === TokenKind.Output)
|
||||
.map((token) => {
|
||||
// manually remove filters 😅
|
||||
return token.content.split("|").map((entry) => entry.trim())[0];
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)
|
||||
/** @returns {boolean|undefined} */
|
||||
permalinkNeedsCompilation(str) {
|
||||
if (typeof str === "string") {
|
||||
return this.needsCompilation(str);
|
||||
}
|
||||
}
|
||||
|
||||
needsCompilation(str) {
|
||||
let options = this.liquidLib.options;
|
||||
|
||||
return (
|
||||
str.indexOf(options.tagDelimiterLeft) !== -1 ||
|
||||
str.indexOf(options.outputDelimiterLeft) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let engine = this.liquidLib;
|
||||
let tmplReady = engine.parse(str, inputPath);
|
||||
|
||||
// Required for relative includes
|
||||
let options = {};
|
||||
if (!inputPath || inputPath === "liquid" || inputPath === "md") {
|
||||
// do nothing
|
||||
} else {
|
||||
options.root = [TemplatePath.getDirFromFilePath(inputPath)];
|
||||
}
|
||||
|
||||
return async function (data) {
|
||||
let tmpl = await tmplReady;
|
||||
|
||||
return engine.render(tmpl, data, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Liquid;
|
||||
93
node_modules/@11ty/eleventy/src/Engines/Markdown.js
generated
vendored
Normal file
93
node_modules/@11ty/eleventy/src/Engines/Markdown.js
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
import markdownIt from "markdown-it";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
|
||||
class Markdown extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.markdownOptions = {};
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.md);
|
||||
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
setLibrary(mdLib) {
|
||||
this.mdLib = mdLib || markdownIt(this.getMarkdownOptions());
|
||||
|
||||
// Overrides a highlighter set in `markdownOptions`
|
||||
// This is separate so devs can pass in a new mdLib and still use the official eleventy plugin for markdown highlighting
|
||||
if (this.config.markdownHighlighter && typeof this.mdLib.set === "function") {
|
||||
this.mdLib.set({
|
||||
highlight: this.config.markdownHighlighter,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof this.mdLib.disable === "function") {
|
||||
// Disable indented code blocks by default (Issue #2438)
|
||||
this.mdLib.disable("code");
|
||||
}
|
||||
|
||||
this.setEngineLib(this.mdLib);
|
||||
}
|
||||
|
||||
setMarkdownOptions(options) {
|
||||
this.markdownOptions = options;
|
||||
}
|
||||
|
||||
getMarkdownOptions() {
|
||||
// work with "mode" presets https://github.com/markdown-it/markdown-it#init-with-presets-and-options
|
||||
if (typeof this.markdownOptions === "string") {
|
||||
return this.markdownOptions;
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
html: true,
|
||||
},
|
||||
this.markdownOptions || {},
|
||||
);
|
||||
}
|
||||
|
||||
async compile(str, inputPath, preTemplateEngine, bypassMarkdown) {
|
||||
let mdlib = this.mdLib;
|
||||
|
||||
if (preTemplateEngine) {
|
||||
let engine;
|
||||
if (typeof preTemplateEngine === "string") {
|
||||
engine = await this.engineManager.getEngine(preTemplateEngine, this.extensionMap);
|
||||
} else {
|
||||
engine = preTemplateEngine;
|
||||
}
|
||||
|
||||
let fnReady = engine.compile(str, inputPath);
|
||||
|
||||
if (bypassMarkdown) {
|
||||
return async function (data) {
|
||||
let fn = await fnReady;
|
||||
return fn(data);
|
||||
};
|
||||
} else {
|
||||
return async function (data) {
|
||||
let fn = await fnReady;
|
||||
let preTemplateEngineRender = await fn(data);
|
||||
let finishedRender = mdlib.render(preTemplateEngineRender, data);
|
||||
return finishedRender;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (bypassMarkdown) {
|
||||
return function () {
|
||||
return str;
|
||||
};
|
||||
} else {
|
||||
return function (data) {
|
||||
return mdlib.render(str, data);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Markdown;
|
||||
447
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js
generated
vendored
Executable file
447
node_modules/@11ty/eleventy/src/Engines/Nunjucks.js
generated
vendored
Executable file
@@ -0,0 +1,447 @@
|
||||
import NunjucksLib from "nunjucks";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateEngine from "./TemplateEngine.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import EventBusUtil from "../Util/EventBusUtil.js";
|
||||
import { augmentObject } from "./Util/ContextAugmenter.js";
|
||||
|
||||
class EleventyNunjucksError extends EleventyBaseError {}
|
||||
|
||||
class Nunjucks extends TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
super(name, eleventyConfig);
|
||||
|
||||
this.nunjucksEnvironmentOptions = this.config.nunjucksEnvironmentOptions || { dev: true };
|
||||
|
||||
this.nunjucksPrecompiledTemplates = this.config.nunjucksPrecompiledTemplates || {};
|
||||
this._usingPrecompiled = Object.keys(this.nunjucksPrecompiledTemplates).length > 0;
|
||||
|
||||
this.setLibrary(this.config.libraryOverrides.njk);
|
||||
|
||||
this.cacheable = true;
|
||||
}
|
||||
|
||||
_setEnv(override) {
|
||||
if (override) {
|
||||
this.njkEnv = override;
|
||||
} else if (this._usingPrecompiled) {
|
||||
// Precompiled templates to avoid eval!
|
||||
const NodePrecompiledLoader = function () {};
|
||||
|
||||
NodePrecompiledLoader.prototype.getSource = (name) => {
|
||||
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/precompiled-loader.js#L5
|
||||
return {
|
||||
src: {
|
||||
type: "code",
|
||||
obj: this.nunjucksPrecompiledTemplates[name],
|
||||
},
|
||||
// Maybe add this?
|
||||
// path,
|
||||
// noCache: true
|
||||
};
|
||||
};
|
||||
|
||||
this.njkEnv = new NunjucksLib.Environment(
|
||||
new NodePrecompiledLoader(),
|
||||
this.nunjucksEnvironmentOptions,
|
||||
);
|
||||
} else {
|
||||
let paths = new Set();
|
||||
paths.add(super.getIncludesDir());
|
||||
paths.add(TemplatePath.getWorkingDir());
|
||||
|
||||
// Filter out undefined paths
|
||||
let fsLoader = new NunjucksLib.FileSystemLoader(Array.from(paths).filter(Boolean));
|
||||
|
||||
this.njkEnv = new NunjucksLib.Environment(fsLoader, this.nunjucksEnvironmentOptions);
|
||||
}
|
||||
|
||||
this.config.events.emit("eleventy.engine.njk", {
|
||||
nunjucks: NunjucksLib,
|
||||
environment: this.njkEnv,
|
||||
});
|
||||
}
|
||||
|
||||
setLibrary(override) {
|
||||
this._setEnv(override);
|
||||
|
||||
// Correct, but overbroad. Better would be to evict more granularly, but
|
||||
// resolution from paths isn't straightforward.
|
||||
EventBusUtil.soloOn("eleventy.templateModified", (/*path*/) => {
|
||||
this.njkEnv.invalidateCache();
|
||||
});
|
||||
|
||||
this.setEngineLib(this.njkEnv);
|
||||
|
||||
this.addFilters(this.config.nunjucksFilters);
|
||||
this.addFilters(this.config.nunjucksAsyncFilters, true);
|
||||
|
||||
// TODO these all go to the same place (addTag), add warnings for overwrites
|
||||
// TODO(zachleat): variableName should work with quotes or without quotes (same as {% set %})
|
||||
this.addPairedShortcode("setAsync", function (content, variableName) {
|
||||
this.ctx[variableName] = content;
|
||||
return "";
|
||||
});
|
||||
|
||||
this.addCustomTags(this.config.nunjucksTags);
|
||||
this.addAllShortcodes(this.config.nunjucksShortcodes);
|
||||
this.addAllShortcodes(this.config.nunjucksAsyncShortcodes, true);
|
||||
this.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes);
|
||||
this.addAllPairedShortcodes(this.config.nunjucksAsyncPairedShortcodes, true);
|
||||
this.addGlobals(this.config.nunjucksGlobals);
|
||||
}
|
||||
|
||||
addFilters(filters, isAsync) {
|
||||
for (let name in filters) {
|
||||
this.njkEnv.addFilter(name, Nunjucks.wrapFilter(name, filters[name]), isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
static wrapFilter(name, fn) {
|
||||
return function (...args) {
|
||||
try {
|
||||
augmentObject(this, {
|
||||
source: this.ctx,
|
||||
lazy: false, // context.env?.opts.throwOnUndefined,
|
||||
});
|
||||
|
||||
return fn.call(this, ...args);
|
||||
} catch (e) {
|
||||
throw new EleventyNunjucksError(
|
||||
`Error in Nunjucks Filter \`${name}\`${this.page ? ` (${this.page.inputPath})` : ""}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Shortcodes
|
||||
static normalizeContext(context) {
|
||||
let obj = {};
|
||||
if (context.ctx) {
|
||||
obj.ctx = context.ctx;
|
||||
obj.env = context.env;
|
||||
|
||||
augmentObject(obj, {
|
||||
source: context.ctx,
|
||||
lazy: false, // context.env?.opts.throwOnUndefined,
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
addCustomTags(tags) {
|
||||
for (let name in tags) {
|
||||
this.addTag(name, tags[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addTag(name, tagFn) {
|
||||
let tagObj;
|
||||
if (typeof tagFn === "function") {
|
||||
tagObj = tagFn(NunjucksLib, this.njkEnv);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})",
|
||||
);
|
||||
}
|
||||
|
||||
this.njkEnv.addExtension(name, tagObj);
|
||||
}
|
||||
|
||||
addGlobals(globals) {
|
||||
for (let name in globals) {
|
||||
this.addGlobal(name, globals[name]);
|
||||
}
|
||||
}
|
||||
|
||||
addGlobal(name, globalFn) {
|
||||
this.njkEnv.addGlobal(name, globalFn);
|
||||
}
|
||||
|
||||
addAllShortcodes(shortcodes, isAsync = false) {
|
||||
for (let name in shortcodes) {
|
||||
this.addShortcode(name, shortcodes[name], isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
addAllPairedShortcodes(shortcodes, isAsync = false) {
|
||||
for (let name in shortcodes) {
|
||||
this.addPairedShortcode(name, shortcodes[name], isAsync);
|
||||
}
|
||||
}
|
||||
|
||||
_getShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
return function ShortcodeFunction() {
|
||||
this.tags = [shortcodeName];
|
||||
|
||||
this.parse = function (parser, nodes) {
|
||||
let args;
|
||||
let tok = parser.nextToken();
|
||||
|
||||
args = parser.parseSignature(true, true);
|
||||
|
||||
// Nunjucks bug with non-paired custom tags bug still exists even
|
||||
// though this issue is closed. Works fine for paired.
|
||||
// https://github.com/mozilla/nunjucks/issues/158
|
||||
if (args.children.length === 0) {
|
||||
args.addChild(new nodes.Literal(0, 0, ""));
|
||||
}
|
||||
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
if (isAsync) {
|
||||
return new nodes.CallExtensionAsync(this, "run", args);
|
||||
}
|
||||
return new nodes.CallExtension(this, "run", args);
|
||||
};
|
||||
|
||||
this.run = function (...args) {
|
||||
let resolve;
|
||||
if (isAsync) {
|
||||
resolve = args.pop();
|
||||
}
|
||||
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
if (isAsync) {
|
||||
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
|
||||
|
||||
// #3286 error messaging when the shortcode is not a promise
|
||||
if (!ret?.then) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ret.then(
|
||||
function (returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString("" + returnValue));
|
||||
},
|
||||
function (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(`Error with Nunjucks shortcode \`${shortcodeName}\``, e),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
let ret = shortcodeFn.call(Nunjucks.normalizeContext(context), ...argArray);
|
||||
return new NunjucksLib.runtime.SafeString("" + ret);
|
||||
} catch (e) {
|
||||
throw new EleventyNunjucksError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
_getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
return function PairedShortcodeFunction() {
|
||||
this.tags = [shortcodeName];
|
||||
|
||||
this.parse = function (parser, nodes) {
|
||||
var tok = parser.nextToken();
|
||||
|
||||
var args = parser.parseSignature(true, true);
|
||||
parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
var body = parser.parseUntilBlocks("end" + shortcodeName);
|
||||
parser.advanceAfterBlockEnd();
|
||||
|
||||
return new nodes.CallExtensionAsync(this, "run", args, [body]);
|
||||
};
|
||||
|
||||
this.run = function (...args) {
|
||||
let resolve = args.pop();
|
||||
let body = args.pop();
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
body(function (e, bodyContent) {
|
||||
if (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (isAsync) {
|
||||
let ret = shortcodeFn.call(
|
||||
Nunjucks.normalizeContext(context),
|
||||
bodyContent,
|
||||
...argArray,
|
||||
);
|
||||
|
||||
// #3286 error messaging when the shortcode is not a promise
|
||||
if (!ret?.then) {
|
||||
throw new EleventyNunjucksError(
|
||||
`Error with Nunjucks shortcode \`${shortcodeName}\`: it was defined as asynchronous but was actually synchronous. This is important for Nunjucks.`,
|
||||
);
|
||||
}
|
||||
|
||||
ret.then(
|
||||
function (returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
|
||||
},
|
||||
function (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
resolve(
|
||||
null,
|
||||
new NunjucksLib.runtime.SafeString(
|
||||
shortcodeFn.call(Nunjucks.normalizeContext(context), bodyContent, ...argArray),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(
|
||||
`Error with Nunjucks paired shortcode \`${shortcodeName}\``,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
addShortcode(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
let fn = this._getShortcodeFn(shortcodeName, shortcodeFn, isAsync);
|
||||
this.njkEnv.addExtension(shortcodeName, new fn());
|
||||
}
|
||||
|
||||
addPairedShortcode(shortcodeName, shortcodeFn, isAsync = false) {
|
||||
let fn = this._getPairedShortcodeFn(shortcodeName, shortcodeFn, isAsync);
|
||||
this.njkEnv.addExtension(shortcodeName, new fn());
|
||||
}
|
||||
|
||||
// Don’t return a boolean if permalink is a function (see TemplateContent->renderPermalink)
|
||||
permalinkNeedsCompilation(str) {
|
||||
if (typeof str === "string") {
|
||||
return this.needsCompilation(str);
|
||||
}
|
||||
}
|
||||
|
||||
needsCompilation(str) {
|
||||
// Defend against syntax customisations:
|
||||
// https://mozilla.github.io/nunjucks/api.html#customizing-syntax
|
||||
let optsTags = this.njkEnv.opts.tags || {};
|
||||
let blockStart = optsTags.blockStart || "{%";
|
||||
let variableStart = optsTags.variableStart || "{{";
|
||||
let commentStart = optsTags.variableStart || "{#";
|
||||
|
||||
return (
|
||||
str.indexOf(blockStart) !== -1 ||
|
||||
str.indexOf(variableStart) !== -1 ||
|
||||
str.indexOf(commentStart) !== -1
|
||||
);
|
||||
}
|
||||
|
||||
_getParseExtensions() {
|
||||
if (this._parseExtensions) {
|
||||
return this._parseExtensions;
|
||||
}
|
||||
|
||||
// add extensions so the parser knows about our custom tags/blocks
|
||||
let ext = [];
|
||||
for (let name in this.config.nunjucksTags) {
|
||||
let fn = this._getShortcodeFn(name, () => {});
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksShortcodes) {
|
||||
let fn = this._getShortcodeFn(name, () => {});
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksAsyncShortcodes) {
|
||||
let fn = this._getShortcodeFn(name, () => {}, true);
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksPairedShortcodes) {
|
||||
let fn = this._getPairedShortcodeFn(name, () => {});
|
||||
ext.push(new fn());
|
||||
}
|
||||
for (let name in this.config.nunjucksAsyncPairedShortcodes) {
|
||||
let fn = this._getPairedShortcodeFn(name, () => {}, true);
|
||||
ext.push(new fn());
|
||||
}
|
||||
|
||||
this._parseExtensions = ext;
|
||||
return ext;
|
||||
}
|
||||
|
||||
/* Outputs an Array of lodash get selectors */
|
||||
parseForSymbols(str) {
|
||||
const { parser, nodes } = NunjucksLib;
|
||||
let obj = parser.parse(str, this._getParseExtensions());
|
||||
let linesplit = str.split("\n");
|
||||
let values = obj.findAll(nodes.Value);
|
||||
let symbols = obj.findAll(nodes.Symbol).map((entry) => {
|
||||
let name = [entry.value];
|
||||
let nestedIndex = -1;
|
||||
for (let val of values) {
|
||||
if (nestedIndex > -1) {
|
||||
/* deep.object.syntax */
|
||||
if (linesplit[val.lineno].charAt(nestedIndex) === ".") {
|
||||
name.push(val.value);
|
||||
nestedIndex += val.value.length + 1;
|
||||
} else {
|
||||
nestedIndex = -1;
|
||||
}
|
||||
} else if (
|
||||
val.lineno === entry.lineno &&
|
||||
val.colno === entry.colno &&
|
||||
val.value === entry.value
|
||||
) {
|
||||
nestedIndex = entry.colno + entry.value.length;
|
||||
}
|
||||
}
|
||||
return name.join(".");
|
||||
});
|
||||
|
||||
let uniqueSymbols = Array.from(new Set(symbols));
|
||||
return uniqueSymbols;
|
||||
}
|
||||
|
||||
async compile(str, inputPath) {
|
||||
let tmpl;
|
||||
|
||||
// *All* templates are precompiled to avoid runtime eval
|
||||
if (this._usingPrecompiled) {
|
||||
tmpl = this.njkEnv.getTemplate(str, true);
|
||||
} else if (!inputPath || inputPath === "njk" || inputPath === "md") {
|
||||
tmpl = new NunjucksLib.Template(str, this.njkEnv, null, false);
|
||||
} else {
|
||||
tmpl = new NunjucksLib.Template(str, this.njkEnv, inputPath, false);
|
||||
}
|
||||
|
||||
return function (data) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
tmpl.render(data, function (err, res) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Nunjucks;
|
||||
184
node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js
generated
vendored
Normal file
184
node_modules/@11ty/eleventy/src/Engines/TemplateEngine.js
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
import EleventyExtensionMap from "../EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
class TemplateEngineConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateEngine {
|
||||
constructor(name, eleventyConfig) {
|
||||
this.name = name;
|
||||
|
||||
this.engineLib = null;
|
||||
this.cacheable = false;
|
||||
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateEngineConfigError("Missing `eleventyConfig` argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get includesDir() {
|
||||
return this.dirs.includes;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (this.eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new Error("Expecting a TemplateConfig instance.");
|
||||
}
|
||||
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
get benchmarks() {
|
||||
if (!this._benchmarks) {
|
||||
this._benchmarks = {
|
||||
aggregate: this.config.benchmarkManager.get("Aggregate"),
|
||||
};
|
||||
}
|
||||
return this._benchmarks;
|
||||
}
|
||||
|
||||
get engineManager() {
|
||||
return this._engineManager;
|
||||
}
|
||||
|
||||
set engineManager(manager) {
|
||||
this._engineManager = manager;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
get extensions() {
|
||||
if (!this._extensions) {
|
||||
this._extensions = this.extensionMap.getExtensionsFromKey(this.name);
|
||||
}
|
||||
return this._extensions;
|
||||
}
|
||||
|
||||
get extensionEntries() {
|
||||
if (!this._extensionEntries) {
|
||||
this._extensionEntries = this.extensionMap.getExtensionEntriesFromKey(this.name);
|
||||
}
|
||||
return this._extensionEntries;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
// Backwards compat
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
setEngineLib(engineLib) {
|
||||
this.engineLib = engineLib;
|
||||
|
||||
// Run engine amendments (via issue #2438)
|
||||
for (let amendment of this.config.libraryAmendments[this.name] || []) {
|
||||
// TODO it’d be nice if this were async friendly
|
||||
amendment(engineLib);
|
||||
}
|
||||
}
|
||||
|
||||
getEngineLib() {
|
||||
return this.engineLib;
|
||||
}
|
||||
|
||||
async _testRender(str, data) {
|
||||
// @ts-ignore
|
||||
let fn = await this.compile(str);
|
||||
return fn(data);
|
||||
}
|
||||
|
||||
useJavaScriptImport() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// JavaScript files defer to the module loader rather than read the files to strings
|
||||
needsToReadFileContents() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getExtraDataFromFile() {
|
||||
return {};
|
||||
}
|
||||
|
||||
getCompileCacheKey(str, inputPath) {
|
||||
// Changing to use inputPath and contents, using only file contents (`str`) caused issues when two
|
||||
// different files had identical content (2.0.0-canary.16)
|
||||
|
||||
// Caches are now segmented based on inputPath so using inputPath here is superfluous (2.0.0-canary.19)
|
||||
// But we do want a non-falsy value here even if `str` is an empty string.
|
||||
return {
|
||||
useCache: true,
|
||||
key: inputPath + str,
|
||||
};
|
||||
}
|
||||
|
||||
get defaultTemplateFileExtension() {
|
||||
return "html";
|
||||
}
|
||||
|
||||
// Whether or not to wrap in Eleventy layouts
|
||||
useLayouts() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {boolean|undefined} */
|
||||
permalinkNeedsCompilation(str) {
|
||||
return this.needsCompilation();
|
||||
}
|
||||
|
||||
// whether or not compile is needed or can we return the plaintext?
|
||||
needsCompilation(str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure compile is implemented downstream.
|
||||
* @abstract
|
||||
* @return {Promise}
|
||||
*/
|
||||
async compile() {
|
||||
throw new Error("compile() must be implemented by engine");
|
||||
}
|
||||
|
||||
// See https://v3.11ty.dev/docs/watch-serve/#watch-javascript-dependencies
|
||||
static shouldSpiderJavaScriptDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasDependencies(inputPath) {
|
||||
if (this.config.uses.getDependencies(inputPath) === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
isFileRelevantTo(inputPath, comparisonFile) {
|
||||
return this.config.uses.isFileRelevantTo(inputPath, comparisonFile);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateEngine;
|
||||
197
node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
generated
vendored
Normal file
197
node_modules/@11ty/eleventy/src/Engines/TemplateEngineManager.js
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
import debugUtil from "debug";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import { EleventyImportFromEleventy } from "../Util/Require.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateEngineManager");
|
||||
|
||||
class TemplateEngineManagerConfigError extends EleventyBaseError {}
|
||||
|
||||
class TemplateEngineManager {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplateEngineManagerConfigError("Missing or invalid `config` argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
|
||||
this.engineCache = {};
|
||||
this.importCache = {};
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
static isAlias(entry) {
|
||||
if (entry.aliasKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return entry.key !== entry.extension;
|
||||
}
|
||||
|
||||
static isSimpleAlias(entry) {
|
||||
if (!this.isAlias(entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// has keys other than key, extension, and aliasKey
|
||||
return (
|
||||
Object.keys(entry).some((key) => {
|
||||
return key !== "key" && key !== "extension" && key !== "aliasKey";
|
||||
}) === false
|
||||
);
|
||||
}
|
||||
|
||||
get keyToClassNameMap() {
|
||||
if (!this._keyToClassNameMap) {
|
||||
this._keyToClassNameMap = {
|
||||
md: "Markdown",
|
||||
html: "Html",
|
||||
njk: "Nunjucks",
|
||||
liquid: "Liquid",
|
||||
"11ty.js": "JavaScript",
|
||||
};
|
||||
|
||||
// Custom entries *can* overwrite default entries above
|
||||
if ("extensionMap" in this.config) {
|
||||
for (let entry of this.config.extensionMap) {
|
||||
// either the key does not already exist or it is not a simple alias and is an override: https://v3.11ty.dev/docs/languages/custom/#overriding-an-existing-template-language
|
||||
let existingTarget = this._keyToClassNameMap[entry.key];
|
||||
let isAlias = TemplateEngineManager.isAlias(entry);
|
||||
|
||||
if (!existingTarget && isAlias) {
|
||||
throw new Error(
|
||||
`An attempt to alias ${entry.aliasKey} to ${entry.key} was made, but ${entry.key} is not a recognized template syntax.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isAlias) {
|
||||
// only `key` and `extension`, not `compile` or other options
|
||||
if (!TemplateEngineManager.isSimpleAlias(entry)) {
|
||||
this._keyToClassNameMap[entry.aliasKey] = "Custom";
|
||||
} else {
|
||||
this._keyToClassNameMap[entry.aliasKey] = this._keyToClassNameMap[entry.key];
|
||||
}
|
||||
} else {
|
||||
// not an alias, so `key` and `extension` are the same here.
|
||||
// *can* override a built-in extension!
|
||||
this._keyToClassNameMap[entry.key] = "Custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._keyToClassNameMap;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.engineCache = {};
|
||||
}
|
||||
|
||||
getClassNameFromTemplateKey(key) {
|
||||
return this.keyToClassNameMap[key];
|
||||
}
|
||||
|
||||
hasEngine(name) {
|
||||
return !!this.getClassNameFromTemplateKey(name);
|
||||
}
|
||||
|
||||
isEngineRemovedFromCore(name) {
|
||||
return ["ejs", "hbs", "mustache", "haml", "pug"].includes(name) && !this.hasEngine(name);
|
||||
}
|
||||
|
||||
async getEngineClassByExtension(extension) {
|
||||
if (this.importCache[extension]) {
|
||||
return this.importCache[extension];
|
||||
}
|
||||
|
||||
let promise;
|
||||
|
||||
// We include these as raw strings (and not more readable variables) so they’re parsed by a bundler.
|
||||
if (extension === "md") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Markdown.js");
|
||||
} else if (extension === "html") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Html.js");
|
||||
} else if (extension === "njk") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Nunjucks.js");
|
||||
} else if (extension === "liquid") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/Liquid.js");
|
||||
} else if (extension === "11ty.js") {
|
||||
promise = EleventyImportFromEleventy("./src/Engines/JavaScript.js");
|
||||
} else {
|
||||
promise = this.getCustomEngineClass();
|
||||
}
|
||||
|
||||
this.importCache[extension] = promise;
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async getCustomEngineClass() {
|
||||
if (!this._CustomEngine) {
|
||||
this._CustomEngine = EleventyImportFromEleventy("./src/Engines/Custom.js");
|
||||
}
|
||||
return this._CustomEngine;
|
||||
}
|
||||
|
||||
async #getEngine(name, extensionMap) {
|
||||
let cls = await this.getEngineClassByExtension(name);
|
||||
let instance = new cls(name, this.eleventyConfig);
|
||||
instance.extensionMap = extensionMap;
|
||||
instance.engineManager = this;
|
||||
|
||||
let extensionEntry = extensionMap.getExtensionEntry(name);
|
||||
|
||||
// Override a built-in extension (md => md)
|
||||
// If provided a "Custom" engine using addExtension, but that engine's instance is *not* custom,
|
||||
// The user must be overriding a built-in engine i.e. addExtension('md', { ...overrideBehavior })
|
||||
let className = this.getClassNameFromTemplateKey(name);
|
||||
|
||||
if (className === "Custom" && instance.constructor.name !== "CustomEngine") {
|
||||
let CustomEngine = await this.getCustomEngineClass();
|
||||
let overrideCustomEngine = new CustomEngine(name, this.eleventyConfig);
|
||||
|
||||
// Keep track of the "default" engine 11ty would normally use
|
||||
// This allows the user to access the default engine in their override
|
||||
overrideCustomEngine.setDefaultEngine(instance);
|
||||
|
||||
instance = overrideCustomEngine;
|
||||
// Alias to a built-in extension (11ty.tsx => 11ty.js)
|
||||
} else if (
|
||||
instance.constructor.name === "CustomEngine" &&
|
||||
TemplateEngineManager.isAlias(extensionEntry)
|
||||
) {
|
||||
// add defaultRenderer for complex aliases with their own compile functions.
|
||||
let originalEngineInstance = await this.getEngine(extensionEntry.key, extensionMap);
|
||||
instance.setDefaultEngine(originalEngineInstance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async getEngine(name, extensionMap) {
|
||||
// Warning about engine deprecation
|
||||
if (this.isEngineRemovedFromCore(name)) {
|
||||
throw new Error(
|
||||
`Per the 11ty Community Survey (2023), the "${name}" template language was moved from core to an officially supported plugin in v3.0. These plugins live here: https://github.com/11ty/eleventy-plugin-template-languages and are documented on their respective template language docs at https://v3.11ty.dev/docs/languages/ You are also empowered to implement *any* template language yourself using https://v3.11ty.dev/docs/languages/custom/`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.hasEngine(name)) {
|
||||
throw new Error(`Template Engine ${name} does not exist in getEngine()`);
|
||||
}
|
||||
|
||||
// TODO these cached engines should be based on extensions not name, then we can remove the error in
|
||||
// "Double override (not aliases) throws an error" test in TemplateRenderCustomTest.js
|
||||
if (!this.engineCache[name]) {
|
||||
debug("Engine cache miss %o (should only happen once per type)", name);
|
||||
// Make sure cache key is based on name and not path
|
||||
// Custom class is used for all plugins, cache once per plugin
|
||||
this.engineCache[name] = this.#getEngine(name, extensionMap);
|
||||
}
|
||||
|
||||
return this.engineCache[name];
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateEngineManager;
|
||||
67
node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js
generated
vendored
Normal file
67
node_modules/@11ty/eleventy/src/Engines/Util/ContextAugmenter.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
const DATA_KEYS = ["page", "eleventy"];
|
||||
|
||||
function augmentFunction(fn, options = {}) {
|
||||
let t = typeof fn;
|
||||
if (t !== "function") {
|
||||
throw new Error(
|
||||
"Invalid type passed to `augmentFunction`. A function was expected and received: " + t,
|
||||
);
|
||||
}
|
||||
|
||||
/** @this {object} */
|
||||
return function (...args) {
|
||||
let context = augmentObject(this || {}, options);
|
||||
return fn.call(context, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
function augmentObject(targetObject, options = {}) {
|
||||
options = Object.assign(
|
||||
{
|
||||
source: undefined, // where to copy from
|
||||
overwrite: true,
|
||||
lazy: false, // lazily fetch the property
|
||||
// getter: function() {},
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
for (let key of DATA_KEYS) {
|
||||
// Skip if overwrite: false and prop already exists on target
|
||||
if (!options.overwrite && targetObject[key]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (options.lazy) {
|
||||
let value;
|
||||
if (typeof options.getter == "function") {
|
||||
value = () => options.getter(key, options.source);
|
||||
} else {
|
||||
value = () => options.source?.[key];
|
||||
}
|
||||
|
||||
// lazy getter important for Liquid strictVariables support
|
||||
Object.defineProperty(targetObject, key, {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
let value;
|
||||
if (typeof options.getter == "function") {
|
||||
value = options.getter(key, options.source);
|
||||
} else {
|
||||
value = options.source?.[key];
|
||||
}
|
||||
|
||||
if (value) {
|
||||
targetObject[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetObject;
|
||||
}
|
||||
|
||||
export { DATA_KEYS as augmentKeys, augmentFunction, augmentObject };
|
||||
9
node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Errors/DuplicatePermalinkOutputError.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class DuplicatePermalinkOutputError extends EleventyBaseError {
|
||||
get removeDuplicateErrorStringFromOutput() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default DuplicatePermalinkOutputError;
|
||||
24
node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js
generated
vendored
Normal file
24
node_modules/@11ty/eleventy/src/Errors/EleventyBaseError.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* This class serves as basis for all Eleventy-specific errors.
|
||||
* @ignore
|
||||
*/
|
||||
class EleventyBaseError extends Error {
|
||||
/**
|
||||
* @param {string} message - The error message to display.
|
||||
* @param {unknown} [originalError] - The original error caught.
|
||||
*/
|
||||
constructor(message, originalError) {
|
||||
super(message);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
if (originalError) {
|
||||
this.originalError = originalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
export default EleventyBaseError;
|
||||
151
node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js
generated
vendored
Normal file
151
node_modules/@11ty/eleventy/src/Errors/EleventyErrorHandler.js
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
import util from "node:util";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import ConsoleLogger from "../Util/ConsoleLogger.js";
|
||||
import EleventyErrorUtil from "./EleventyErrorUtil.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EleventyErrorHandler");
|
||||
|
||||
class EleventyErrorHandler {
|
||||
constructor() {
|
||||
this._isVerbose = true;
|
||||
}
|
||||
|
||||
get isVerbose() {
|
||||
return this._isVerbose;
|
||||
}
|
||||
|
||||
set isVerbose(verbose) {
|
||||
this._isVerbose = !!verbose;
|
||||
this.logger.isVerbose = !!verbose;
|
||||
}
|
||||
|
||||
get logger() {
|
||||
if (!this._logger) {
|
||||
this._logger = new ConsoleLogger();
|
||||
this._logger.isVerbose = this.isVerbose;
|
||||
}
|
||||
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
set logger(logger) {
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
warn(e, msg) {
|
||||
if (msg) {
|
||||
this.initialMessage(msg, "warn", "yellow");
|
||||
}
|
||||
this.log(e, "warn");
|
||||
}
|
||||
|
||||
fatal(e, msg) {
|
||||
this.error(e, msg);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
once(type, e, msg) {
|
||||
if (e.__errorAlreadyLogged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[type || "error"](e, msg);
|
||||
|
||||
Object.defineProperty(e, "__errorAlreadyLogged", {
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
|
||||
error(e, msg) {
|
||||
if (msg) {
|
||||
this.initialMessage(msg, "error", "red", true);
|
||||
}
|
||||
this.log(e, "error", undefined, true);
|
||||
}
|
||||
|
||||
static getTotalErrorCount(e) {
|
||||
let totalErrorCount = 0;
|
||||
let errorCountRef = e;
|
||||
while (errorCountRef) {
|
||||
totalErrorCount++;
|
||||
errorCountRef = errorCountRef.originalError;
|
||||
}
|
||||
return totalErrorCount;
|
||||
}
|
||||
|
||||
//https://nodejs.org/api/process.html
|
||||
log(e, type = "log", chalkColor = "", forceToConsole = false) {
|
||||
if (process.env.DEBUG) {
|
||||
debug("Full error object: %o", util.inspect(e, { showHidden: false, depth: null }));
|
||||
}
|
||||
|
||||
let showStack = true;
|
||||
if (e.skipOriginalStack) {
|
||||
showStack = false;
|
||||
}
|
||||
|
||||
let totalErrorCount = EleventyErrorHandler.getTotalErrorCount(e);
|
||||
let ref = e;
|
||||
let index = 1;
|
||||
while (ref) {
|
||||
let nextRef = ref.originalError;
|
||||
|
||||
// Nunjucks wraps errors and puts the original in error.cause
|
||||
if (nextRef?.cause?.originalError) {
|
||||
nextRef = nextRef.cause.originalError;
|
||||
}
|
||||
|
||||
if (!nextRef && EleventyErrorUtil.hasEmbeddedError(ref.message)) {
|
||||
nextRef = EleventyErrorUtil.deconvertErrorToObject(ref);
|
||||
}
|
||||
|
||||
if (nextRef?.skipOriginalStack) {
|
||||
showStack = false;
|
||||
}
|
||||
|
||||
this.logger.message(
|
||||
`${totalErrorCount > 1 ? `${index}. ` : ""}${(
|
||||
EleventyErrorUtil.cleanMessage(ref.message) || "(No error message provided)"
|
||||
).trim()}${ref.name !== "Error" ? ` (via ${ref.name})` : ""}`,
|
||||
type,
|
||||
chalkColor,
|
||||
forceToConsole,
|
||||
);
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
debug(`(${type} stack): ${ref.stack}`);
|
||||
} else if (!nextRef) {
|
||||
// last error in the loop
|
||||
|
||||
// remove duplicate error messages if the stack contains the original message output above
|
||||
let stackStr = ref.stack || "";
|
||||
if (e.removeDuplicateErrorStringFromOutput) {
|
||||
stackStr = stackStr.replace(
|
||||
`${ref.name}: ${ref.message}`,
|
||||
"(Repeated output has been truncated…)",
|
||||
);
|
||||
}
|
||||
|
||||
if (showStack) {
|
||||
this.logger.message(
|
||||
"\nOriginal error stack trace: " + stackStr,
|
||||
type,
|
||||
chalkColor,
|
||||
forceToConsole,
|
||||
);
|
||||
}
|
||||
}
|
||||
ref = nextRef;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
initialMessage(message, type = "log", chalkColor = "blue", forceToConsole = false) {
|
||||
if (message) {
|
||||
this.logger.message(message + ":", type, chalkColor, forceToConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { EleventyErrorHandler };
|
||||
72
node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js
generated
vendored
Normal file
72
node_modules/@11ty/eleventy/src/Errors/EleventyErrorUtil.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import TemplateContentPrematureUseError from "./TemplateContentPrematureUseError.js";
|
||||
|
||||
/* Hack to workaround the variety of error handling schemes in template languages */
|
||||
class EleventyErrorUtil {
|
||||
static get prefix() {
|
||||
return ">>>>>11ty>>>>>";
|
||||
}
|
||||
static get suffix() {
|
||||
return "<<<<<11ty<<<<<";
|
||||
}
|
||||
|
||||
static hasEmbeddedError(msg) {
|
||||
if (!msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return msg.includes(EleventyErrorUtil.prefix) && msg.includes(EleventyErrorUtil.suffix);
|
||||
}
|
||||
|
||||
static cleanMessage(msg) {
|
||||
if (!msg) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!EleventyErrorUtil.hasEmbeddedError(msg)) {
|
||||
return "" + msg;
|
||||
}
|
||||
|
||||
return msg.slice(0, Math.max(0, msg.indexOf(EleventyErrorUtil.prefix)));
|
||||
}
|
||||
|
||||
static deconvertErrorToObject(error) {
|
||||
if (!error || !error.message) {
|
||||
throw new Error(`Could not convert error object from: ${error}`);
|
||||
}
|
||||
if (!EleventyErrorUtil.hasEmbeddedError(error.message)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let msg = error.message;
|
||||
let objectString = msg.substring(
|
||||
msg.indexOf(EleventyErrorUtil.prefix) + EleventyErrorUtil.prefix.length,
|
||||
msg.lastIndexOf(EleventyErrorUtil.suffix),
|
||||
);
|
||||
let obj = JSON.parse(objectString);
|
||||
obj.name = error.name;
|
||||
return obj;
|
||||
}
|
||||
|
||||
// pass an error through a random template engine’s error handling unscathed
|
||||
static convertErrorToString(error) {
|
||||
return (
|
||||
EleventyErrorUtil.prefix +
|
||||
JSON.stringify({ message: error.message, stack: error.stack }) +
|
||||
EleventyErrorUtil.suffix
|
||||
);
|
||||
}
|
||||
|
||||
static isPrematureTemplateContentError(e) {
|
||||
// TODO the rest of the template engines
|
||||
return (
|
||||
e instanceof TemplateContentPrematureUseError ||
|
||||
(e.originalError &&
|
||||
(e.originalError.name === "RenderError" ||
|
||||
e.originalError.name === "UndefinedVariableError") &&
|
||||
e.originalError.originalError instanceof TemplateContentPrematureUseError) || // Liquid
|
||||
(e.message || "").indexOf("TemplateContentPrematureUseError") > -1
|
||||
); // Nunjucks
|
||||
}
|
||||
}
|
||||
|
||||
export default EleventyErrorUtil;
|
||||
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentPrematureUseError.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class TemplateContentPrematureUseError extends EleventyBaseError {}
|
||||
|
||||
export default TemplateContentPrematureUseError;
|
||||
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Errors/TemplateContentUnrenderedTemplateError.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class TemplateContentUnrenderedTemplateError extends EleventyBaseError {}
|
||||
|
||||
export default TemplateContentUnrenderedTemplateError;
|
||||
5
node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import EleventyBaseError from "./EleventyBaseError.js";
|
||||
|
||||
class UsingCircularTemplateContentReferenceError extends EleventyBaseError {}
|
||||
|
||||
export default UsingCircularTemplateContentReferenceError;
|
||||
23
node_modules/@11ty/eleventy/src/EventBus.js
generated
vendored
Normal file
23
node_modules/@11ty/eleventy/src/EventBus.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EventEmitter from "./Util/AsyncEventEmitter.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:EventBus");
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/EventBus
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
debug("Setting up global EventBus.");
|
||||
/**
|
||||
* Provides a global event bus that modules deep down in the stack can
|
||||
* subscribe to from a global singleton for decoupled pub/sub.
|
||||
* @type {module:11ty/eleventy/Util/AsyncEventEmitter~AsyncEventEmitter}
|
||||
*/
|
||||
let bus = new EventEmitter();
|
||||
bus.setMaxListeners(100);
|
||||
|
||||
debug("EventBus max listener count: %o", bus.getMaxListeners());
|
||||
|
||||
export default bus;
|
||||
102
node_modules/@11ty/eleventy/src/FileSystemSearch.js
generated
vendored
Normal file
102
node_modules/@11ty/eleventy/src/FileSystemSearch.js
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import fastglob from "fast-glob";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:FastGlobManager");
|
||||
|
||||
class FileSystemSearch {
|
||||
constructor() {
|
||||
this.inputs = {};
|
||||
this.outputs = {};
|
||||
this.promises = {};
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
getCacheKey(key, globs, options) {
|
||||
if (Array.isArray(globs)) {
|
||||
globs = globs.sort();
|
||||
}
|
||||
return key + JSON.stringify(globs) + JSON.stringify(options);
|
||||
}
|
||||
|
||||
// returns a promise
|
||||
search(key, globs, options = {}) {
|
||||
debug("Glob search (%o) searching for: %o", key, globs);
|
||||
|
||||
if (!Array.isArray(globs)) {
|
||||
globs = [globs];
|
||||
}
|
||||
|
||||
// Strip leading slashes from everything!
|
||||
globs = globs.map((entry) => TemplatePath.stripLeadingDotSlash(entry));
|
||||
|
||||
if (options.ignore && Array.isArray(options.ignore)) {
|
||||
options.ignore = options.ignore.map((entry) => TemplatePath.stripLeadingDotSlash(entry));
|
||||
debug("Glob search (%o) ignoring: %o", key, options.ignore);
|
||||
}
|
||||
|
||||
let cacheKey = this.getCacheKey(key, globs, options);
|
||||
|
||||
// Only after the promise has resolved
|
||||
if (this.outputs[cacheKey]) {
|
||||
return Array.from(this.outputs[cacheKey]);
|
||||
}
|
||||
|
||||
if (!this.promises[cacheKey]) {
|
||||
this.inputs[cacheKey] = {
|
||||
input: globs,
|
||||
options,
|
||||
};
|
||||
|
||||
this.count++;
|
||||
|
||||
this.promises[cacheKey] = fastglob(
|
||||
globs,
|
||||
Object.assign(
|
||||
{
|
||||
caseSensitiveMatch: false, // insensitive
|
||||
dot: true,
|
||||
},
|
||||
options,
|
||||
),
|
||||
).then((results) => {
|
||||
this.outputs[cacheKey] = new Set(
|
||||
results.map((entry) => TemplatePath.addLeadingDotSlash(entry)),
|
||||
);
|
||||
return Array.from(this.outputs[cacheKey]);
|
||||
});
|
||||
}
|
||||
|
||||
// may be an unresolved promise
|
||||
return this.promises[cacheKey];
|
||||
}
|
||||
|
||||
_modify(path, setOperation) {
|
||||
path = TemplatePath.stripLeadingDotSlash(path);
|
||||
|
||||
let normalized = TemplatePath.addLeadingDotSlash(path);
|
||||
|
||||
for (let key in this.inputs) {
|
||||
let { input, options } = this.inputs[key];
|
||||
if (
|
||||
isGlobMatch(path, input, {
|
||||
ignore: options.ignore,
|
||||
})
|
||||
) {
|
||||
this.outputs[key][setOperation](normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(path) {
|
||||
this._modify(path, "add");
|
||||
}
|
||||
|
||||
delete(path) {
|
||||
this._modify(path, "delete");
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSystemSearch;
|
||||
20
node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js
generated
vendored
Normal file
20
node_modules/@11ty/eleventy/src/Filters/GetCollectionItem.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function getCollectionItem(collection, page, modifier = 0) {
|
||||
let j = 0;
|
||||
let index;
|
||||
for (let item of collection) {
|
||||
if (
|
||||
item.inputPath === page.inputPath &&
|
||||
(item.outputPath === page.outputPath || item.url === page.url)
|
||||
) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if (index !== undefined && collection?.length) {
|
||||
if (index + modifier >= 0 && index + modifier < collection.length) {
|
||||
return collection[index + modifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
17
node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js
generated
vendored
Normal file
17
node_modules/@11ty/eleventy/src/Filters/GetCollectionItemIndex.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// TODO locale-friendly, see GetLocaleCollectionItem.js)
|
||||
export default function getCollectionItemIndex(collection, page) {
|
||||
if (!page) {
|
||||
page = this.page;
|
||||
}
|
||||
|
||||
let j = 0;
|
||||
for (let item of collection) {
|
||||
if (
|
||||
item.inputPath === page.inputPath &&
|
||||
(item.outputPath === page.outputPath || item.url === page.url)
|
||||
) {
|
||||
return j;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
47
node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js
generated
vendored
Normal file
47
node_modules/@11ty/eleventy/src/Filters/GetLocaleCollectionItem.js
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import getCollectionItem from "./GetCollectionItem.js";
|
||||
|
||||
// Work with I18n Plugin src/Plugins/I18nPlugin.js to retrieve root pages (not i18n pages)
|
||||
function resolveRootPage(config, pageOverride, languageCode) {
|
||||
let localeFilter = config.getFilter("locale_page");
|
||||
if (!localeFilter || typeof localeFilter !== "function") {
|
||||
return pageOverride;
|
||||
}
|
||||
|
||||
// returns root/default-language `page` object
|
||||
return localeFilter.call(this, pageOverride, languageCode);
|
||||
}
|
||||
|
||||
function getLocaleCollectionItem(config, collection, pageOverride, langCode, indexModifier = 0) {
|
||||
if (!langCode) {
|
||||
// if page.lang exists (2.0.0-canary.14 and i18n plugin added, use page language)
|
||||
if (this.page.lang) {
|
||||
langCode = this.page.lang;
|
||||
} else {
|
||||
return getCollectionItem(collection, pageOverride || this.page, indexModifier);
|
||||
}
|
||||
}
|
||||
|
||||
let rootPage = resolveRootPage.call(this, config, pageOverride); // implied current page, default language
|
||||
let modifiedRootItem = getCollectionItem(collection, rootPage, indexModifier);
|
||||
if (!modifiedRootItem) {
|
||||
return; // no root item exists for the previous/next page
|
||||
}
|
||||
|
||||
// Resolve modified root `page` back to locale `page`
|
||||
// This will return a non localized version of the page as a fallback
|
||||
let modifiedLocalePage = resolveRootPage.call(this, config, modifiedRootItem.data.page, langCode);
|
||||
// already localized (or default language)
|
||||
if (!("__locale_page_resolved" in modifiedLocalePage)) {
|
||||
return modifiedRootItem;
|
||||
}
|
||||
|
||||
// find the modified locale `page` again in `collections.all`
|
||||
let all =
|
||||
this.collections?.all ||
|
||||
this.ctx?.collections?.all ||
|
||||
this.context?.environments?.collections?.all ||
|
||||
[];
|
||||
return getCollectionItem(all, modifiedLocalePage, 0);
|
||||
}
|
||||
|
||||
export default getLocaleCollectionItem;
|
||||
14
node_modules/@11ty/eleventy/src/Filters/Slug.js
generated
vendored
Normal file
14
node_modules/@11ty/eleventy/src/Filters/Slug.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import slugify from "slugify";
|
||||
|
||||
export default function (str, options = {}) {
|
||||
return slugify(
|
||||
"" + str,
|
||||
Object.assign(
|
||||
{
|
||||
replacement: "-",
|
||||
lower: true,
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
14
node_modules/@11ty/eleventy/src/Filters/Slugify.js
generated
vendored
Normal file
14
node_modules/@11ty/eleventy/src/Filters/Slugify.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
export default function (str, options = {}) {
|
||||
return slugify(
|
||||
"" + str,
|
||||
Object.assign(
|
||||
{
|
||||
// lowercase: true, // default
|
||||
decamelize: false,
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
35
node_modules/@11ty/eleventy/src/Filters/Url.js
generated
vendored
Normal file
35
node_modules/@11ty/eleventy/src/Filters/Url.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import isValidUrl from "../Util/ValidUrl.js";
|
||||
|
||||
// Note: This filter is used in the Eleventy Navigation plugin in versions prior to 0.3.4
|
||||
export default function (url, pathPrefix) {
|
||||
// work with undefined
|
||||
url = url || "";
|
||||
|
||||
if (isValidUrl(url) || (url.startsWith("//") && url !== "//")) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (pathPrefix === undefined || typeof pathPrefix !== "string") {
|
||||
// When you retrieve this with config.getFilter("url") it
|
||||
// grabs the pathPrefix argument from your config for you (see defaultConfig.js)
|
||||
throw new Error("pathPrefix (String) is required in the `url` filter.");
|
||||
}
|
||||
|
||||
let normUrl = TemplatePath.normalizeUrlPath(url);
|
||||
let normRootDir = TemplatePath.normalizeUrlPath("/", pathPrefix);
|
||||
let normFull = TemplatePath.normalizeUrlPath("/", pathPrefix, url);
|
||||
let isRootDirTrailingSlash =
|
||||
normRootDir.length && normRootDir.charAt(normRootDir.length - 1) === "/";
|
||||
|
||||
// minor difference with straight `normalize`, "" resolves to root dir and not "."
|
||||
// minor difference with straight `normalize`, "/" resolves to root dir
|
||||
if (normUrl === "/" || normUrl === normRootDir) {
|
||||
return normRootDir + (!isRootDirTrailingSlash ? "/" : "");
|
||||
} else if (normUrl.indexOf("/") === 0) {
|
||||
return normFull;
|
||||
}
|
||||
|
||||
return normUrl;
|
||||
}
|
||||
424
node_modules/@11ty/eleventy/src/GlobalDependencyMap.js
generated
vendored
Normal file
424
node_modules/@11ty/eleventy/src/GlobalDependencyMap.js
generated
vendored
Normal file
@@ -0,0 +1,424 @@
|
||||
import { DepGraph } from "dependency-graph";
|
||||
import debugUtil from "debug";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import JavaScriptDependencies from "./Util/JavaScriptDependencies.js";
|
||||
import PathNormalizer from "./Util/PathNormalizer.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:Dependencies");
|
||||
|
||||
class GlobalDependencyMap {
|
||||
// dependency-graph requires these keys to be alphabetic strings
|
||||
static LAYOUT_KEY = "layout";
|
||||
static COLLECTION_PREFIX = "__collection:";
|
||||
|
||||
#templateConfig;
|
||||
|
||||
reset() {
|
||||
this._map = undefined;
|
||||
}
|
||||
|
||||
setIsEsm(isEsm) {
|
||||
this.isEsm = isEsm;
|
||||
}
|
||||
|
||||
setTemplateConfig(templateConfig) {
|
||||
this.#templateConfig = templateConfig;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (this.config) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
|
||||
// These have leading dot slashes, but so do the paths from Eleventy
|
||||
this.config.events.once("eleventy.layouts", async (layouts) => {
|
||||
await this.addLayoutsToMap(layouts);
|
||||
});
|
||||
}
|
||||
|
||||
filterOutLayouts(nodes = []) {
|
||||
return nodes.filter((node) => {
|
||||
let data = this.map.getNodeData(node);
|
||||
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
filterOutCollections(nodes = []) {
|
||||
return nodes.filter((node) => !node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX));
|
||||
}
|
||||
|
||||
removeLayoutNodes(normalizedLayouts) {
|
||||
let nodes = this.map.overallOrder();
|
||||
for (let node of nodes) {
|
||||
let data = this.map.getNodeData(node);
|
||||
if (!data || !data.type || data.type !== GlobalDependencyMap.LAYOUT_KEY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// previous layout is not in the new layout map (no templates are using it)
|
||||
if (!normalizedLayouts[node]) {
|
||||
this.map.removeNode(node);
|
||||
}
|
||||
// important: if the layout map changed to have different templates (but was not removed)
|
||||
// this is already handled by `resetNode` called via TemplateMap
|
||||
}
|
||||
}
|
||||
|
||||
// Eleventy Layouts don’t show up in the dependency graph, so we handle those separately
|
||||
async addLayoutsToMap(layouts) {
|
||||
let normalizedLayouts = this.normalizeLayoutsObject(layouts);
|
||||
|
||||
// Clear out any previous layout relationships to make way for the new ones
|
||||
this.removeLayoutNodes(normalizedLayouts);
|
||||
|
||||
for (let layout in normalizedLayouts) {
|
||||
// We add this pre-emptively to add the `layout` data
|
||||
if (!this.map.hasNode(layout)) {
|
||||
this.map.addNode(layout, {
|
||||
type: GlobalDependencyMap.LAYOUT_KEY,
|
||||
});
|
||||
} else {
|
||||
this.map.setNodeData(layout, {
|
||||
type: GlobalDependencyMap.LAYOUT_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
// Potential improvement: only add the first template in the chain for a template and manage any upstream layouts by their own relationships
|
||||
for (let pageTemplate of normalizedLayouts[layout]) {
|
||||
this.addDependency(pageTemplate, [layout]);
|
||||
}
|
||||
|
||||
if (this.#templateConfig?.shouldSpiderJavaScriptDependencies()) {
|
||||
let deps = await JavaScriptDependencies.getDependencies([layout], this.isEsm);
|
||||
this.addDependency(layout, deps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get map() {
|
||||
if (!this._map) {
|
||||
this._map = new DepGraph({ circular: true });
|
||||
}
|
||||
|
||||
return this._map;
|
||||
}
|
||||
|
||||
set map(graph) {
|
||||
this._map = graph;
|
||||
}
|
||||
|
||||
normalizeNode(node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO tests for this
|
||||
// Fix URL objects passed in (sass does this)
|
||||
if (typeof node !== "string" && "toString" in node) {
|
||||
node = node.toString();
|
||||
}
|
||||
|
||||
if (typeof node !== "string") {
|
||||
throw new Error("`addDependencies` files must be strings. Received:" + node);
|
||||
}
|
||||
|
||||
return PathNormalizer.fullNormalization(node);
|
||||
}
|
||||
|
||||
normalizeLayoutsObject(layouts) {
|
||||
let o = {};
|
||||
for (let rawLayout in layouts) {
|
||||
let layout = this.normalizeNode(rawLayout);
|
||||
o[layout] = layouts[rawLayout].map((entry) => this.normalizeNode(entry));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
getDependantsFor(node) {
|
||||
if (!node) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
if (!this.map.hasNode(node)) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
// Direct dependants and dependencies, both publish and consume from collections
|
||||
return this.map.directDependantsOf(node);
|
||||
}
|
||||
|
||||
hasNode(node) {
|
||||
return this.map.hasNode(this.normalizeNode(node));
|
||||
}
|
||||
|
||||
findCollectionsRemovedFrom(node, collectionNames) {
|
||||
if (!this.hasNode(node)) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
let prevDeps = this.getDependantsFor(node)
|
||||
.filter((entry) => {
|
||||
return entry.startsWith(GlobalDependencyMap.COLLECTION_PREFIX);
|
||||
})
|
||||
.map((entry) => {
|
||||
return GlobalDependencyMap.getEntryFromCollectionKey(entry);
|
||||
});
|
||||
|
||||
let prevDepsSet = new Set(prevDeps);
|
||||
let deleted = new Set();
|
||||
for (let dep of prevDepsSet) {
|
||||
if (!collectionNames.has(dep)) {
|
||||
deleted.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
resetNode(node) {
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
if (!this.map.hasNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don’t want to remove relationships that consume this, controlled by the upstream content
|
||||
// for (let dep of this.map.directDependantsOf(node)) {
|
||||
// this.map.removeDependency(dep, node);
|
||||
// }
|
||||
|
||||
for (let dep of this.map.directDependenciesOf(node)) {
|
||||
this.map.removeDependency(node, dep);
|
||||
}
|
||||
}
|
||||
|
||||
getTemplatesThatConsumeCollections(collectionNames) {
|
||||
let templates = new Set();
|
||||
for (let name of collectionNames) {
|
||||
let collectionName = GlobalDependencyMap.getCollectionKeyForEntry(name);
|
||||
if (!this.map.hasNode(collectionName)) {
|
||||
continue;
|
||||
}
|
||||
for (let node of this.map.dependantsOf(collectionName)) {
|
||||
if (!node.startsWith(GlobalDependencyMap.COLLECTION_PREFIX)) {
|
||||
let data = this.map.getNodeData(node);
|
||||
if (!data || !data.type || data.type != GlobalDependencyMap.LAYOUT_KEY) {
|
||||
templates.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
getLayoutsUsedBy(node) {
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
if (!this.map.hasNode(node)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let layouts = [];
|
||||
|
||||
// include self, if layout
|
||||
if (this.map.getNodeData(node)?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
layouts.push(node);
|
||||
}
|
||||
|
||||
this.map.dependantsOf(node).forEach((node) => {
|
||||
let data = this.map.getNodeData(node);
|
||||
// we only want layouts
|
||||
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
return layouts.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
// In order
|
||||
// Does not include original templatePaths (unless *they* are second-order relevant)
|
||||
getTemplatesRelevantToTemplateList(templatePaths) {
|
||||
let overallOrder = this.map.overallOrder();
|
||||
overallOrder = this.filterOutLayouts(overallOrder);
|
||||
overallOrder = this.filterOutCollections(overallOrder);
|
||||
|
||||
let relevantLookup = {};
|
||||
for (let inputPath of templatePaths) {
|
||||
inputPath = TemplatePath.stripLeadingDotSlash(inputPath);
|
||||
|
||||
let deps = this.getDependencies(inputPath, false);
|
||||
|
||||
if (Array.isArray(deps)) {
|
||||
let paths = this.filterOutCollections(deps);
|
||||
for (let node of paths) {
|
||||
relevantLookup[node] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return overallOrder.filter((node) => {
|
||||
if (relevantLookup[node]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Layouts are not relevant to compile cache and can be ignored
|
||||
getDependencies(node, includeLayouts = true) {
|
||||
node = this.normalizeNode(node);
|
||||
|
||||
// `false` means the Node was unknown
|
||||
if (!this.map.hasNode(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.map.dependenciesOf(node).filter((node) => {
|
||||
if (includeLayouts) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// When includeLayouts is `false` we want to filter out layouts
|
||||
let data = this.map.getNodeData(node);
|
||||
if (data?.type === GlobalDependencyMap.LAYOUT_KEY) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// node arguments are already normalized
|
||||
_addDependency(from, toArray = []) {
|
||||
this.map.addNode(from);
|
||||
|
||||
if (!Array.isArray(toArray)) {
|
||||
throw new Error("Second argument to `addDependency` must be an Array.");
|
||||
}
|
||||
|
||||
// debug("%o depends on %o", from, toArray);
|
||||
|
||||
for (let to of toArray) {
|
||||
if (!this.map.hasNode(to)) {
|
||||
this.map.addNode(to);
|
||||
}
|
||||
if (from !== to) {
|
||||
this.map.addDependency(from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDependency(from, toArray = []) {
|
||||
this._addDependency(
|
||||
this.normalizeNode(from),
|
||||
toArray.map((to) => this.normalizeNode(to)),
|
||||
);
|
||||
}
|
||||
|
||||
static getEntryFromCollectionKey(entry) {
|
||||
return entry.slice(GlobalDependencyMap.COLLECTION_PREFIX.length);
|
||||
}
|
||||
|
||||
static getCollectionKeyForEntry(entry) {
|
||||
return `${GlobalDependencyMap.COLLECTION_PREFIX}${entry}`;
|
||||
}
|
||||
|
||||
addDependencyConsumesCollection(from, collectionName) {
|
||||
let nodeName = this.normalizeNode(from);
|
||||
debug("%o depends on collection: %o", nodeName, collectionName);
|
||||
this._addDependency(nodeName, [GlobalDependencyMap.getCollectionKeyForEntry(collectionName)]);
|
||||
}
|
||||
|
||||
addDependencyPublishesToCollection(from, collectionName) {
|
||||
let normalizedFrom = this.normalizeNode(from);
|
||||
this._addDependency(GlobalDependencyMap.getCollectionKeyForEntry(collectionName), [
|
||||
normalizedFrom,
|
||||
]);
|
||||
}
|
||||
|
||||
// Layouts are not relevant to compile cache and can be ignored
|
||||
hasDependency(from, to, includeLayouts) {
|
||||
to = this.normalizeNode(to);
|
||||
|
||||
let deps = this.getDependencies(from, includeLayouts); // normalizes `from`
|
||||
|
||||
if (!deps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return deps.includes(to);
|
||||
}
|
||||
|
||||
// Layouts are not relevant to compile cache and can be ignored
|
||||
isFileRelevantTo(fullTemplateInputPath, comparisonFile, includeLayouts) {
|
||||
fullTemplateInputPath = this.normalizeNode(fullTemplateInputPath);
|
||||
comparisonFile = this.normalizeNode(comparisonFile);
|
||||
|
||||
// No watch/serve changed file
|
||||
if (!comparisonFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The file that changed is the relevant file
|
||||
if (fullTemplateInputPath === comparisonFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The file that changed is a dependency of the template
|
||||
// comparisonFile is used by fullTemplateInputPath
|
||||
if (this.hasDependency(fullTemplateInputPath, comparisonFile, includeLayouts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isFileUsedBy(parent, child, includeLayouts) {
|
||||
if (this.hasDependency(parent, child, includeLayouts)) {
|
||||
// child is used by parent
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
stringify() {
|
||||
return JSON.stringify(this.map, function replacer(key, value) {
|
||||
// Serialize internal Map objects.
|
||||
if (value instanceof Map) {
|
||||
let obj = {};
|
||||
for (let [k, v] of value) {
|
||||
obj[k] = v;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
restore(persisted) {
|
||||
let obj = JSON.parse(persisted);
|
||||
let graph = new DepGraph({ circular: true });
|
||||
|
||||
// https://github.com/jriecken/dependency-graph/issues/44
|
||||
// Restore top level serialized Map objects (in stringify above)
|
||||
for (let key in obj) {
|
||||
let map = graph[key];
|
||||
for (let k in obj[key]) {
|
||||
let v = obj[key][k];
|
||||
map.set(k, v);
|
||||
}
|
||||
}
|
||||
this.map = graph;
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalDependencyMap;
|
||||
151
node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
generated
vendored
Normal file
151
node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
import { DeepCopy } from "@11ty/eleventy-utils";
|
||||
import urlFilter from "../Filters/Url.js";
|
||||
import PathPrefixer from "../Util/PathPrefixer.js";
|
||||
import { HtmlTransformer } from "../Util/HtmlTransformer.js";
|
||||
import isValidUrl from "../Util/ValidUrl.js";
|
||||
|
||||
function addPathPrefixToUrl(url, pathPrefix, base) {
|
||||
let u;
|
||||
if (base) {
|
||||
u = new URL(url, base);
|
||||
} else {
|
||||
u = new URL(url);
|
||||
}
|
||||
|
||||
// Add pathPrefix **after** url is transformed using base
|
||||
if (pathPrefix) {
|
||||
u.pathname = PathPrefixer.joinUrlParts(pathPrefix, u.pathname);
|
||||
}
|
||||
return u.toString();
|
||||
}
|
||||
|
||||
// pathprefix is only used when overrideBase is a full URL
|
||||
function transformUrl(url, base, opts = {}) {
|
||||
let { pathPrefix, pageUrl } = opts;
|
||||
|
||||
// full URL, return as-is
|
||||
if (isValidUrl(url)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Not a full URL, but with a full base URL
|
||||
// e.g. relative urls like "subdir/", "../subdir", "./subdir"
|
||||
if (isValidUrl(base)) {
|
||||
// convert relative paths to absolute path first using pageUrl
|
||||
if (pageUrl && !url.startsWith("/")) {
|
||||
let urlObj = new URL(url, `http://example.com${pageUrl}`);
|
||||
url = urlObj.pathname + (urlObj.hash || "");
|
||||
}
|
||||
|
||||
return addPathPrefixToUrl(url, pathPrefix, base);
|
||||
}
|
||||
|
||||
// Not a full URL, nor a full base URL (call the built-in `url` filter)
|
||||
return urlFilter(url, base);
|
||||
}
|
||||
|
||||
function eleventyHtmlBasePlugin(eleventyConfig, defaultOptions = {}) {
|
||||
let opts = DeepCopy(
|
||||
{
|
||||
// eleventyConfig.pathPrefix is new in Eleventy 2.0.0-canary.15
|
||||
// `base` can be a directory (for path prefix transformations)
|
||||
// OR a full URL with origin and pathname
|
||||
baseHref: eleventyConfig.pathPrefix,
|
||||
|
||||
extensions: "html",
|
||||
},
|
||||
defaultOptions,
|
||||
);
|
||||
|
||||
// `filters` option to rename filters was removed in 3.0.0-alpha.13
|
||||
// Renaming these would cause issues in other plugins (e.g. RSS)
|
||||
if (opts.filters !== undefined) {
|
||||
throw new Error(
|
||||
"The `filters` option in the HTML Base plugin was removed to prevent future cross-plugin compatibility issues.",
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.baseHref === undefined) {
|
||||
throw new Error("The `base` option is required in the HTML Base plugin.");
|
||||
}
|
||||
|
||||
eleventyConfig.addFilter("addPathPrefixToFullUrl", function (url) {
|
||||
return addPathPrefixToUrl(url, eleventyConfig.pathPrefix);
|
||||
});
|
||||
|
||||
// Apply to one URL
|
||||
eleventyConfig.addFilter(
|
||||
"htmlBaseUrl",
|
||||
|
||||
/** @this {object} */
|
||||
function (url, baseOverride, pageUrlOverride) {
|
||||
let base = baseOverride || opts.baseHref;
|
||||
|
||||
// Do nothing with a default base
|
||||
if (base === "/") {
|
||||
return url;
|
||||
}
|
||||
|
||||
return transformUrl(url, base, {
|
||||
pathPrefix: eleventyConfig.pathPrefix,
|
||||
pageUrl: pageUrlOverride || this.page?.url,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Apply to a block of HTML
|
||||
eleventyConfig.addAsyncFilter(
|
||||
"transformWithHtmlBase",
|
||||
|
||||
/** @this {object} */
|
||||
function (content, baseOverride, pageUrlOverride) {
|
||||
let base = baseOverride || opts.baseHref;
|
||||
|
||||
// Do nothing with a default base
|
||||
if (base === "/") {
|
||||
return content;
|
||||
}
|
||||
|
||||
return HtmlTransformer.transformStandalone(content, (url) => {
|
||||
return transformUrl(url.trim(), base, {
|
||||
pathPrefix: eleventyConfig.pathPrefix,
|
||||
pageUrl: pageUrlOverride || this.page?.url,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Apply to all HTML output in your project
|
||||
eleventyConfig.htmlTransformer.addUrlTransform(
|
||||
opts.extensions,
|
||||
|
||||
/** @this {object} */
|
||||
function (urlInMarkup) {
|
||||
// baseHref override is via renderTransforms filter for adding the absolute URL (e.g. https://example.com/pathPrefix/) for RSS/Atom/JSON feeds
|
||||
return transformUrl(urlInMarkup.trim(), this.baseHref || opts.baseHref, {
|
||||
pathPrefix: eleventyConfig.pathPrefix,
|
||||
pageUrl: this.url,
|
||||
});
|
||||
},
|
||||
{
|
||||
priority: -1, // run last (especially after PathToUrl transform)
|
||||
enabled: function (context) {
|
||||
// Enabled when pathPrefix is non-default or via renderTransforms
|
||||
return context.baseHref || opts.baseHref !== "/";
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/html-base-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default eleventyHtmlBasePlugin;
|
||||
export { transformUrl as applyBaseToUrl };
|
||||
317
node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
generated
vendored
Normal file
317
node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
import { bcp47Normalize } from "bcp-47-normalize";
|
||||
import iso639 from "iso-639-1";
|
||||
import { DeepCopy } from "@11ty/eleventy-utils";
|
||||
|
||||
// pathPrefix note:
|
||||
// When using `locale_url` filter with the `url` filter, `locale_url` must run first like
|
||||
// `| locale_url | url`. If you run `| url | locale_url` it won’t match correctly.
|
||||
|
||||
// TODO improvement would be to throw an error if `locale_url` finds a url with the
|
||||
// path prefix at the beginning? Would need a better way to know `url` has transformed a string
|
||||
// rather than just raw comparison.
|
||||
// e.g. --pathprefix=/en/ should return `/en/en/` for `/en/index.liquid`
|
||||
|
||||
class LangUtils {
|
||||
static getLanguageCodeFromInputPath(filepath) {
|
||||
return (filepath || "").split("/").find((entry) => Comparator.isLangCode(entry));
|
||||
}
|
||||
|
||||
static getLanguageCodeFromUrl(url) {
|
||||
let s = (url || "").split("/");
|
||||
return s.length > 0 && Comparator.isLangCode(s[1]) ? s[1] : "";
|
||||
}
|
||||
|
||||
static swapLanguageCodeNoCheck(str, langCode) {
|
||||
let found = false;
|
||||
return str
|
||||
.split("/")
|
||||
.map((entry) => {
|
||||
// only match the first one
|
||||
if (!found && Comparator.isLangCode(entry)) {
|
||||
found = true;
|
||||
return langCode;
|
||||
}
|
||||
return entry;
|
||||
})
|
||||
.join("/");
|
||||
}
|
||||
|
||||
static swapLanguageCode(str, langCode) {
|
||||
if (!Comparator.isLangCode(langCode)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
return LangUtils.swapLanguageCodeNoCheck(str, langCode);
|
||||
}
|
||||
}
|
||||
|
||||
class Comparator {
|
||||
// https://en.wikipedia.org/wiki/IETF_language_tag#Relation_to_other_standards
|
||||
// Requires a ISO-639-1 language code at the start (2 characters before the first -)
|
||||
static isLangCode(code) {
|
||||
let [s] = (code || "").split("-");
|
||||
if (!iso639.validate(s)) {
|
||||
return false;
|
||||
}
|
||||
if (!bcp47Normalize(code)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static urlHasLangCode(url, code) {
|
||||
if (!Comparator.isLangCode(code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.split("/").some((entry) => entry === code);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeInputPath(inputPath, extensionMap) {
|
||||
if (extensionMap) {
|
||||
return extensionMap.removeTemplateExtension(inputPath);
|
||||
}
|
||||
return inputPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* Input: {
|
||||
* '/en-us/test/': './test/stubs-i18n/en-us/test.11ty.js',
|
||||
* '/en/test/': './test/stubs-i18n/en/test.liquid',
|
||||
* '/es/test/': './test/stubs-i18n/es/test.njk',
|
||||
* '/non-lang-file/': './test/stubs-i18n/non-lang-file.njk'
|
||||
* }
|
||||
*
|
||||
* Output: {
|
||||
* '/en-us/test/': [ { url: '/en/test/' }, { url: '/es/test/' } ],
|
||||
* '/en/test/': [ { url: '/en-us/test/' }, { url: '/es/test/' } ],
|
||||
* '/es/test/': [ { url: '/en-us/test/' }, { url: '/en/test/' } ]
|
||||
* }
|
||||
*/
|
||||
function getLocaleUrlsMap(urlToInputPath, extensionMap, options = {}) {
|
||||
let filemap = {};
|
||||
|
||||
for (let url in urlToInputPath) {
|
||||
// Group number comes from Pagination.js
|
||||
let { inputPath: originalFilepath, groupNumber } = urlToInputPath[url];
|
||||
let filepath = normalizeInputPath(originalFilepath, extensionMap);
|
||||
let replaced =
|
||||
LangUtils.swapLanguageCodeNoCheck(filepath, "__11ty_i18n") + `_group:${groupNumber}`;
|
||||
|
||||
if (!filemap[replaced]) {
|
||||
filemap[replaced] = [];
|
||||
}
|
||||
|
||||
let langCode = LangUtils.getLanguageCodeFromInputPath(originalFilepath);
|
||||
if (!langCode) {
|
||||
langCode = LangUtils.getLanguageCodeFromUrl(url);
|
||||
}
|
||||
if (!langCode) {
|
||||
langCode = options.defaultLanguage;
|
||||
}
|
||||
|
||||
if (langCode) {
|
||||
filemap[replaced].push({
|
||||
url,
|
||||
lang: langCode,
|
||||
label: iso639.getNativeName(langCode.split("-")[0]),
|
||||
});
|
||||
} else {
|
||||
filemap[replaced].push({ url });
|
||||
}
|
||||
}
|
||||
|
||||
// Default sorted by lang code
|
||||
for (let key in filemap) {
|
||||
filemap[key].sort(function (a, b) {
|
||||
if (a.lang < b.lang) {
|
||||
return -1;
|
||||
}
|
||||
if (a.lang > b.lang) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// map of input paths => array of localized urls
|
||||
let urlMap = {};
|
||||
for (let filepath in filemap) {
|
||||
for (let entry of filemap[filepath]) {
|
||||
let url = entry.url;
|
||||
if (!urlMap[url]) {
|
||||
urlMap[url] = filemap[filepath].filter((entry) => {
|
||||
if (entry.lang) {
|
||||
return true;
|
||||
}
|
||||
return entry.url !== url;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlMap;
|
||||
}
|
||||
|
||||
function eleventyI18nPlugin(eleventyConfig, opts = {}) {
|
||||
let options = DeepCopy(
|
||||
{
|
||||
defaultLanguage: "",
|
||||
filters: {
|
||||
url: "locale_url",
|
||||
links: "locale_links",
|
||||
},
|
||||
errorMode: "strict", // allow-fallback, never
|
||||
},
|
||||
opts,
|
||||
);
|
||||
|
||||
if (!options.defaultLanguage) {
|
||||
throw new Error(
|
||||
"You must specify a `defaultLanguage` in Eleventy’s Internationalization (I18N) plugin.",
|
||||
);
|
||||
}
|
||||
|
||||
let extensionMap;
|
||||
eleventyConfig.on("eleventy.extensionmap", (map) => {
|
||||
extensionMap = map;
|
||||
});
|
||||
|
||||
let bench = eleventyConfig.benchmarkManager.get("Aggregate");
|
||||
let contentMaps = {};
|
||||
eleventyConfig.on("eleventy.contentMap", function ({ urlToInputPath, inputPathToUrl }) {
|
||||
let b = bench.get("(i18n Plugin) Setting up content map.");
|
||||
b.before();
|
||||
contentMaps.inputPathToUrl = inputPathToUrl;
|
||||
contentMaps.urlToInputPath = urlToInputPath;
|
||||
|
||||
contentMaps.localeUrlsMap = getLocaleUrlsMap(urlToInputPath, extensionMap, options);
|
||||
b.after();
|
||||
});
|
||||
|
||||
eleventyConfig.addGlobalData("eleventyComputed.page.lang", () => {
|
||||
// if addGlobalData receives a function it will execute it immediately,
|
||||
// so we return a nested function for computed data
|
||||
return (data) => {
|
||||
return LangUtils.getLanguageCodeFromUrl(data.page.url) || options.defaultLanguage;
|
||||
};
|
||||
});
|
||||
|
||||
// Normalize a theoretical URL based on the current page’s language
|
||||
// If a non-localized file exists, returns the URL without a language assigned
|
||||
// Fails if no file exists (localized and not localized)
|
||||
eleventyConfig.addFilter(options.filters.url, function (url, langCodeOverride) {
|
||||
let langCode =
|
||||
langCodeOverride ||
|
||||
LangUtils.getLanguageCodeFromUrl(this.page?.url) ||
|
||||
options.defaultLanguage;
|
||||
|
||||
// Already has a language code on it and has a relevant url with the target language code
|
||||
if (
|
||||
contentMaps.localeUrlsMap[url] ||
|
||||
(!url.endsWith("/") && contentMaps.localeUrlsMap[`${url}/`])
|
||||
) {
|
||||
for (let existingUrlObj of contentMaps.localeUrlsMap[url] ||
|
||||
contentMaps.localeUrlsMap[`${url}/`]) {
|
||||
if (Comparator.urlHasLangCode(existingUrlObj.url, langCode)) {
|
||||
return existingUrlObj.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needs the language code prepended to the URL
|
||||
let prependedLangCodeUrl = `/${langCode}${url}`;
|
||||
if (
|
||||
contentMaps.localeUrlsMap[prependedLangCodeUrl] ||
|
||||
(!prependedLangCodeUrl.endsWith("/") && contentMaps.localeUrlsMap[`${prependedLangCodeUrl}/`])
|
||||
) {
|
||||
return prependedLangCodeUrl;
|
||||
}
|
||||
|
||||
if (
|
||||
contentMaps.urlToInputPath[url] ||
|
||||
(!url.endsWith("/") && contentMaps.urlToInputPath[`${url}/`])
|
||||
) {
|
||||
// this is not a localized file (independent of a language code)
|
||||
if (options.errorMode === "strict") {
|
||||
throw new Error(
|
||||
`Localized file for URL ${prependedLangCodeUrl} was not found in your project. A non-localized version does exist—are you sure you meant to use the \`${options.filters.url}\` filter for this? You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
|
||||
);
|
||||
}
|
||||
} else if (options.errorMode === "allow-fallback") {
|
||||
// You’re linking to a localized file that doesn’t exist!
|
||||
throw new Error(
|
||||
`Localized file for URL ${prependedLangCodeUrl} was not found in your project! You will need to add it if you want to link to it using the \`${options.filters.url}\` filter. You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
|
||||
);
|
||||
}
|
||||
|
||||
return url;
|
||||
});
|
||||
|
||||
// Refactor to use url
|
||||
// Find the links that are localized alternates to the inputPath argument
|
||||
eleventyConfig.addFilter(options.filters.links, function (urlOverride) {
|
||||
let url = urlOverride || this.page?.url;
|
||||
return (contentMaps.localeUrlsMap[url] || []).filter((entry) => {
|
||||
return entry.url !== url;
|
||||
});
|
||||
});
|
||||
|
||||
// Returns a `page`-esque variable for the root default language page
|
||||
// If paginated, returns first result only
|
||||
eleventyConfig.addFilter(
|
||||
"locale_page", // This is not exposed in `options` because it is an Eleventy internals filter (used in get*CollectionItem filters)
|
||||
function (pageOverride, languageCode) {
|
||||
// both args here are optional
|
||||
if (!languageCode) {
|
||||
languageCode = options.defaultLanguage;
|
||||
}
|
||||
|
||||
let page = pageOverride || this.page;
|
||||
let url; // new url
|
||||
if (contentMaps.localeUrlsMap[page.url]) {
|
||||
for (let entry of contentMaps.localeUrlsMap[page.url]) {
|
||||
if (entry.lang === languageCode) {
|
||||
url = entry.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inputPath = LangUtils.swapLanguageCode(page.inputPath, languageCode);
|
||||
|
||||
if (
|
||||
!url ||
|
||||
!Array.isArray(contentMaps.inputPathToUrl[inputPath]) ||
|
||||
contentMaps.inputPathToUrl[inputPath].length === 0
|
||||
) {
|
||||
// no internationalized pages found
|
||||
return page;
|
||||
}
|
||||
|
||||
let result = {
|
||||
// // note that the permalink/slug may be different for the localized file!
|
||||
url,
|
||||
inputPath,
|
||||
filePathStem: LangUtils.swapLanguageCode(page.filePathStem, languageCode),
|
||||
// outputPath is omitted here, not necessary for GetCollectionItem.js if url is provided
|
||||
__locale_page_resolved: true,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { Comparator, LangUtils };
|
||||
|
||||
Object.defineProperty(eleventyI18nPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/i18n-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(eleventyI18nPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default eleventyI18nPlugin;
|
||||
103
node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
generated
vendored
Normal file
103
node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
import matchHelper from "posthtml-match-helper";
|
||||
import { decodeHTML } from "entities";
|
||||
|
||||
import slugifyFilter from "../Filters/Slugify.js";
|
||||
import MemoizeUtil from "../Util/MemoizeFunction.js";
|
||||
|
||||
function getTextNodeContent(node) {
|
||||
if (node.attrs?.["eleventy:id-ignore"] === "") {
|
||||
delete node.attrs["eleventy:id-ignore"];
|
||||
return "";
|
||||
}
|
||||
if (!node.content) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return node.content
|
||||
.map((entry) => {
|
||||
if (typeof entry === "string") {
|
||||
return entry;
|
||||
}
|
||||
if (Array.isArray(entry.content)) {
|
||||
return getTextNodeContent(entry);
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function IdAttributePlugin(eleventyConfig, options = {}) {
|
||||
if (!options.slugify) {
|
||||
options.slugify = MemoizeUtil(slugifyFilter);
|
||||
}
|
||||
if (!options.selector) {
|
||||
options.selector = "[id],h1,h2,h3,h4,h5,h6";
|
||||
}
|
||||
options.decodeEntities = options.decodeEntities ?? true;
|
||||
options.checkDuplicates = options.checkDuplicates ?? "error";
|
||||
|
||||
eleventyConfig.htmlTransformer.addPosthtmlPlugin(
|
||||
"html",
|
||||
function (pluginOptions = {}) {
|
||||
if (typeof options.filter === "function") {
|
||||
if (options.filter(pluginOptions) === false) {
|
||||
return function () {};
|
||||
}
|
||||
}
|
||||
|
||||
return function (tree) {
|
||||
// One per page
|
||||
let conflictCheck = {};
|
||||
// Cache heading nodes for conflict resolution
|
||||
let headingNodes = {};
|
||||
|
||||
tree.match(matchHelper(options.selector), function (node) {
|
||||
if (node.attrs?.id) {
|
||||
let id = node.attrs?.id;
|
||||
if (conflictCheck[id]) {
|
||||
conflictCheck[id]++;
|
||||
if (headingNodes[id]) {
|
||||
// Rename conflicting assigned heading id
|
||||
let newId = `${id}-${conflictCheck[id]}`;
|
||||
headingNodes[newId] = headingNodes[id];
|
||||
headingNodes[newId].attrs.id = newId;
|
||||
delete headingNodes[id];
|
||||
} else if (options.checkDuplicates === "error") {
|
||||
// Existing `id` conflicts with assigned heading id, throw error
|
||||
throw new Error(
|
||||
"Duplicate `id` attribute (" +
|
||||
id +
|
||||
") in markup on " +
|
||||
pluginOptions.page.inputPath,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
conflictCheck[id] = 1;
|
||||
}
|
||||
} else if (!node.attrs?.id && node.content) {
|
||||
node.attrs = node.attrs || {};
|
||||
let textContent = getTextNodeContent(node);
|
||||
if (options.decodeEntities) {
|
||||
textContent = decodeHTML(textContent);
|
||||
}
|
||||
let id = options.slugify(textContent);
|
||||
|
||||
if (conflictCheck[id]) {
|
||||
conflictCheck[id]++;
|
||||
id = `${id}-${conflictCheck[id]}`;
|
||||
} else {
|
||||
conflictCheck[id] = 1;
|
||||
}
|
||||
|
||||
headingNodes[id] = node;
|
||||
node.attrs.id = id;
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
};
|
||||
} /* , {} // pluginOptions */,
|
||||
);
|
||||
}
|
||||
|
||||
export { IdAttributePlugin };
|
||||
177
node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
generated
vendored
Normal file
177
node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
import path from "node:path";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import isValidUrl from "../Util/ValidUrl.js";
|
||||
|
||||
function getValidPath(contentMap, testPath) {
|
||||
let normalized = TemplatePath.addLeadingDotSlash(testPath);
|
||||
|
||||
// it must exist in the content map to be valid
|
||||
if (contentMap[normalized]) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeInputPath(targetInputPath, inputDir, sourceInputPath, contentMap) {
|
||||
// inputDir is optional at the beginning of the developer supplied-path
|
||||
|
||||
// Input directory already on the input path
|
||||
if (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) {
|
||||
let absolutePath = getValidPath(contentMap, targetInputPath);
|
||||
if (absolutePath) {
|
||||
return absolutePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Relative to project input directory
|
||||
let relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath));
|
||||
if (relativeToInputDir) {
|
||||
return relativeToInputDir;
|
||||
}
|
||||
|
||||
if (targetInputPath && !path.isAbsolute(targetInputPath)) {
|
||||
// Relative to source file’s input path
|
||||
let sourceInputDir = TemplatePath.getDirFromFilePath(sourceInputPath);
|
||||
let relativeToSourceFile = getValidPath(
|
||||
contentMap,
|
||||
TemplatePath.join(sourceInputDir, targetInputPath),
|
||||
);
|
||||
if (relativeToSourceFile) {
|
||||
return relativeToSourceFile;
|
||||
}
|
||||
}
|
||||
|
||||
// the transform may have sent in a URL so we just return it as-is
|
||||
return targetInputPath;
|
||||
}
|
||||
|
||||
function parseFilePath(filepath) {
|
||||
try {
|
||||
/* u: URL {
|
||||
href: 'file:///tmpl.njk#anchor',
|
||||
origin: 'null',
|
||||
protocol: 'file:',
|
||||
username: '',
|
||||
password: '',
|
||||
host: '',
|
||||
hostname: '',
|
||||
port: '',
|
||||
pathname: '/tmpl.njk',
|
||||
search: '',
|
||||
searchParams: URLSearchParams {},
|
||||
hash: '#anchor'
|
||||
} */
|
||||
|
||||
// Note that `node:url` -> pathToFileURL creates an absolute path, which we don’t want
|
||||
// URL(`file:#anchor`) gives back a pathname of `/`
|
||||
let u = new URL(`file:${filepath}`);
|
||||
filepath = filepath.replace(u.search, "");
|
||||
filepath = filepath.replace(u.hash, "");
|
||||
|
||||
return [
|
||||
// search includes ?, hash includes #
|
||||
u.search + u.hash,
|
||||
filepath,
|
||||
];
|
||||
} catch (e) {
|
||||
return ["", filepath];
|
||||
}
|
||||
}
|
||||
|
||||
function FilterPlugin(eleventyConfig) {
|
||||
let contentMap;
|
||||
eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
|
||||
contentMap = inputPathToUrl;
|
||||
});
|
||||
|
||||
eleventyConfig.addFilter("inputPathToUrl", function (targetFilePath) {
|
||||
if (!contentMap) {
|
||||
throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter.");
|
||||
}
|
||||
|
||||
if (isValidUrl(targetFilePath)) {
|
||||
return targetFilePath;
|
||||
}
|
||||
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
let suffix = "";
|
||||
[suffix, targetFilePath] = parseFilePath(targetFilePath);
|
||||
// @ts-ignore
|
||||
targetFilePath = normalizeInputPath(targetFilePath, inputDir, this.page.inputPath, contentMap);
|
||||
|
||||
let urls = contentMap[targetFilePath];
|
||||
if (!urls || urls.length === 0) {
|
||||
throw new Error(
|
||||
"`inputPathToUrl` filter could not find a matching target for " + targetFilePath,
|
||||
);
|
||||
}
|
||||
|
||||
return `${urls[0]}${suffix}`;
|
||||
});
|
||||
}
|
||||
|
||||
function TransformPlugin(eleventyConfig, defaultOptions = {}) {
|
||||
let opts = Object.assign(
|
||||
{
|
||||
extensions: "html",
|
||||
},
|
||||
defaultOptions,
|
||||
);
|
||||
|
||||
let contentMap = null;
|
||||
eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
|
||||
contentMap = inputPathToUrl;
|
||||
});
|
||||
|
||||
eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) {
|
||||
if (!contentMap) {
|
||||
throw new Error("Internal error: contentMap not available for the `pathToUrl` Transform.");
|
||||
}
|
||||
if (isValidUrl(targetFilepathOrUrl)) {
|
||||
return targetFilepathOrUrl;
|
||||
}
|
||||
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
|
||||
let suffix = "";
|
||||
[suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);
|
||||
targetFilepathOrUrl = normalizeInputPath(
|
||||
targetFilepathOrUrl,
|
||||
inputDir,
|
||||
// @ts-ignore
|
||||
this.page.inputPath,
|
||||
contentMap,
|
||||
);
|
||||
|
||||
let urls = contentMap[targetFilepathOrUrl];
|
||||
if (!targetFilepathOrUrl || !urls || urls.length === 0) {
|
||||
// fallback, transforms don’t error on missing paths (though the pathToUrl filter does)
|
||||
return `${targetFilepathOrUrl}${suffix}`;
|
||||
}
|
||||
|
||||
return `${urls[0]}${suffix}`;
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(FilterPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/inputpath-to-url-filter-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(FilterPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(TransformPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/inputpath-to-url-transform-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(TransformPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default TransformPlugin;
|
||||
|
||||
export { FilterPlugin, TransformPlugin };
|
||||
380
node_modules/@11ty/eleventy/src/Plugins/Pagination.js
generated
vendored
Executable file
380
node_modules/@11ty/eleventy/src/Plugins/Pagination.js
generated
vendored
Executable file
@@ -0,0 +1,380 @@
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { DeepCopy } from "@11ty/eleventy-utils";
|
||||
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
|
||||
// import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
|
||||
import TemplateData from "../Data/TemplateData.js";
|
||||
|
||||
const { set: lodashSet, get: lodashGet, chunk: lodashChunk } = lodash;
|
||||
|
||||
class PaginationConfigError extends EleventyBaseError {}
|
||||
class PaginationError extends EleventyBaseError {}
|
||||
|
||||
class Pagination {
|
||||
constructor(tmpl, data, config) {
|
||||
if (!config) {
|
||||
throw new PaginationConfigError("Expected `config` argument to Pagination class.");
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
|
||||
this.setTemplate(tmpl);
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
get inputPathForErrorMessages() {
|
||||
if (this.template) {
|
||||
return ` (${this.template.inputPath})`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static hasPagination(data) {
|
||||
return "pagination" in data;
|
||||
}
|
||||
|
||||
hasPagination() {
|
||||
if (!this.data) {
|
||||
throw new Error(
|
||||
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
return Pagination.hasPagination(this.data);
|
||||
}
|
||||
|
||||
circularReferenceCheck(data) {
|
||||
let key = data.pagination.data;
|
||||
let includedTags = TemplateData.getIncludedTagNames(data);
|
||||
|
||||
for (let tag of includedTags) {
|
||||
if (`collections.${tag}` === key) {
|
||||
throw new PaginationError(
|
||||
`Pagination circular reference${this.inputPathForErrorMessages}, data:\`${key}\` iterates over both the \`${tag}\` collection and also supplies pages to that collection.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.data = data || {};
|
||||
this.target = [];
|
||||
|
||||
if (!this.hasPagination()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.pagination) {
|
||||
throw new Error(
|
||||
`Misconfigured pagination data in template front matter${this.inputPathForErrorMessages} (YAML front matter precaution: did you use tabs and not spaces for indentation?).`,
|
||||
);
|
||||
} else if (!("size" in data.pagination)) {
|
||||
throw new Error(
|
||||
`Missing pagination size in front matter data${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
this.circularReferenceCheck(data);
|
||||
|
||||
this.size = data.pagination.size;
|
||||
this.alias = data.pagination.alias;
|
||||
this.fullDataSet = this._get(this.data, this._getDataKey());
|
||||
// this returns an array
|
||||
this.target = this._resolveItems();
|
||||
this.chunkedItems = this.pagedItems;
|
||||
}
|
||||
|
||||
setTemplate(tmpl) {
|
||||
this.template = tmpl;
|
||||
}
|
||||
|
||||
_getDataKey() {
|
||||
return this.data.pagination.data;
|
||||
}
|
||||
|
||||
shouldResolveDataToObjectValues() {
|
||||
if ("resolve" in this.data.pagination) {
|
||||
return this.data.pagination.resolve === "values";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isFiltered(value) {
|
||||
if ("filter" in this.data.pagination) {
|
||||
let filtered = this.data.pagination.filter;
|
||||
if (Array.isArray(filtered)) {
|
||||
return filtered.indexOf(value) > -1;
|
||||
}
|
||||
|
||||
return filtered === value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_has(target, key) {
|
||||
let notFoundValue = "__NOT_FOUND_ERROR__";
|
||||
let data = lodashGet(target, key, notFoundValue);
|
||||
return data !== notFoundValue;
|
||||
}
|
||||
|
||||
_get(target, key) {
|
||||
let notFoundValue = "__NOT_FOUND_ERROR__";
|
||||
let data = lodashGet(target, key, notFoundValue);
|
||||
if (data === notFoundValue) {
|
||||
throw new Error(
|
||||
`Could not find pagination data${this.inputPathForErrorMessages}, went looking for: ${key}`,
|
||||
);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
_resolveItems() {
|
||||
let keys;
|
||||
if (Array.isArray(this.fullDataSet)) {
|
||||
keys = this.fullDataSet;
|
||||
this.paginationTargetType = "array";
|
||||
} else if (isPlainObject(this.fullDataSet)) {
|
||||
this.paginationTargetType = "object";
|
||||
if (this.shouldResolveDataToObjectValues()) {
|
||||
keys = Object.values(this.fullDataSet);
|
||||
} else {
|
||||
keys = Object.keys(this.fullDataSet);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected data found in pagination target${this.inputPathForErrorMessages}: expected an Array or an Object.`,
|
||||
);
|
||||
}
|
||||
|
||||
// keys must be an array
|
||||
let result = keys.slice();
|
||||
|
||||
if (this.data.pagination.before && typeof this.data.pagination.before === "function") {
|
||||
// we don’t need to make a copy of this because we .slice() above to create a new copy
|
||||
let fns = {};
|
||||
if (this.config) {
|
||||
fns = this.config.javascriptFunctions;
|
||||
}
|
||||
result = this.data.pagination.before.call(fns, result, this.data);
|
||||
}
|
||||
|
||||
if (this.data.pagination.reverse === true) {
|
||||
result = result.reverse();
|
||||
}
|
||||
|
||||
if (this.data.pagination.filter) {
|
||||
result = result.filter((value) => !this.isFiltered(value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
get pagedItems() {
|
||||
if (!this.data) {
|
||||
throw new Error(
|
||||
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
|
||||
const chunks = lodashChunk(this.target, this.size);
|
||||
if (this.data.pagination?.generatePageOnEmptyData) {
|
||||
return chunks.length ? chunks : [[]];
|
||||
} else {
|
||||
return chunks;
|
||||
}
|
||||
}
|
||||
|
||||
getPageCount() {
|
||||
if (!this.hasPagination()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.chunkedItems.length;
|
||||
}
|
||||
|
||||
getNormalizedItems(pageItems) {
|
||||
return this.size === 1 ? pageItems[0] : pageItems;
|
||||
}
|
||||
|
||||
getOverrideDataPages(items, pageNumber) {
|
||||
return {
|
||||
// See Issue #345 for more examples
|
||||
page: {
|
||||
previous: pageNumber > 0 ? this.getNormalizedItems(items[pageNumber - 1]) : null,
|
||||
next: pageNumber < items.length - 1 ? this.getNormalizedItems(items[pageNumber + 1]) : null,
|
||||
first: items.length ? this.getNormalizedItems(items[0]) : null,
|
||||
last: items.length ? this.getNormalizedItems(items[items.length - 1]) : null,
|
||||
},
|
||||
|
||||
pageNumber,
|
||||
};
|
||||
}
|
||||
|
||||
getOverrideDataLinks(pageNumber, templateCount, links) {
|
||||
let obj = {};
|
||||
|
||||
// links are okay but hrefs are better
|
||||
obj.previousPageLink = pageNumber > 0 ? links[pageNumber - 1] : null;
|
||||
obj.previous = obj.previousPageLink;
|
||||
|
||||
obj.nextPageLink = pageNumber < templateCount - 1 ? links[pageNumber + 1] : null;
|
||||
obj.next = obj.nextPageLink;
|
||||
|
||||
obj.firstPageLink = links.length > 0 ? links[0] : null;
|
||||
obj.lastPageLink = links.length > 0 ? links[links.length - 1] : null;
|
||||
|
||||
obj.links = links;
|
||||
// todo deprecated, consistency with collections and use links instead
|
||||
obj.pageLinks = links;
|
||||
return obj;
|
||||
}
|
||||
|
||||
getOverrideDataHrefs(pageNumber, templateCount, hrefs) {
|
||||
let obj = {};
|
||||
|
||||
// hrefs are better than links
|
||||
obj.previousPageHref = pageNumber > 0 ? hrefs[pageNumber - 1] : null;
|
||||
obj.nextPageHref = pageNumber < templateCount - 1 ? hrefs[pageNumber + 1] : null;
|
||||
|
||||
obj.firstPageHref = hrefs.length > 0 ? hrefs[0] : null;
|
||||
obj.lastPageHref = hrefs.length > 0 ? hrefs[hrefs.length - 1] : null;
|
||||
|
||||
obj.hrefs = hrefs;
|
||||
|
||||
// better names
|
||||
obj.href = {
|
||||
previous: obj.previousPageHref,
|
||||
next: obj.nextPageHref,
|
||||
first: obj.firstPageHref,
|
||||
last: obj.lastPageHref,
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
async getPageTemplates() {
|
||||
if (!this.data) {
|
||||
throw new Error(
|
||||
`Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.hasPagination()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
let items = this.chunkedItems;
|
||||
let pages = this.size === 1 ? items.map((entry) => entry[0]) : items;
|
||||
|
||||
let links = [];
|
||||
let hrefs = [];
|
||||
|
||||
let hasPermalinkField = Boolean(this.data[this.config.keys.permalink]);
|
||||
let hasComputedPermalinkField = Boolean(
|
||||
this.data.eleventyComputed && this.data.eleventyComputed[this.config.keys.permalink],
|
||||
);
|
||||
|
||||
// Do *not* pass collections through DeepCopy, we’ll re-add them back in later.
|
||||
let collections = this.data.collections;
|
||||
if (collections) {
|
||||
delete this.data.collections;
|
||||
}
|
||||
|
||||
let parentData = DeepCopy(
|
||||
{
|
||||
pagination: {
|
||||
data: this.data.pagination.data,
|
||||
size: this.data.pagination.size,
|
||||
alias: this.alias,
|
||||
pages,
|
||||
},
|
||||
},
|
||||
this.data,
|
||||
);
|
||||
|
||||
// Restore skipped collections
|
||||
if (collections) {
|
||||
this.data.collections = collections;
|
||||
// Keep the original reference to the collections, no deep copy!!
|
||||
parentData.collections = collections;
|
||||
}
|
||||
|
||||
// TODO this does work fine but let’s wait on enabling it.
|
||||
// DeepFreeze(parentData, ["collections"]);
|
||||
|
||||
// TODO future improvement dea: use a light Template wrapper for paged template clones (PagedTemplate?)
|
||||
// so that we don’t have the memory cost of the full template (and can reuse the parent
|
||||
// template for some things)
|
||||
|
||||
let indices = new Set();
|
||||
for (let j = 0; j <= items.length - 1; j++) {
|
||||
indices.add(j);
|
||||
}
|
||||
|
||||
for (let pageNumber of indices) {
|
||||
let cloned = await this.template.clone();
|
||||
|
||||
if (pageNumber > 0 && !hasPermalinkField && !hasComputedPermalinkField) {
|
||||
cloned.setExtraOutputSubdirectory(pageNumber);
|
||||
}
|
||||
|
||||
let paginationData = {
|
||||
pagination: {
|
||||
items: items[pageNumber],
|
||||
},
|
||||
page: {},
|
||||
};
|
||||
Object.assign(paginationData.pagination, this.getOverrideDataPages(items, pageNumber));
|
||||
|
||||
if (this.alias) {
|
||||
lodashSet(paginationData, this.alias, this.getNormalizedItems(items[pageNumber]));
|
||||
}
|
||||
|
||||
// Do *not* deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454
|
||||
let clonedData = ProxyWrap(paginationData, parentData);
|
||||
|
||||
// Previous method:
|
||||
// let clonedData = DeepCopy(paginationData, parentData);
|
||||
|
||||
let { /*linkInstance,*/ rawPath, path, href } = await cloned.getOutputLocations(clonedData);
|
||||
// TODO subdirectory to links if the site doesn’t live at /
|
||||
if (rawPath) {
|
||||
links.push("/" + rawPath);
|
||||
}
|
||||
|
||||
hrefs.push(href);
|
||||
|
||||
// page.url and page.outputPath are used to avoid another getOutputLocations call later, see Template->addComputedData
|
||||
clonedData.page.url = href;
|
||||
clonedData.page.outputPath = path;
|
||||
|
||||
entries.push({
|
||||
pageNumber,
|
||||
|
||||
// This is used by i18n Plugin to allow subgroups of nested pagination to be separate
|
||||
groupNumber: items[pageNumber]?.[0]?.eleventyPaginationGroupNumber,
|
||||
|
||||
template: cloned,
|
||||
data: clonedData,
|
||||
});
|
||||
}
|
||||
|
||||
// we loop twice to pass in the appropriate prev/next links (already full generated now)
|
||||
let index = 0;
|
||||
for (let pageEntry of entries) {
|
||||
let linksObj = this.getOverrideDataLinks(index, items.length, links);
|
||||
|
||||
Object.assign(pageEntry.data.pagination, linksObj);
|
||||
|
||||
let hrefsObj = this.getOverrideDataHrefs(index, items.length, hrefs);
|
||||
Object.assign(pageEntry.data.pagination, hrefsObj);
|
||||
index++;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
481
node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
generated
vendored
Normal file
481
node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
import fs from "graceful-fs";
|
||||
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import { evalToken } from "liquidjs";
|
||||
|
||||
// TODO add a first-class Markdown component to expose this using Markdown-only syntax (will need to be synchronous for markdown-it)
|
||||
|
||||
import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
|
||||
import TemplateDataInitialGlobalData from "../Data/TemplateDataInitialGlobalData.js";
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import TemplateRender from "../TemplateRender.js";
|
||||
import ProjectDirectories from "../Util/ProjectDirectories.js";
|
||||
import TemplateConfig from "../TemplateConfig.js";
|
||||
import Liquid from "../Engines/Liquid.js";
|
||||
|
||||
class EleventyNunjucksError extends EleventyBaseError {}
|
||||
|
||||
/** @this {object} */
|
||||
async function compile(content, templateLang, options = {}) {
|
||||
let { templateConfig, extensionMap } = options;
|
||||
|
||||
if (!templateConfig) {
|
||||
templateConfig = new TemplateConfig(null, false);
|
||||
templateConfig.setDirectories(new ProjectDirectories());
|
||||
await templateConfig.init();
|
||||
}
|
||||
|
||||
// Breaking change in 2.0+, previous default was `html` and now we default to the page template syntax
|
||||
if (!templateLang) {
|
||||
templateLang = this.page.templateSyntax;
|
||||
}
|
||||
|
||||
let tr = new TemplateRender(templateLang, templateConfig);
|
||||
tr.extensionMap = extensionMap;
|
||||
if (templateLang) {
|
||||
await tr.setEngineOverride(templateLang);
|
||||
} else {
|
||||
await tr.init();
|
||||
}
|
||||
|
||||
// TODO tie this to the class, not the extension
|
||||
if (
|
||||
tr.engine.name === "11ty.js" ||
|
||||
tr.engine.name === "11ty.cjs" ||
|
||||
tr.engine.name === "11ty.mjs"
|
||||
) {
|
||||
throw new Error(
|
||||
"11ty.js is not yet supported as a template engine for `renderTemplate`. Use `renderFile` instead!",
|
||||
);
|
||||
}
|
||||
|
||||
return tr.getCompiledTemplate(content);
|
||||
}
|
||||
|
||||
// No templateLang default, it should infer from the inputPath.
|
||||
async function compileFile(inputPath, options = {}, templateLang) {
|
||||
let { templateConfig, extensionMap, config } = options;
|
||||
if (!inputPath) {
|
||||
throw new Error("Missing file path argument passed to the `renderFile` shortcode.");
|
||||
}
|
||||
|
||||
if (!fs.existsSync(TemplatePath.normalizeOperatingSystemFilePath(inputPath))) {
|
||||
throw new Error(
|
||||
"Could not find render plugin file for the `renderFile` shortcode, looking for: " + inputPath,
|
||||
);
|
||||
}
|
||||
|
||||
let wasTemplateConfigMissing = false;
|
||||
if (!templateConfig) {
|
||||
templateConfig = new TemplateConfig(null, false);
|
||||
templateConfig.setDirectories(new ProjectDirectories());
|
||||
wasTemplateConfigMissing = true;
|
||||
}
|
||||
if (config && typeof config === "function") {
|
||||
await config(templateConfig.userConfig);
|
||||
}
|
||||
if (wasTemplateConfigMissing) {
|
||||
await templateConfig.init();
|
||||
}
|
||||
|
||||
let tr = new TemplateRender(inputPath, templateConfig);
|
||||
tr.extensionMap = extensionMap;
|
||||
|
||||
if (templateLang) {
|
||||
await tr.setEngineOverride(templateLang);
|
||||
} else {
|
||||
await tr.init();
|
||||
}
|
||||
|
||||
if (!tr.engine.needsToReadFileContents()) {
|
||||
return tr.getCompiledTemplate(null);
|
||||
}
|
||||
|
||||
// TODO we could make this work with full templates (with front matter?)
|
||||
let content = fs.readFileSync(inputPath, "utf8");
|
||||
return tr.getCompiledTemplate(content);
|
||||
}
|
||||
|
||||
/** @this {object} */
|
||||
async function renderShortcodeFn(fn, data) {
|
||||
if (fn === undefined) {
|
||||
return;
|
||||
} else if (typeof fn !== "function") {
|
||||
throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
|
||||
}
|
||||
|
||||
// if the user passes a string or other literal, remap to an object.
|
||||
if (!isPlainObject(data)) {
|
||||
data = {
|
||||
_: data,
|
||||
};
|
||||
}
|
||||
|
||||
if ("data" in this && isPlainObject(this.data)) {
|
||||
// when options.accessGlobalData is true, this allows the global data
|
||||
// to be accessed inside of the shortcode as a fallback
|
||||
|
||||
data = ProxyWrap(data, this.data);
|
||||
} else {
|
||||
// save `page` and `eleventy` for reuse
|
||||
data.page = this.page;
|
||||
data.eleventy = this.eleventy;
|
||||
}
|
||||
|
||||
return fn(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/Plugins/RenderPlugin
|
||||
*/
|
||||
|
||||
/**
|
||||
* A plugin to add shortcodes to render an Eleventy template
|
||||
* string (or file) inside of another template. {@link https://v3.11ty.dev/docs/plugins/render/}
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param {module:11ty/eleventy/UserConfig} eleventyConfig - User-land configuration instance.
|
||||
* @param {object} options - Plugin options
|
||||
*/
|
||||
function eleventyRenderPlugin(eleventyConfig, options = {}) {
|
||||
/**
|
||||
* @typedef {object} options
|
||||
* @property {string} [tagName] - The shortcode name to render a template string.
|
||||
* @property {string} [tagNameFile] - The shortcode name to render a template file.
|
||||
* @property {module:11ty/eleventy/TemplateConfig} [templateConfig] - Configuration object
|
||||
* @property {boolean} [accessGlobalData] - Whether or not the template has access to the page’s data.
|
||||
*/
|
||||
let defaultOptions = {
|
||||
tagName: "renderTemplate",
|
||||
tagNameFile: "renderFile",
|
||||
filterName: "renderContent",
|
||||
templateConfig: null,
|
||||
accessGlobalData: false,
|
||||
};
|
||||
let opts = Object.assign(defaultOptions, options);
|
||||
|
||||
function liquidTemplateTag(liquidEngine, tagName) {
|
||||
// via https://github.com/harttle/liquidjs/blob/b5a22fa0910c708fe7881ef170ed44d3594e18f3/src/builtin/tags/raw.ts
|
||||
return {
|
||||
parse: function (tagToken, remainTokens) {
|
||||
this.name = tagToken.name;
|
||||
|
||||
if (eleventyConfig.liquid.parameterParsing === "builtin") {
|
||||
this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
|
||||
// note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
|
||||
} else {
|
||||
this.legacyArgs = tagToken.args;
|
||||
}
|
||||
|
||||
this.tokens = [];
|
||||
|
||||
var stream = liquidEngine.parser
|
||||
.parseStream(remainTokens)
|
||||
.on("token", (token) => {
|
||||
if (token.name === "end" + tagName) stream.stop();
|
||||
else this.tokens.push(token);
|
||||
})
|
||||
.on("end", () => {
|
||||
throw new Error(`tag ${tagToken.getText()} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
render: function* (ctx) {
|
||||
let normalizedContext = {};
|
||||
if (ctx) {
|
||||
if (opts.accessGlobalData) {
|
||||
// parent template data cascade
|
||||
normalizedContext.data = ctx.getAll();
|
||||
}
|
||||
|
||||
normalizedContext.page = ctx.get(["page"]);
|
||||
normalizedContext.eleventy = ctx.get(["eleventy"]);
|
||||
}
|
||||
|
||||
let argArray = [];
|
||||
if (this.legacyArgs) {
|
||||
let rawArgs = Liquid.parseArguments(null, this.legacyArgs);
|
||||
for (let arg of rawArgs) {
|
||||
let b = yield liquidEngine.evalValue(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
} else if (this.orderedArgs) {
|
||||
for (let arg of this.orderedArgs) {
|
||||
let b = yield evalToken(arg, ctx);
|
||||
argArray.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
// plaintext paired shortcode content
|
||||
let body = this.tokens.map((token) => token.getText()).join("");
|
||||
|
||||
let ret = _renderStringShortcodeFn.call(
|
||||
normalizedContext,
|
||||
body,
|
||||
// templateLang, data
|
||||
...argArray,
|
||||
);
|
||||
yield ret;
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO I don’t think this works with whitespace control, e.g. {%- endrenderTemplate %}
|
||||
function nunjucksTemplateTag(NunjucksLib, tagName) {
|
||||
return new (function () {
|
||||
this.tags = [tagName];
|
||||
|
||||
this.parse = function (parser, nodes) {
|
||||
var tok = parser.nextToken();
|
||||
|
||||
var args = parser.parseSignature(true, true);
|
||||
const begun = parser.advanceAfterBlockEnd(tok.value);
|
||||
|
||||
// This code was ripped from the Nunjucks parser for `raw`
|
||||
// https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/parser.js#L655
|
||||
const endTagName = "end" + tagName;
|
||||
// Look for upcoming raw blocks (ignore all other kinds of blocks)
|
||||
const rawBlockRegex = new RegExp(
|
||||
"([\\s\\S]*?){%\\s*(" + tagName + "|" + endTagName + ")\\s*(?=%})%}",
|
||||
);
|
||||
let rawLevel = 1;
|
||||
let str = "";
|
||||
let matches = null;
|
||||
|
||||
// Exit when there's nothing to match
|
||||
// or when we've found the matching "endraw" block
|
||||
while ((matches = parser.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) {
|
||||
const all = matches[0];
|
||||
const pre = matches[1];
|
||||
const blockName = matches[2];
|
||||
|
||||
// Adjust rawlevel
|
||||
if (blockName === tagName) {
|
||||
rawLevel += 1;
|
||||
} else if (blockName === endTagName) {
|
||||
rawLevel -= 1;
|
||||
}
|
||||
|
||||
// Add to str
|
||||
if (rawLevel === 0) {
|
||||
// We want to exclude the last "endraw"
|
||||
str += pre;
|
||||
// Move tokenizer to beginning of endraw block
|
||||
parser.tokens.backN(all.length - pre.length);
|
||||
} else {
|
||||
str += all;
|
||||
}
|
||||
}
|
||||
|
||||
let body = new nodes.Output(begun.lineno, begun.colno, [
|
||||
new nodes.TemplateData(begun.lineno, begun.colno, str),
|
||||
]);
|
||||
return new nodes.CallExtensionAsync(this, "run", args, [body]);
|
||||
};
|
||||
|
||||
this.run = function (...args) {
|
||||
let resolve = args.pop();
|
||||
let body = args.pop();
|
||||
let [context, ...argArray] = args;
|
||||
|
||||
let normalizedContext = {};
|
||||
if (context.ctx?.page) {
|
||||
normalizedContext.ctx = context.ctx;
|
||||
|
||||
// TODO .data
|
||||
// if(opts.accessGlobalData) {
|
||||
// normalizedContext.data = context.ctx;
|
||||
// }
|
||||
|
||||
normalizedContext.page = context.ctx.page;
|
||||
normalizedContext.eleventy = context.ctx.eleventy;
|
||||
}
|
||||
|
||||
body(function (e, bodyContent) {
|
||||
if (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
|
||||
);
|
||||
}
|
||||
|
||||
Promise.resolve(
|
||||
_renderStringShortcodeFn.call(
|
||||
normalizedContext,
|
||||
bodyContent,
|
||||
// templateLang, data
|
||||
...argArray,
|
||||
),
|
||||
).then(
|
||||
function (returnValue) {
|
||||
resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
|
||||
},
|
||||
function (e) {
|
||||
resolve(
|
||||
new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
|
||||
null,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// This will only work on 1.0.0-beta.5+ but is only necessary if you want to reuse your config inside of template shortcodes.
|
||||
// Just rendering raw templates (without filters, shortcodes, etc. from your config) will work fine on old versions.
|
||||
let templateConfig;
|
||||
eleventyConfig.on("eleventy.config", (cfg) => {
|
||||
templateConfig = cfg;
|
||||
});
|
||||
|
||||
let extensionMap;
|
||||
eleventyConfig.on("eleventy.extensionmap", (map) => {
|
||||
extensionMap = map;
|
||||
});
|
||||
|
||||
/** @this {object} */
|
||||
async function _renderStringShortcodeFn(content, templateLang, data = {}) {
|
||||
// Default is fn(content, templateLang, data) but we want to support fn(content, data) too
|
||||
if (typeof templateLang !== "string") {
|
||||
data = templateLang;
|
||||
templateLang = false;
|
||||
}
|
||||
|
||||
let fn = await compile.call(this, content, templateLang, {
|
||||
templateConfig: opts.templateConfig || templateConfig,
|
||||
extensionMap,
|
||||
});
|
||||
|
||||
return renderShortcodeFn.call(this, fn, data);
|
||||
}
|
||||
|
||||
/** @this {object} */
|
||||
async function _renderFileShortcodeFn(inputPath, data = {}, templateLang) {
|
||||
let options = {
|
||||
templateConfig: opts.templateConfig || templateConfig,
|
||||
extensionMap,
|
||||
};
|
||||
|
||||
let fn = await compileFile.call(this, inputPath, options, templateLang);
|
||||
|
||||
return renderShortcodeFn.call(this, fn, data);
|
||||
}
|
||||
|
||||
// Render strings
|
||||
if (opts.tagName) {
|
||||
// use falsy to opt-out
|
||||
eleventyConfig.addJavaScriptFunction(opts.tagName, _renderStringShortcodeFn);
|
||||
|
||||
eleventyConfig.addLiquidTag(opts.tagName, function (liquidEngine) {
|
||||
return liquidTemplateTag(liquidEngine, opts.tagName);
|
||||
});
|
||||
|
||||
eleventyConfig.addNunjucksTag(opts.tagName, function (nunjucksLib) {
|
||||
return nunjucksTemplateTag(nunjucksLib, opts.tagName);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter for rendering strings
|
||||
if (opts.filterName) {
|
||||
eleventyConfig.addAsyncFilter(opts.filterName, _renderStringShortcodeFn);
|
||||
}
|
||||
|
||||
// Render File
|
||||
// use `false` to opt-out
|
||||
if (opts.tagNameFile) {
|
||||
eleventyConfig.addAsyncShortcode(opts.tagNameFile, _renderFileShortcodeFn);
|
||||
}
|
||||
}
|
||||
|
||||
// Will re-use the same configuration instance both at a top level and across any nested renders
|
||||
class RenderManager {
|
||||
/** @type {Promise|undefined} */
|
||||
#hasConfigInitialized;
|
||||
|
||||
constructor() {
|
||||
this.templateConfig = new TemplateConfig(null, false);
|
||||
this.templateConfig.setDirectories(new ProjectDirectories());
|
||||
|
||||
// This is the only plugin running on the Edge
|
||||
this.templateConfig.userConfig.addPlugin(eleventyRenderPlugin, {
|
||||
templateConfig: this.templateConfig,
|
||||
accessGlobalData: true,
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.#hasConfigInitialized) {
|
||||
return this.#hasConfigInitialized;
|
||||
}
|
||||
if (this.templateConfig.hasInitialized()) {
|
||||
return true;
|
||||
}
|
||||
this.#hasConfigInitialized = this.templateConfig.init();
|
||||
await this.#hasConfigInitialized;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// `callback` is async-friendly but requires await upstream
|
||||
config(callback) {
|
||||
// run an extra `function(eleventyConfig)` configuration callbacks
|
||||
if (callback && typeof callback === "function") {
|
||||
return callback(this.templateConfig.userConfig);
|
||||
}
|
||||
}
|
||||
|
||||
get initialGlobalData() {
|
||||
if (!this._data) {
|
||||
this._data = new TemplateDataInitialGlobalData(this.templateConfig);
|
||||
}
|
||||
return this._data;
|
||||
}
|
||||
|
||||
// because we don’t have access to the full data cascade—but
|
||||
// we still want configuration data added via `addGlobalData`
|
||||
async getData(...data) {
|
||||
await this.init();
|
||||
|
||||
let globalData = await this.initialGlobalData.getData();
|
||||
let merged = Merge({}, globalData, ...data);
|
||||
return merged;
|
||||
}
|
||||
|
||||
async compile(content, templateLang, options = {}) {
|
||||
await this.init();
|
||||
|
||||
// Missing here: extensionMap
|
||||
options.templateConfig = this.templateConfig;
|
||||
|
||||
// We don’t need `compile.call(this)` here because the Edge always uses "liquid" as the template lang (instead of relying on this.page.templateSyntax)
|
||||
// returns promise
|
||||
return compile(content, templateLang, options);
|
||||
}
|
||||
|
||||
async render(fn, edgeData, buildTimeData) {
|
||||
await this.init();
|
||||
|
||||
let mergedData = await this.getData(edgeData);
|
||||
// Set .data for options.accessGlobalData feature
|
||||
let context = {
|
||||
data: mergedData,
|
||||
};
|
||||
|
||||
return renderShortcodeFn.call(context, fn, buildTimeData);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(eleventyRenderPlugin, "eleventyPackage", {
|
||||
value: "@11ty/eleventy/render-plugin",
|
||||
});
|
||||
|
||||
Object.defineProperty(eleventyRenderPlugin, "eleventyPluginOptions", {
|
||||
value: {
|
||||
unique: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default eleventyRenderPlugin;
|
||||
|
||||
export { compileFile as File, compile as String, RenderManager };
|
||||
1124
node_modules/@11ty/eleventy/src/Template.js
generated
vendored
Executable file
1124
node_modules/@11ty/eleventy/src/Template.js
generated
vendored
Executable file
File diff suppressed because it is too large
Load Diff
85
node_modules/@11ty/eleventy/src/TemplateBehavior.js
generated
vendored
Normal file
85
node_modules/@11ty/eleventy/src/TemplateBehavior.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplateBehavior {
|
||||
#isRenderOptional;
|
||||
|
||||
constructor(config) {
|
||||
this.render = true;
|
||||
this.write = true;
|
||||
this.outputFormat = null;
|
||||
|
||||
if (!config) {
|
||||
throw new Error("Missing config argument in TemplateBehavior");
|
||||
}
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// Render override set to false
|
||||
isRenderableDisabled() {
|
||||
return this.renderableOverride === false;
|
||||
}
|
||||
|
||||
isRenderableOptional() {
|
||||
return this.#isRenderOptional;
|
||||
}
|
||||
|
||||
// undefined (fallback), true, false
|
||||
setRenderableOverride(renderableOverride) {
|
||||
if (renderableOverride === "optional") {
|
||||
this.#isRenderOptional = true;
|
||||
this.renderableOverride = undefined;
|
||||
} else {
|
||||
this.#isRenderOptional = false;
|
||||
this.renderableOverride = renderableOverride;
|
||||
}
|
||||
}
|
||||
|
||||
// permalink *has* a build key or output is json/ndjson
|
||||
isRenderable() {
|
||||
return this.renderableOverride ?? (this.render || this.isRenderForced());
|
||||
}
|
||||
|
||||
setOutputFormat(format) {
|
||||
this.outputFormat = format;
|
||||
}
|
||||
|
||||
isRenderForced() {
|
||||
return this.outputFormat === "json" || this.outputFormat === "ndjson";
|
||||
}
|
||||
|
||||
isWriteable() {
|
||||
return this.write;
|
||||
}
|
||||
|
||||
// Duplicate logic with TemplatePermalink constructor
|
||||
setRenderViaDataCascade(data) {
|
||||
// render is false *only* if `build` key does not exist in permalink objects (both in data and eleventyComputed)
|
||||
// (note that permalink: false means it won’t write but will still render)
|
||||
|
||||
let keys = new Set();
|
||||
if (isPlainObject(data.permalink)) {
|
||||
for (let key of Object.keys(data.permalink)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
let computedKey = this.config.keys.computed;
|
||||
if (computedKey in data && isPlainObject(data[computedKey]?.permalink)) {
|
||||
for (let key of Object.keys(data[computedKey].permalink)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (keys.size) {
|
||||
this.render = keys.has("build");
|
||||
}
|
||||
}
|
||||
|
||||
setFromPermalink(templatePermalink) {
|
||||
// this.render is duplicated between TemplatePermalink and `setRenderViaDataCascade` above
|
||||
this.render = templatePermalink._isRendered;
|
||||
|
||||
this.write = templatePermalink._writeToFileSystem;
|
||||
}
|
||||
}
|
||||
export default TemplateBehavior;
|
||||
104
node_modules/@11ty/eleventy/src/TemplateCache.js
generated
vendored
Normal file
104
node_modules/@11ty/eleventy/src/TemplateCache.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import eventBus from "./EventBus.js";
|
||||
|
||||
// Note: this is only used for TemplateLayout right now but could be used for more
|
||||
// Just be careful because right now the TemplateLayout cache keys are not directly mapped to paths
|
||||
// So you may get collisions if you use this for other things.
|
||||
class TemplateCache {
|
||||
constructor() {
|
||||
this.cache = {};
|
||||
this.cacheByInputPath = {};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache = {};
|
||||
this.cacheByInputPath = {};
|
||||
}
|
||||
|
||||
// alias
|
||||
removeAll() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
size() {
|
||||
return Object.keys(this.cacheByInputPath).length;
|
||||
}
|
||||
|
||||
add(layoutTemplate) {
|
||||
let keys = new Set();
|
||||
|
||||
if (typeof layoutTemplate === "string") {
|
||||
throw new Error(
|
||||
"Invalid argument type passed to TemplateCache->add(). Should be a TemplateLayout.",
|
||||
);
|
||||
}
|
||||
|
||||
if ("getFullKey" in layoutTemplate) {
|
||||
keys.add(layoutTemplate.getFullKey());
|
||||
}
|
||||
|
||||
if ("getKey" in layoutTemplate) {
|
||||
// if `key` was an alias, also set to the pathed layout value too
|
||||
// e.g. `layout: "default"` and `layout: "default.liquid"` will both map to the same template.
|
||||
keys.add(layoutTemplate.getKey());
|
||||
}
|
||||
|
||||
for (let key of keys) {
|
||||
this.cache[key] = layoutTemplate;
|
||||
}
|
||||
|
||||
// also the full template input path for use with eleventy --serve/--watch e.g. `_includes/default.liquid` (see `remove` below)
|
||||
let fullPath = TemplatePath.stripLeadingDotSlash(layoutTemplate.inputPath);
|
||||
this.cacheByInputPath[fullPath] = layoutTemplate;
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return key in this.cache;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (!this.has(key)) {
|
||||
throw new Error(`Could not find ${key} in TemplateCache.`);
|
||||
}
|
||||
|
||||
return this.cache[key];
|
||||
}
|
||||
|
||||
remove(layoutFilePath) {
|
||||
layoutFilePath = TemplatePath.stripLeadingDotSlash(layoutFilePath);
|
||||
if (!this.cacheByInputPath[layoutFilePath]) {
|
||||
// not a layout file
|
||||
return;
|
||||
}
|
||||
|
||||
let layoutTemplate = this.cacheByInputPath[layoutFilePath];
|
||||
layoutTemplate.resetCaches();
|
||||
|
||||
let keys = layoutTemplate.getCacheKeys();
|
||||
for (let key of keys) {
|
||||
delete this.cache[key];
|
||||
}
|
||||
|
||||
delete this.cacheByInputPath[layoutFilePath];
|
||||
}
|
||||
}
|
||||
|
||||
let layoutCache = new TemplateCache();
|
||||
|
||||
eventBus.on("eleventy.resourceModified", (path, usedBy, metadata = {}) => {
|
||||
// https://github.com/11ty/eleventy-plugin-bundle/issues/10
|
||||
if (metadata.viaConfigReset) {
|
||||
layoutCache.removeAll();
|
||||
} else if (metadata.relevantLayouts?.length) {
|
||||
// reset the appropriate layouts relevant to the file.
|
||||
for (let layoutPath of metadata.relevantLayouts || []) {
|
||||
layoutCache.remove(layoutPath);
|
||||
}
|
||||
} else {
|
||||
layoutCache.remove(path);
|
||||
}
|
||||
});
|
||||
|
||||
// singleton
|
||||
export default layoutCache;
|
||||
81
node_modules/@11ty/eleventy/src/TemplateCollection.js
generated
vendored
Executable file
81
node_modules/@11ty/eleventy/src/TemplateCollection.js
generated
vendored
Executable file
@@ -0,0 +1,81 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import Sortable from "./Util/Objects/Sortable.js";
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
class TemplateCollection extends Sortable {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._filteredByGlobsCache = new Map();
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.items.slice();
|
||||
}
|
||||
|
||||
getAllSorted() {
|
||||
return this.sort(Sortable.sortFunctionDateInputPath);
|
||||
}
|
||||
|
||||
getSortedByDate() {
|
||||
return this.sort(Sortable.sortFunctionDate);
|
||||
}
|
||||
|
||||
getGlobs(globs) {
|
||||
if (typeof globs === "string") {
|
||||
globs = [globs];
|
||||
}
|
||||
|
||||
globs = globs.map((glob) => TemplatePath.addLeadingDotSlash(glob));
|
||||
|
||||
return globs;
|
||||
}
|
||||
|
||||
getFilteredByGlob(globs) {
|
||||
globs = this.getGlobs(globs);
|
||||
|
||||
let key = globs.join("::");
|
||||
if (!this._dirty) {
|
||||
// Try to find a pre-sorted list and clone it.
|
||||
if (this._filteredByGlobsCache.has(key)) {
|
||||
return [...this._filteredByGlobsCache.get(key)];
|
||||
}
|
||||
} else if (this._filteredByGlobsCache.size) {
|
||||
// Blow away cache
|
||||
this._filteredByGlobsCache = new Map();
|
||||
}
|
||||
|
||||
let filtered = this.getAllSorted().filter((item) => {
|
||||
return isGlobMatch(item.inputPath, globs);
|
||||
});
|
||||
this._dirty = false;
|
||||
this._filteredByGlobsCache.set(key, [...filtered]);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
getFilteredByTag(tagName) {
|
||||
return this.getAllSorted().filter((item) => {
|
||||
if (!tagName || TemplateData.getIncludedTagNames(item.data).includes(tagName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getFilteredByTags(...tags) {
|
||||
return this.getAllSorted().filter((item) => {
|
||||
let itemTags = TemplateData.getIncludedTagNames(item.data);
|
||||
return tags.every((requiredTag) => {
|
||||
if (Array.isArray(itemTags)) {
|
||||
return itemTags.includes(requiredTag);
|
||||
} else {
|
||||
return itemTags === requiredTag;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateCollection;
|
||||
551
node_modules/@11ty/eleventy/src/TemplateConfig.js
generated
vendored
Normal file
551
node_modules/@11ty/eleventy/src/TemplateConfig.js
generated
vendored
Normal file
@@ -0,0 +1,551 @@
|
||||
import fs from "node:fs";
|
||||
import chalk from "kleur";
|
||||
import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { EleventyImportRaw, EleventyImportRawFromEleventy } from "./Util/Require.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import UserConfig from "./UserConfig.js";
|
||||
import GlobalDependencyMap from "./GlobalDependencyMap.js";
|
||||
import ExistsCache from "./Util/ExistsCache.js";
|
||||
import eventBus from "./EventBus.js";
|
||||
import ProjectTemplateFormats from "./Util/ProjectTemplateFormats.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateConfig");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateConfig");
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/TemplateConfig
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config as used by the template.
|
||||
* @typedef {object} module:11ty/eleventy/TemplateConfig~TemplateConfig~config
|
||||
* @property {String} [pathPrefix] - The path prefix.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Errors in eleventy config.
|
||||
* @ignore
|
||||
*/
|
||||
class EleventyConfigError extends EleventyBaseError {}
|
||||
|
||||
/**
|
||||
* Errors in eleventy plugins.
|
||||
* @ignore
|
||||
*/
|
||||
class EleventyPluginError extends EleventyBaseError {}
|
||||
|
||||
/**
|
||||
* Config for a template.
|
||||
* @ignore
|
||||
* @param {{}} customRootConfig - tbd.
|
||||
* @param {String} projectConfigPath - Path to local project config.
|
||||
*/
|
||||
class TemplateConfig {
|
||||
#templateFormats;
|
||||
#runMode;
|
||||
#configManuallyDefined = false;
|
||||
/** @type {UserConfig} */
|
||||
#userConfig = new UserConfig();
|
||||
|
||||
constructor(customRootConfig, projectConfigPath) {
|
||||
/** @type {object} */
|
||||
this.overrides = {};
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
* @description Path to local project config.
|
||||
* @default .eleventy.js
|
||||
*/
|
||||
if (projectConfigPath !== undefined) {
|
||||
this.#configManuallyDefined = true;
|
||||
|
||||
if (!projectConfigPath) {
|
||||
// falsy skips config files
|
||||
this.projectConfigPaths = [];
|
||||
} else {
|
||||
this.projectConfigPaths = [projectConfigPath];
|
||||
}
|
||||
} else {
|
||||
this.projectConfigPaths = [
|
||||
".eleventy.js",
|
||||
"eleventy.config.js",
|
||||
"eleventy.config.mjs",
|
||||
"eleventy.config.cjs",
|
||||
];
|
||||
}
|
||||
|
||||
if (customRootConfig) {
|
||||
/**
|
||||
* @type {object}
|
||||
* @description Custom root config.
|
||||
*/
|
||||
this.customRootConfig = customRootConfig;
|
||||
debug("Warning: Using custom root config!");
|
||||
} else {
|
||||
this.customRootConfig = null;
|
||||
}
|
||||
|
||||
this.hasConfigMerged = false;
|
||||
this.isEsm = false;
|
||||
}
|
||||
|
||||
get userConfig() {
|
||||
return this.#userConfig;
|
||||
}
|
||||
|
||||
get aggregateBenchmark() {
|
||||
return this.userConfig.benchmarks.aggregate;
|
||||
}
|
||||
|
||||
/* Setter for Logger */
|
||||
setLogger(logger) {
|
||||
this.logger = logger;
|
||||
this.userConfig.logger = this.logger;
|
||||
}
|
||||
|
||||
/* Setter for Directories instance */
|
||||
setDirectories(directories) {
|
||||
this.directories = directories;
|
||||
this.userConfig.directories = directories.getUserspaceInstance();
|
||||
}
|
||||
|
||||
/* Setter for TemplateFormats instance */
|
||||
setTemplateFormats(templateFormats) {
|
||||
this.#templateFormats = templateFormats;
|
||||
}
|
||||
|
||||
get templateFormats() {
|
||||
if (!this.#templateFormats) {
|
||||
this.#templateFormats = new ProjectTemplateFormats();
|
||||
}
|
||||
return this.#templateFormats;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
get inputDir() {
|
||||
return this.directories.input;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.#runMode = runMode;
|
||||
}
|
||||
|
||||
shouldSpiderJavaScriptDependencies() {
|
||||
// not for a standard build
|
||||
return (
|
||||
(this.#runMode === "watch" || this.#runMode === "serve") &&
|
||||
this.userConfig.watchJavaScriptDependencies
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalises local project config file path.
|
||||
*
|
||||
* @method
|
||||
* @returns {String|undefined} - The normalised local project config file path.
|
||||
*/
|
||||
getLocalProjectConfigFile() {
|
||||
let configFiles = this.getLocalProjectConfigFiles();
|
||||
// Add the configFiles[0] in case of a test, where no file exists on the file system
|
||||
let configFile = configFiles.find((path) => path && fs.existsSync(path)) || configFiles[0];
|
||||
if (configFile) {
|
||||
return configFile;
|
||||
}
|
||||
}
|
||||
|
||||
getLocalProjectConfigFiles() {
|
||||
if (this.projectConfigPaths?.length > 0) {
|
||||
return TemplatePath.addLeadingDotSlashArray(this.projectConfigPaths.filter((path) => path));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
setProjectUsingEsm(isEsmProject) {
|
||||
this.isEsm = !!isEsmProject;
|
||||
this.usesGraph.setIsEsm(isEsmProject);
|
||||
}
|
||||
|
||||
getIsProjectUsingEsm() {
|
||||
return this.isEsm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the configuration.
|
||||
*/
|
||||
async reset() {
|
||||
debugDev("Resetting configuration: TemplateConfig and UserConfig.");
|
||||
this.userConfig.reset();
|
||||
// await this.initializeRootConfig();
|
||||
await this.forceReloadConfig();
|
||||
this.usesGraph.reset();
|
||||
|
||||
// Clear the compile cache
|
||||
eventBus.emit("eleventy.compileCacheReset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the configuration while in watch mode.
|
||||
*
|
||||
* @todo Add implementation.
|
||||
*/
|
||||
resetOnWatch() {
|
||||
// nothing yet
|
||||
}
|
||||
|
||||
hasInitialized() {
|
||||
return this.hasConfigMerged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async-friendly init method
|
||||
*/
|
||||
async init(overrides) {
|
||||
await this.initializeRootConfig();
|
||||
|
||||
if (overrides) {
|
||||
this.appendToRootConfig(overrides);
|
||||
}
|
||||
|
||||
this.config = await this.mergeConfig();
|
||||
this.hasConfigMerged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a reload of the configuration object.
|
||||
*/
|
||||
async forceReloadConfig() {
|
||||
this.hasConfigMerged = false;
|
||||
await this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config object.
|
||||
*
|
||||
* @returns {{}} - The config object.
|
||||
*/
|
||||
getConfig() {
|
||||
if (!this.hasConfigMerged) {
|
||||
throw new Error("Invalid call to .getConfig(). Needs an .init() first.");
|
||||
}
|
||||
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the config path.
|
||||
*
|
||||
* @param {String} path - The new config path.
|
||||
*/
|
||||
async setProjectConfigPath(path) {
|
||||
this.#configManuallyDefined = true;
|
||||
|
||||
if (path !== undefined) {
|
||||
this.projectConfigPaths = [path];
|
||||
} else {
|
||||
this.projectConfigPaths = [];
|
||||
}
|
||||
|
||||
if (this.hasConfigMerged) {
|
||||
// merge it again
|
||||
debugDev("Merging in getConfig again after setting the local project config path.");
|
||||
await this.forceReloadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the path prefix.
|
||||
*
|
||||
* @param {String} pathPrefix - The new path prefix.
|
||||
*/
|
||||
setPathPrefix(pathPrefix) {
|
||||
if (pathPrefix && pathPrefix !== "/") {
|
||||
debug("Setting pathPrefix to %o", pathPrefix);
|
||||
this.overrides.pathPrefix = pathPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current path prefix denoting the root folder the output will be deployed to
|
||||
*
|
||||
* @returns {String} - The path prefix string
|
||||
*/
|
||||
getPathPrefix() {
|
||||
if (this.overrides.pathPrefix) {
|
||||
return this.overrides.pathPrefix;
|
||||
}
|
||||
|
||||
if (!this.hasConfigMerged) {
|
||||
throw new Error("Config has not yet merged. Needs `init()`.");
|
||||
}
|
||||
|
||||
return this.config?.pathPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps the config object.
|
||||
*/
|
||||
async initializeRootConfig() {
|
||||
this.rootConfig = this.customRootConfig;
|
||||
if (!this.rootConfig) {
|
||||
let { default: cfg } = await EleventyImportRawFromEleventy("./src/defaultConfig.js");
|
||||
this.rootConfig = cfg;
|
||||
}
|
||||
|
||||
if (typeof this.rootConfig === "function") {
|
||||
// Not yet using async in defaultConfig.js
|
||||
this.rootConfig = this.rootConfig.call(this, this.userConfig);
|
||||
}
|
||||
|
||||
debug("Default Eleventy config %o", this.rootConfig);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add additional overrides to the root config object, used for testing
|
||||
*
|
||||
* @param {object} - a subset of the return Object from the user’s config file.
|
||||
*/
|
||||
appendToRootConfig(obj) {
|
||||
Object.assign(this.rootConfig, obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the userland plugins from the Config
|
||||
*
|
||||
* @param {object} - the return Object from the user’s config file.
|
||||
*/
|
||||
async processPlugins({ dir, pathPrefix }) {
|
||||
this.userConfig.dir = dir;
|
||||
this.userConfig.pathPrefix = pathPrefix;
|
||||
|
||||
// for Nested addPlugin calls, Issue #1925
|
||||
this.userConfig._enablePluginExecution();
|
||||
|
||||
let storedActiveNamespace = this.userConfig.activeNamespace;
|
||||
for (let { plugin, options, pluginNamespace } of this.userConfig.plugins) {
|
||||
try {
|
||||
this.userConfig.activeNamespace = pluginNamespace;
|
||||
await this.userConfig._executePlugin(plugin, options);
|
||||
} catch (e) {
|
||||
let name = this.userConfig._getPluginName(plugin);
|
||||
let namespaces = [storedActiveNamespace, pluginNamespace].filter((entry) => !!entry);
|
||||
|
||||
let namespaceStr = "";
|
||||
if (namespaces.length) {
|
||||
namespaceStr = ` (namespace: ${namespaces.join(".")})`;
|
||||
}
|
||||
|
||||
throw new EleventyPluginError(
|
||||
`Error processing ${name ? `the \`${name}\`` : "a"} plugin${namespaceStr}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.userConfig.activeNamespace = storedActiveNamespace;
|
||||
|
||||
this.userConfig._disablePluginExecution();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and executes the local configuration file
|
||||
*
|
||||
* @returns {Promise<object>} merged - The merged config file object.
|
||||
*/
|
||||
async requireLocalConfigFile() {
|
||||
let localConfig = {};
|
||||
let exportedConfig = {};
|
||||
|
||||
let path = this.projectConfigPaths.filter((path) => path).find((path) => fs.existsSync(path));
|
||||
|
||||
if (this.projectConfigPaths.length > 0 && this.#configManuallyDefined && !path) {
|
||||
throw new EleventyConfigError(
|
||||
"A configuration file was specified but not found: " + this.projectConfigPaths.join(", "),
|
||||
);
|
||||
}
|
||||
|
||||
debug(`Merging default config with ${path}`);
|
||||
if (path) {
|
||||
try {
|
||||
let { default: configDefaultReturn, config: exportedConfigObject } =
|
||||
await EleventyImportRaw(path, this.isEsm ? "esm" : "cjs");
|
||||
|
||||
exportedConfig = exportedConfigObject || {};
|
||||
|
||||
if (this.directories && Object.keys(exportedConfigObject?.dir || {}).length > 0) {
|
||||
debug(
|
||||
"Setting directories via `config.dir` export from config file: %o",
|
||||
exportedConfigObject.dir,
|
||||
);
|
||||
this.directories.setViaConfigObject(exportedConfigObject.dir);
|
||||
}
|
||||
|
||||
if (typeof configDefaultReturn === "function") {
|
||||
localConfig = await configDefaultReturn(this.userConfig);
|
||||
} else {
|
||||
localConfig = configDefaultReturn;
|
||||
}
|
||||
|
||||
// Removed a check for `filters` in 3.0.0-alpha.6 (now using addTransform instead) https://v3.11ty.dev/docs/config/#transforms
|
||||
} catch (err) {
|
||||
let isModuleError =
|
||||
err instanceof Error && (err?.message || "").includes("Cannot find module");
|
||||
|
||||
// TODO the error message here is bad and I feel bad (needs more accurate info)
|
||||
return Promise.reject(
|
||||
new EleventyConfigError(
|
||||
`Error in your Eleventy config file '${path}'.` +
|
||||
(isModuleError ? chalk.cyan(" You may need to run `npm install`.") : ""),
|
||||
err,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug(
|
||||
"Project config file not found (not an error—skipping). Looked in: %o",
|
||||
this.projectConfigPaths,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
localConfig,
|
||||
exportedConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges different config files together.
|
||||
*
|
||||
* @returns {Promise<object>} merged - The merged config file.
|
||||
*/
|
||||
async mergeConfig() {
|
||||
let { localConfig, exportedConfig } = await this.requireLocalConfigFile();
|
||||
|
||||
// Merge `export const config = {}` with `return {}` in config callback
|
||||
if (isPlainObject(exportedConfig)) {
|
||||
localConfig = Merge(localConfig || {}, exportedConfig);
|
||||
}
|
||||
|
||||
if (this.directories) {
|
||||
if (Object.keys(this.userConfig.directoryAssignments || {}).length > 0) {
|
||||
debug(
|
||||
"Setting directories via set*Directory configuration APIs %o",
|
||||
this.userConfig.directoryAssignments,
|
||||
);
|
||||
this.directories.setViaConfigObject(this.userConfig.directoryAssignments);
|
||||
}
|
||||
|
||||
if (localConfig && Object.keys(localConfig?.dir || {}).length > 0) {
|
||||
debug(
|
||||
"Setting directories via `dir` object return from configuration file: %o",
|
||||
localConfig.dir,
|
||||
);
|
||||
this.directories.setViaConfigObject(localConfig.dir);
|
||||
}
|
||||
}
|
||||
|
||||
// `templateFormats` is an override via `setTemplateFormats`
|
||||
if (this.userConfig?.templateFormats) {
|
||||
this.templateFormats.setViaConfig(this.userConfig.templateFormats);
|
||||
} else if (localConfig?.templateFormats || this.rootConfig?.templateFormats) {
|
||||
// Local project config or defaultConfig.js
|
||||
this.templateFormats.setViaConfig(
|
||||
localConfig.templateFormats || this.rootConfig?.templateFormats,
|
||||
);
|
||||
}
|
||||
|
||||
// `templateFormatsAdded` is additive via `addTemplateFormats`
|
||||
if (this.userConfig?.templateFormatsAdded) {
|
||||
this.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);
|
||||
}
|
||||
|
||||
let mergedConfig = Merge({}, this.rootConfig, localConfig);
|
||||
|
||||
// Setup a few properties for plugins:
|
||||
|
||||
// Set frozen templateFormats
|
||||
mergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());
|
||||
|
||||
// Setup pathPrefix set via command line for plugin consumption
|
||||
if (this.overrides.pathPrefix) {
|
||||
mergedConfig.pathPrefix = this.overrides.pathPrefix;
|
||||
}
|
||||
|
||||
// Returning a falsy value (e.g. "") from user config should reset to the default value.
|
||||
if (!mergedConfig.pathPrefix) {
|
||||
mergedConfig.pathPrefix = this.rootConfig.pathPrefix;
|
||||
}
|
||||
|
||||
// This is not set in UserConfig.js so that getters aren’t converted to strings
|
||||
// We want to error if someone attempts to use a setter there.
|
||||
if (this.directories) {
|
||||
mergedConfig.directories = this.directories.getUserspaceInstance();
|
||||
}
|
||||
|
||||
// Delay processing plugins until after the result of localConfig is returned
|
||||
// But BEFORE the rest of the config options are merged
|
||||
// this way we can pass directories and other template information to plugins
|
||||
|
||||
await this.userConfig.events.emit("eleventy.beforeConfig", this.userConfig);
|
||||
|
||||
let pluginsBench = this.aggregateBenchmark.get("Processing plugins in config");
|
||||
pluginsBench.before();
|
||||
await this.processPlugins(mergedConfig);
|
||||
pluginsBench.after();
|
||||
|
||||
// Template formats added via plugins
|
||||
if (this.userConfig?.templateFormatsAdded) {
|
||||
this.templateFormats.addViaConfig(this.userConfig.templateFormatsAdded);
|
||||
mergedConfig.templateFormats = Object.freeze(this.templateFormats.getTemplateFormats());
|
||||
}
|
||||
|
||||
let eleventyConfigApiMergingObject = this.userConfig.getMergingConfigObject();
|
||||
|
||||
if ("templateFormats" in eleventyConfigApiMergingObject) {
|
||||
throw new Error(
|
||||
"Internal error: templateFormats should not return from `getMergingConfigObject`",
|
||||
);
|
||||
}
|
||||
|
||||
// Overrides are only used by pathPrefix
|
||||
debug("Configuration overrides: %o", this.overrides);
|
||||
Merge(mergedConfig, eleventyConfigApiMergingObject, this.overrides);
|
||||
|
||||
debug("Current configuration: %o", mergedConfig);
|
||||
|
||||
// Add to the merged config too
|
||||
mergedConfig.uses = this.usesGraph;
|
||||
|
||||
// this is used for the layouts event
|
||||
this.usesGraph.setConfig(mergedConfig);
|
||||
|
||||
return mergedConfig;
|
||||
}
|
||||
|
||||
get usesGraph() {
|
||||
if (!this._usesGraph) {
|
||||
this._usesGraph = new GlobalDependencyMap();
|
||||
this._usesGraph.setIsEsm(this.isEsm);
|
||||
this._usesGraph.setTemplateConfig(this);
|
||||
}
|
||||
return this._usesGraph;
|
||||
}
|
||||
|
||||
get uses() {
|
||||
if (!this.usesGraph) {
|
||||
throw new Error("The Eleventy Global Dependency Graph has not yet been initialized.");
|
||||
}
|
||||
return this.usesGraph;
|
||||
}
|
||||
|
||||
get existsCache() {
|
||||
if (!this._existsCache) {
|
||||
this._existsCache = new ExistsCache();
|
||||
this._existsCache.setDirectoryCheck(true);
|
||||
}
|
||||
return this._existsCache;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateConfig;
|
||||
712
node_modules/@11ty/eleventy/src/TemplateContent.js
generated
vendored
Normal file
712
node_modules/@11ty/eleventy/src/TemplateContent.js
generated
vendored
Normal file
@@ -0,0 +1,712 @@
|
||||
import os from "node:os";
|
||||
|
||||
import fs from "graceful-fs";
|
||||
import matter from "gray-matter";
|
||||
import lodash from "@11ty/lodash-custom";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
import chardet from "chardet";
|
||||
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import TemplateRender from "./TemplateRender.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
|
||||
import eventBus from "./EventBus.js";
|
||||
|
||||
const { set: lodashSet } = lodash;
|
||||
const debug = debugUtil("Eleventy:TemplateContent");
|
||||
const debugDiagnostic = debugUtil("Eleventy:Diagnostics");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateContent");
|
||||
|
||||
class TemplateContentConfigError extends EleventyBaseError {}
|
||||
class TemplateContentFrontMatterError extends EleventyBaseError {}
|
||||
class TemplateContentCompileError extends EleventyBaseError {}
|
||||
class TemplateContentRenderError extends EleventyBaseError {}
|
||||
|
||||
class TemplateContent {
|
||||
constructor(inputPath, templateConfig) {
|
||||
if (!templateConfig || templateConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplateContentConfigError(
|
||||
"Missing or invalid `templateConfig` argument to TemplateContent",
|
||||
);
|
||||
}
|
||||
this.eleventyConfig = templateConfig;
|
||||
this.inputPath = inputPath;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
getResetTypes(types) {
|
||||
if (types) {
|
||||
return Object.assign(
|
||||
{
|
||||
data: false,
|
||||
read: false,
|
||||
render: false,
|
||||
},
|
||||
types,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
data: true,
|
||||
read: true,
|
||||
render: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Called during an incremental build when the template instance is cached but needs to be reset because it has changed
|
||||
resetCaches(types) {
|
||||
types = this.getResetTypes(types);
|
||||
|
||||
if (types.read) {
|
||||
delete this.readingPromise;
|
||||
delete this.inputContent;
|
||||
delete this._frontMatterDataCache;
|
||||
}
|
||||
}
|
||||
|
||||
/* Used by tests */
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
set extensionMap(map) {
|
||||
this._extensionMap = map;
|
||||
}
|
||||
|
||||
set eleventyConfig(config) {
|
||||
this._config = config;
|
||||
|
||||
if (this._config.constructor.name === "TemplateConfig") {
|
||||
this._configOptions = this._config.getConfig();
|
||||
} else {
|
||||
throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
|
||||
}
|
||||
}
|
||||
|
||||
get eleventyConfig() {
|
||||
if (this._config.constructor.name === "TemplateConfig") {
|
||||
return this._config;
|
||||
}
|
||||
throw new TemplateContentConfigError("Tried to get an TemplateConfig but none was found.");
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (this._config.constructor.name === "TemplateConfig" && !this._configOptions) {
|
||||
this._configOptions = this._config.getConfig();
|
||||
}
|
||||
|
||||
return this._configOptions;
|
||||
}
|
||||
|
||||
get bench() {
|
||||
return this.config.benchmarkManager.get("Aggregate");
|
||||
}
|
||||
|
||||
get engine() {
|
||||
return this.templateRender.engine;
|
||||
}
|
||||
|
||||
get templateRender() {
|
||||
if (!this.hasTemplateRender()) {
|
||||
throw new Error(`\`templateRender\` has not yet initialized on ${this.inputPath}`);
|
||||
}
|
||||
|
||||
return this._templateRender;
|
||||
}
|
||||
|
||||
hasTemplateRender() {
|
||||
return !!this._templateRender;
|
||||
}
|
||||
|
||||
async getTemplateRender() {
|
||||
if (!this._templateRender) {
|
||||
this._templateRender = new TemplateRender(this.inputPath, this.eleventyConfig);
|
||||
this._templateRender.extensionMap = this.extensionMap;
|
||||
await this._templateRender.init();
|
||||
}
|
||||
|
||||
return this._templateRender;
|
||||
}
|
||||
|
||||
// For monkey patchers
|
||||
get frontMatter() {
|
||||
if (this.frontMatterOverride) {
|
||||
return this.frontMatterOverride;
|
||||
} else {
|
||||
throw new Error(
|
||||
"Unfortunately you’re using code that monkey patched some Eleventy internals and it isn’t async-friendly. Change your code to use the async `read()` method on the template instead!",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For monkey patchers
|
||||
set frontMatter(contentOverride) {
|
||||
this.frontMatterOverride = contentOverride;
|
||||
}
|
||||
|
||||
getInputPath() {
|
||||
return this.inputPath;
|
||||
}
|
||||
|
||||
getInputDir() {
|
||||
return this.inputDir;
|
||||
}
|
||||
|
||||
isVirtualTemplate() {
|
||||
let def = this.getVirtualTemplateDefinition();
|
||||
return !!def;
|
||||
}
|
||||
|
||||
getVirtualTemplateDefinition() {
|
||||
let inputDirRelativeInputPath =
|
||||
this.eleventyConfig.directories.getInputPathRelativeToInputDirectory(this.inputPath);
|
||||
return this.config.virtualTemplates[inputDirRelativeInputPath];
|
||||
}
|
||||
|
||||
async #read() {
|
||||
let content = await this.inputContent;
|
||||
|
||||
if (content || content === "") {
|
||||
if (this.engine.useJavaScriptImport()) {
|
||||
return {
|
||||
data: {},
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
let options = this.config.frontMatterParsingOptions || {};
|
||||
let fm;
|
||||
try {
|
||||
// Added in 3.0, passed along to front matter engines
|
||||
options.filePath = this.inputPath;
|
||||
fm = matter(content, options);
|
||||
} catch (e) {
|
||||
throw new TemplateContentFrontMatterError(
|
||||
`Having trouble reading front matter from template ${this.inputPath}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.excerpt && fm.excerpt) {
|
||||
let excerptString = fm.excerpt + (options.excerpt_separator || "---");
|
||||
if (fm.content.startsWith(excerptString + os.EOL)) {
|
||||
// with an os-specific newline after excerpt separator
|
||||
fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + os.EOL).length);
|
||||
} else if (fm.content.startsWith(excerptString + "\n")) {
|
||||
// with a newline (\n) after excerpt separator
|
||||
// This is necessary for some git configurations on windows
|
||||
fm.content = fm.excerpt.trim() + "\n" + fm.content.slice((excerptString + 1).length);
|
||||
} else if (fm.content.startsWith(excerptString)) {
|
||||
// no newline after excerpt separator
|
||||
fm.content = fm.excerpt + fm.content.slice(excerptString.length);
|
||||
}
|
||||
|
||||
// alias, defaults to page.excerpt
|
||||
let alias = options.excerpt_alias || "page.excerpt";
|
||||
lodashSet(fm.data, alias, fm.excerpt);
|
||||
}
|
||||
|
||||
// For monkey patchers that used `frontMatter` 🤧
|
||||
// https://github.com/11ty/eleventy/issues/613#issuecomment-999637109
|
||||
// https://github.com/11ty/eleventy/issues/2710#issuecomment-1373854834
|
||||
// Removed this._frontMatter monkey patcher help in 3.0.0-alpha.7
|
||||
|
||||
return fm;
|
||||
} else {
|
||||
return {
|
||||
data: {},
|
||||
content: "",
|
||||
excerpt: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async read() {
|
||||
if (!this.readingPromise) {
|
||||
if (!this.inputContent) {
|
||||
// @cachedproperty
|
||||
this.inputContent = this.getInputContent();
|
||||
}
|
||||
|
||||
// @cachedproperty
|
||||
this.readingPromise = this.#read();
|
||||
}
|
||||
|
||||
return this.readingPromise;
|
||||
}
|
||||
|
||||
/* Incremental builds cache the Template instances (in TemplateWriter) but
|
||||
* these template specific caches are important for Pagination */
|
||||
static cache(path, content) {
|
||||
this._inputCache.set(TemplatePath.absolutePath(path), content);
|
||||
}
|
||||
|
||||
static getCached(path) {
|
||||
return this._inputCache.get(TemplatePath.absolutePath(path));
|
||||
}
|
||||
|
||||
static deleteFromInputCache(path) {
|
||||
this._inputCache.delete(TemplatePath.absolutePath(path));
|
||||
}
|
||||
|
||||
// Used via clone
|
||||
setInputContent(content) {
|
||||
this.inputContent = content;
|
||||
}
|
||||
|
||||
async getInputContent() {
|
||||
let tr = await this.getTemplateRender();
|
||||
|
||||
let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
|
||||
if (virtualTemplateDefinition) {
|
||||
let { content } = virtualTemplateDefinition;
|
||||
return content;
|
||||
}
|
||||
|
||||
if (
|
||||
tr.engine.useJavaScriptImport() &&
|
||||
typeof tr.engine.getInstanceFromInputPath === "function"
|
||||
) {
|
||||
return tr.engine.getInstanceFromInputPath(this.inputPath);
|
||||
}
|
||||
|
||||
if (!tr.engine.needsToReadFileContents()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let templateBenchmark = this.bench.get("Template Read");
|
||||
templateBenchmark.before();
|
||||
|
||||
let content;
|
||||
|
||||
if (this.config.useTemplateCache) {
|
||||
content = TemplateContent.getCached(this.inputPath);
|
||||
}
|
||||
|
||||
if (!content && content !== "") {
|
||||
let contentBuffer = fs.readFileSync(this.inputPath);
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
// Warning: this is slow!!
|
||||
let encoding = chardet.detect(contentBuffer);
|
||||
|
||||
if (encoding.startsWith("UTF-16")) {
|
||||
debug("Warning: %o encoding detected on %o.", encoding, this.inputPath);
|
||||
debugDiagnostic(
|
||||
"%o encoding detected on %o. Please re-save as UTF-8.",
|
||||
encoding,
|
||||
this.inputPath,
|
||||
);
|
||||
} else {
|
||||
debug("%o encoding detected on %o.", encoding, this.inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
content = contentBuffer.toString("utf8");
|
||||
|
||||
if (this.config.useTemplateCache) {
|
||||
TemplateContent.cache(this.inputPath, content);
|
||||
}
|
||||
}
|
||||
|
||||
templateBenchmark.after();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async _testGetFrontMatter() {
|
||||
let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();
|
||||
|
||||
return fm;
|
||||
}
|
||||
|
||||
async getPreRender() {
|
||||
let fm = this.frontMatterOverride ? this.frontMatterOverride : await this.read();
|
||||
|
||||
return fm.content;
|
||||
}
|
||||
|
||||
async #getFrontMatterData() {
|
||||
let fm = await this.read();
|
||||
|
||||
// gray-matter isn’t async-friendly but can return a promise from custom front matter
|
||||
if (fm.data instanceof Promise) {
|
||||
fm.data = await fm.data;
|
||||
}
|
||||
|
||||
let extraData = await this.engine.getExtraDataFromFile(this.inputPath);
|
||||
|
||||
let virtualTemplateDefinition = this.getVirtualTemplateDefinition();
|
||||
let virtualTemplateData;
|
||||
if (virtualTemplateDefinition) {
|
||||
virtualTemplateData = virtualTemplateDefinition.data;
|
||||
}
|
||||
|
||||
let data = TemplateData.mergeDeep(false, fm.data, extraData, virtualTemplateData);
|
||||
let cleanedData = TemplateData.cleanupData(data);
|
||||
|
||||
return {
|
||||
data: cleanedData,
|
||||
excerpt: fm.excerpt,
|
||||
};
|
||||
}
|
||||
|
||||
async getFrontMatterData() {
|
||||
if (!this._frontMatterDataCache) {
|
||||
// @cachedproperty
|
||||
this._frontMatterDataCache = this.#getFrontMatterData();
|
||||
}
|
||||
|
||||
return this._frontMatterDataCache;
|
||||
}
|
||||
|
||||
async getEngineOverride() {
|
||||
let { data: frontMatterData } = await this.getFrontMatterData();
|
||||
return frontMatterData[this.config.keys.engineOverride];
|
||||
}
|
||||
|
||||
_getCompileCache(str) {
|
||||
// Caches used to be bifurcated based on engine name, now they’re based on inputPath
|
||||
let inputPathMap = TemplateContent._compileCache.get(this.inputPath);
|
||||
if (!inputPathMap) {
|
||||
inputPathMap = new Map();
|
||||
TemplateContent._compileCache.set(this.inputPath, inputPathMap);
|
||||
}
|
||||
|
||||
let cacheable = this.engine.cacheable;
|
||||
let { useCache, key } = this.engine.getCompileCacheKey(str, this.inputPath);
|
||||
|
||||
// We also tie the compile cache key to the UserConfig instance, to alleviate issues with global template cache
|
||||
// Better to move the cache to the Eleventy instance instead, no?
|
||||
// (This specifically failed I18nPluginTest cases with filters being cached across tests and not having access to each plugin’s options)
|
||||
key = this.eleventyConfig.userConfig._getUniqueId() + key;
|
||||
|
||||
return [cacheable, key, inputPathMap, useCache];
|
||||
}
|
||||
|
||||
async compile(str, options = {}) {
|
||||
let { type, bypassMarkdown, engineOverride } = options;
|
||||
|
||||
let tr = await this.getTemplateRender();
|
||||
|
||||
if (engineOverride !== undefined) {
|
||||
debugDev("%o overriding template engine to use %o", this.inputPath, engineOverride);
|
||||
await tr.setEngineOverride(engineOverride, bypassMarkdown);
|
||||
} else {
|
||||
tr.setUseMarkdown(!bypassMarkdown);
|
||||
}
|
||||
|
||||
if (bypassMarkdown && !this.engine.needsCompilation(str)) {
|
||||
return function () {
|
||||
return str;
|
||||
};
|
||||
}
|
||||
|
||||
debugDev("%o compile() using engine: %o", this.inputPath, tr.engineName);
|
||||
|
||||
try {
|
||||
let res;
|
||||
if (this.config.useTemplateCache) {
|
||||
let [cacheable, key, cache, useCache] = this._getCompileCache(str);
|
||||
if (cacheable && key) {
|
||||
if (useCache && cache.has(key)) {
|
||||
this.bench.get("(count) Template Compile Cache Hit").incrementCount();
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
this.bench.get("(count) Template Compile Cache Miss").incrementCount();
|
||||
|
||||
// Compile cache is cleared when the resource is modified (below)
|
||||
|
||||
// Compilation is async, so we eagerly cache a Promise that eventually
|
||||
// resolves to the compiled function
|
||||
cache.set(
|
||||
key,
|
||||
new Promise((resolve) => {
|
||||
res = resolve;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let typeStr = type ? ` ${type}` : "";
|
||||
let templateBenchmark = this.bench.get(`Template Compile${typeStr}`);
|
||||
let inputPathBenchmark = this.bench.get(`> Compile${typeStr} > ${this.inputPath}`);
|
||||
templateBenchmark.before();
|
||||
inputPathBenchmark.before();
|
||||
let fn = await tr.getCompiledTemplate(str);
|
||||
inputPathBenchmark.after();
|
||||
templateBenchmark.after();
|
||||
debugDev("%o getCompiledTemplate function created", this.inputPath);
|
||||
if (this.config.useTemplateCache && res) {
|
||||
res(fn);
|
||||
}
|
||||
return fn;
|
||||
} catch (e) {
|
||||
let [cacheable, key, cache] = this._getCompileCache(str);
|
||||
if (cacheable && key) {
|
||||
cache.delete(key);
|
||||
}
|
||||
debug(`Having trouble compiling template ${this.inputPath}: %O`, str);
|
||||
throw new TemplateContentCompileError(
|
||||
`Having trouble compiling template ${this.inputPath}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getParseForSymbolsFunction(str) {
|
||||
let engine = this.engine;
|
||||
|
||||
// Don’t use markdown as the engine to parse for symbols
|
||||
// TODO pass in engineOverride here
|
||||
let preprocessorEngine = this.templateRender.getPreprocessorEngine();
|
||||
if (preprocessorEngine && engine.getName() !== preprocessorEngine) {
|
||||
let replacementEngine = this.templateRender.getEngineByName(preprocessorEngine);
|
||||
if (replacementEngine) {
|
||||
engine = replacementEngine;
|
||||
}
|
||||
}
|
||||
|
||||
if ("parseForSymbols" in engine) {
|
||||
return () => {
|
||||
return engine.parseForSymbols(str);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// used by computed data or for permalink functions
|
||||
async _renderFunction(fn, ...args) {
|
||||
let mixins = Object.assign({}, this.config.javascriptFunctions);
|
||||
let result = await fn.call(mixins, ...args);
|
||||
|
||||
// normalize Buffer away if returned from permalink
|
||||
if (Buffer.isBuffer(result)) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async renderComputedData(str, data) {
|
||||
if (typeof str === "function") {
|
||||
return this._renderFunction(str, data);
|
||||
}
|
||||
|
||||
return this._render(str, data, {
|
||||
type: "Computed Data",
|
||||
bypassMarkdown: true,
|
||||
});
|
||||
}
|
||||
|
||||
async renderPermalink(permalink, data) {
|
||||
let permalinkCompilation = this.engine.permalinkNeedsCompilation(permalink);
|
||||
|
||||
// No string compilation:
|
||||
// ({ compileOptions: { permalink: "raw" }})
|
||||
// These mean `permalink: false`, which is no file system writing:
|
||||
// ({ compileOptions: { permalink: false }})
|
||||
// ({ compileOptions: { permalink: () => false }})
|
||||
// ({ compileOptions: { permalink: () => (() = > false) }})
|
||||
if (permalinkCompilation === false) {
|
||||
return permalink;
|
||||
}
|
||||
|
||||
/* Custom `compile` function for permalinks, usage:
|
||||
permalink: function(permalinkString, inputPath) {
|
||||
return async function(data) {
|
||||
return "THIS IS MY RENDERED PERMALINK";
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (permalinkCompilation && typeof permalinkCompilation === "function") {
|
||||
permalink = await this._renderFunction(permalinkCompilation, permalink, this.inputPath);
|
||||
}
|
||||
|
||||
// Raw permalink function (in the app code data cascade)
|
||||
if (typeof permalink === "function") {
|
||||
return this._renderFunction(permalink, data);
|
||||
}
|
||||
|
||||
return this._render(permalink, data, {
|
||||
type: "Permalink",
|
||||
bypassMarkdown: true,
|
||||
});
|
||||
}
|
||||
|
||||
async render(str, data, bypassMarkdown) {
|
||||
return this._render(str, data, {
|
||||
bypassMarkdown,
|
||||
type: "",
|
||||
});
|
||||
}
|
||||
|
||||
_getPaginationLogSuffix(data) {
|
||||
let suffix = [];
|
||||
if ("pagination" in data) {
|
||||
suffix.push(" (");
|
||||
if (data.pagination.pages) {
|
||||
suffix.push(
|
||||
`${data.pagination.pages.length} page${data.pagination.pages.length !== 1 ? "s" : ""}`,
|
||||
);
|
||||
} else {
|
||||
suffix.push("Pagination");
|
||||
}
|
||||
suffix.push(")");
|
||||
}
|
||||
return suffix.join("");
|
||||
}
|
||||
|
||||
async _render(str, data, options = {}) {
|
||||
let { bypassMarkdown, type } = options;
|
||||
|
||||
try {
|
||||
if (bypassMarkdown && !this.engine.needsCompilation(str)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
let fn = await this.compile(str, {
|
||||
bypassMarkdown,
|
||||
engineOverride: data[this.config.keys.engineOverride],
|
||||
type,
|
||||
});
|
||||
|
||||
if (fn === undefined) {
|
||||
return;
|
||||
} else if (typeof fn !== "function") {
|
||||
throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
|
||||
}
|
||||
|
||||
// Benchmark
|
||||
let templateBenchmark = this.bench.get("Render");
|
||||
let inputPathBenchmark = this.bench.get(
|
||||
`> Render${type ? ` ${type}` : ""} > ${this.inputPath}${this._getPaginationLogSuffix(data)}`,
|
||||
);
|
||||
|
||||
templateBenchmark.before();
|
||||
if (inputPathBenchmark) {
|
||||
inputPathBenchmark.before();
|
||||
}
|
||||
|
||||
let rendered = await fn(data);
|
||||
|
||||
if (inputPathBenchmark) {
|
||||
inputPathBenchmark.after();
|
||||
}
|
||||
templateBenchmark.after();
|
||||
debugDev("%o getCompiledTemplate called, rendered content created", this.inputPath);
|
||||
return rendered;
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
return Promise.reject(e);
|
||||
} else {
|
||||
let tr = await this.getTemplateRender();
|
||||
let engine = tr.getReadableEnginesList();
|
||||
debug(`Having trouble rendering ${engine} template ${this.inputPath}: %O`, str);
|
||||
return Promise.reject(
|
||||
new TemplateContentRenderError(
|
||||
`Having trouble rendering ${engine} template ${this.inputPath}`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionEntries() {
|
||||
return this.engine.extensionEntries;
|
||||
}
|
||||
|
||||
isFileRelevantToThisTemplate(incrementalFile, metadata = {}) {
|
||||
// always relevant if incremental file not set (build everything)
|
||||
if (!incrementalFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let hasDependencies = this.engine.hasDependencies(incrementalFile);
|
||||
|
||||
let isRelevant = this.engine.isFileRelevantTo(this.inputPath, incrementalFile);
|
||||
|
||||
debug(
|
||||
"Test dependencies to see if %o is relevant to %o: %o",
|
||||
this.inputPath,
|
||||
incrementalFile,
|
||||
isRelevant,
|
||||
);
|
||||
|
||||
let extensionEntries = this.getExtensionEntries().filter((entry) => !!entry.isIncrementalMatch);
|
||||
if (extensionEntries.length) {
|
||||
for (let entry of extensionEntries) {
|
||||
if (
|
||||
entry.isIncrementalMatch.call(
|
||||
{
|
||||
inputPath: this.inputPath,
|
||||
isFullTemplate: metadata.isFullTemplate,
|
||||
isFileRelevantToInputPath: isRelevant,
|
||||
doesFileHaveDependencies: hasDependencies,
|
||||
},
|
||||
incrementalFile,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Not great way of building all templates if this is a layout, include, JS dependency.
|
||||
// TODO improve this for default template syntaxes
|
||||
|
||||
// This is the fallback way of determining if something is incremental (no isIncrementalMatch available)
|
||||
// This will be true if the inputPath and incrementalFile are the same
|
||||
if (isRelevant) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// only return true here if dependencies are not known
|
||||
if (!hasDependencies && !metadata.isFullTemplate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TemplateContent._inputCache = new Map();
|
||||
TemplateContent._compileCache = new Map();
|
||||
eventBus.on("eleventy.resourceModified", (path) => {
|
||||
// delete from input cache
|
||||
TemplateContent.deleteFromInputCache(path);
|
||||
|
||||
// delete from compile cache
|
||||
let normalized = TemplatePath.addLeadingDotSlash(path);
|
||||
let compileCache = TemplateContent._compileCache.get(normalized);
|
||||
if (compileCache) {
|
||||
compileCache.clear();
|
||||
}
|
||||
});
|
||||
|
||||
// Used when the configuration file reset https://github.com/11ty/eleventy/issues/2147
|
||||
eventBus.on("eleventy.compileCacheReset", (/*path*/) => {
|
||||
TemplateContent._compileCache = new Map();
|
||||
});
|
||||
|
||||
export default TemplateContent;
|
||||
57
node_modules/@11ty/eleventy/src/TemplateFileSlug.js
generated
vendored
Normal file
57
node_modules/@11ty/eleventy/src/TemplateFileSlug.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import path from "node:path";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplateFileSlug {
|
||||
constructor(inputPath, extensionMap, eleventyConfig) {
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
if (inputDir) {
|
||||
inputPath = TemplatePath.stripLeadingSubPath(inputPath, inputDir);
|
||||
}
|
||||
|
||||
this.inputPath = inputPath;
|
||||
this.cleanInputPath = inputPath.replace(/^.\//, "");
|
||||
|
||||
let dirs = this.cleanInputPath.split("/");
|
||||
this.dirs = dirs;
|
||||
this.dirs.pop();
|
||||
|
||||
this.parsed = path.parse(inputPath);
|
||||
this.filenameNoExt = extensionMap.removeTemplateExtension(this.parsed.base);
|
||||
}
|
||||
|
||||
// `page.filePathStem` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable
|
||||
getFullPathWithoutExtension() {
|
||||
return "/" + TemplatePath.join(...this.dirs, this._getRawSlug());
|
||||
}
|
||||
|
||||
_getRawSlug() {
|
||||
let slug = this.filenameNoExt;
|
||||
return this._stripDateFromSlug(slug);
|
||||
}
|
||||
|
||||
/** Removes dates in the format of YYYY-MM-DD from a given slug string candidate. */
|
||||
_stripDateFromSlug(slug) {
|
||||
let reg = slug.match(/\d{4}-\d{2}-\d{2}-(.*)/);
|
||||
if (reg) {
|
||||
return reg[1];
|
||||
}
|
||||
return slug;
|
||||
}
|
||||
|
||||
// `page.fileSlug` see https://v3.11ty.dev/docs/data-eleventy-supplied/#page-variable
|
||||
getSlug() {
|
||||
let rawSlug = this._getRawSlug();
|
||||
|
||||
if (rawSlug === "index") {
|
||||
if (!this.dirs.length) {
|
||||
return "";
|
||||
}
|
||||
let lastDir = this.dirs[this.dirs.length - 1];
|
||||
return this._stripDateFromSlug(lastDir);
|
||||
}
|
||||
|
||||
return rawSlug;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateFileSlug;
|
||||
35
node_modules/@11ty/eleventy/src/TemplateGlob.js
generated
vendored
Normal file
35
node_modules/@11ty/eleventy/src/TemplateGlob.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplateGlob {
|
||||
static normalizePath(...paths) {
|
||||
if (paths[0].charAt(0) === "!") {
|
||||
throw new Error(
|
||||
`TemplateGlob.normalizePath does not accept ! glob paths like: ${paths.join("")}`,
|
||||
);
|
||||
}
|
||||
return TemplatePath.addLeadingDotSlash(TemplatePath.join(...paths));
|
||||
}
|
||||
|
||||
static normalize(path) {
|
||||
path = path.trim();
|
||||
if (path.charAt(0) === "!") {
|
||||
return "!" + TemplateGlob.normalizePath(path.slice(1));
|
||||
} else {
|
||||
return TemplateGlob.normalizePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
static map(files) {
|
||||
if (typeof files === "string") {
|
||||
return TemplateGlob.normalize(files);
|
||||
} else if (Array.isArray(files)) {
|
||||
return files.map(function (path) {
|
||||
return TemplateGlob.normalize(path);
|
||||
});
|
||||
} else {
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateGlob;
|
||||
242
node_modules/@11ty/eleventy/src/TemplateLayout.js
generated
vendored
Normal file
242
node_modules/@11ty/eleventy/src/TemplateLayout.js
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import TemplateLayoutPathResolver from "./TemplateLayoutPathResolver.js";
|
||||
import TemplateContent from "./TemplateContent.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
import templateCache from "./TemplateCache.js";
|
||||
|
||||
// const debug = debugUtil("Eleventy:TemplateLayout");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateLayout");
|
||||
|
||||
class TemplateLayout extends TemplateContent {
|
||||
constructor(key, extensionMap, eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new Error("Expected `eleventyConfig` in TemplateLayout constructor.");
|
||||
}
|
||||
|
||||
let resolver = new TemplateLayoutPathResolver(key, extensionMap, eleventyConfig);
|
||||
let resolvedPath = resolver.getFullPath();
|
||||
|
||||
super(resolvedPath, eleventyConfig);
|
||||
|
||||
if (!extensionMap) {
|
||||
throw new Error("Expected `extensionMap` in TemplateLayout constructor.");
|
||||
}
|
||||
|
||||
this.extensionMap = extensionMap;
|
||||
this.key = resolver.getNormalizedLayoutKey();
|
||||
this.dataKeyLayoutPath = key;
|
||||
this.inputPath = resolvedPath;
|
||||
}
|
||||
|
||||
getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
getFullKey() {
|
||||
return TemplateLayout.resolveFullKey(this.dataKeyLayoutPath, this.inputDir);
|
||||
}
|
||||
|
||||
getCacheKeys() {
|
||||
return new Set([this.dataKeyLayoutPath, this.getFullKey(), this.key]);
|
||||
}
|
||||
|
||||
static resolveFullKey(key, inputDir) {
|
||||
return TemplatePath.join(inputDir, key);
|
||||
}
|
||||
|
||||
static getTemplate(key, eleventyConfig, extensionMap) {
|
||||
let config = eleventyConfig.getConfig();
|
||||
if (!config.useTemplateCache) {
|
||||
return new TemplateLayout(key, extensionMap, eleventyConfig);
|
||||
}
|
||||
|
||||
let inputDir = eleventyConfig.directories.input;
|
||||
let fullKey = TemplateLayout.resolveFullKey(key, inputDir);
|
||||
if (!templateCache.has(fullKey)) {
|
||||
let layout = new TemplateLayout(key, extensionMap, eleventyConfig);
|
||||
|
||||
templateCache.add(layout);
|
||||
debugDev("Added %o to TemplateCache", key);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
return templateCache.get(fullKey);
|
||||
}
|
||||
|
||||
async getTemplateLayoutMapEntry() {
|
||||
let { data: frontMatterData } = await this.getFrontMatterData();
|
||||
return {
|
||||
// Used by `TemplateLayout.getTemplate()`
|
||||
key: this.dataKeyLayoutPath,
|
||||
|
||||
// used by `this.getData()`
|
||||
frontMatterData,
|
||||
};
|
||||
}
|
||||
|
||||
async #getTemplateLayoutMap() {
|
||||
// For both the eleventy.layouts event and cyclical layout chain checking (e.g., a => b => c => a)
|
||||
let layoutChain = new Set();
|
||||
layoutChain.add(this.inputPath);
|
||||
|
||||
let cfgKey = this.config.keys.layout;
|
||||
let map = [];
|
||||
let mapEntry = await this.getTemplateLayoutMapEntry();
|
||||
|
||||
map.push(mapEntry);
|
||||
|
||||
while (mapEntry.frontMatterData && cfgKey in mapEntry.frontMatterData) {
|
||||
// Layout of the current layout
|
||||
let parentLayoutKey = mapEntry.frontMatterData[cfgKey];
|
||||
|
||||
let layout = TemplateLayout.getTemplate(
|
||||
parentLayoutKey,
|
||||
this.eleventyConfig,
|
||||
this.extensionMap,
|
||||
);
|
||||
|
||||
// Abort if a circular layout chain is detected. Otherwise, we'll time out and run out of memory.
|
||||
if (layoutChain.has(layout.inputPath)) {
|
||||
throw new Error(
|
||||
`Your layouts have a circular reference, starting at ${map[0].key}! The layout at ${layout.inputPath} was specified twice in this layout chain.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Keep track of this layout so we can detect duplicates in subsequent iterations
|
||||
layoutChain.add(layout.inputPath);
|
||||
|
||||
// reassign for next loop
|
||||
mapEntry = await layout.getTemplateLayoutMapEntry();
|
||||
|
||||
map.push(mapEntry);
|
||||
}
|
||||
|
||||
this.layoutChain = Array.from(layoutChain);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
async getTemplateLayoutMap() {
|
||||
if (!this.cachedLayoutMap) {
|
||||
this.cachedLayoutMap = this.#getTemplateLayoutMap();
|
||||
}
|
||||
|
||||
return this.cachedLayoutMap;
|
||||
}
|
||||
|
||||
async getLayoutChain() {
|
||||
if (!Array.isArray(this.layoutChain)) {
|
||||
await this.getTemplateLayoutMap();
|
||||
}
|
||||
|
||||
return this.layoutChain;
|
||||
}
|
||||
|
||||
async #getData() {
|
||||
let map = await this.getTemplateLayoutMap();
|
||||
let dataToMerge = [];
|
||||
for (let j = map.length - 1; j >= 0; j--) {
|
||||
dataToMerge.push(map[j].frontMatterData);
|
||||
}
|
||||
|
||||
// Deep merge of layout front matter
|
||||
let data = TemplateData.mergeDeep(this.config.dataDeepMerge, {}, ...dataToMerge);
|
||||
delete data[this.config.keys.layout];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getData() {
|
||||
if (!this.dataCache) {
|
||||
this.dataCache = this.#getData();
|
||||
}
|
||||
|
||||
return this.dataCache;
|
||||
}
|
||||
|
||||
async #getCachedCompiledLayoutFunction() {
|
||||
let rawInput = await this.getPreRender();
|
||||
let renderFunction = await this.compile(rawInput);
|
||||
return renderFunction;
|
||||
}
|
||||
|
||||
// Do only cache this layout’s render function and delegate the rest to the other templates.
|
||||
async getCachedCompiledLayoutFunction() {
|
||||
if (!this.cachedCompiledLayoutFunction) {
|
||||
this.cachedCompiledLayoutFunction = this.#getCachedCompiledLayoutFunction();
|
||||
}
|
||||
|
||||
return this.cachedCompiledLayoutFunction;
|
||||
}
|
||||
|
||||
async getCompiledLayoutFunctions() {
|
||||
let layoutMap = await this.getTemplateLayoutMap();
|
||||
let fns = [];
|
||||
|
||||
try {
|
||||
fns.push({
|
||||
render: await this.getCachedCompiledLayoutFunction(),
|
||||
});
|
||||
|
||||
if (layoutMap.length > 1) {
|
||||
let [, /*currentLayout*/ parentLayout] = layoutMap;
|
||||
let { key } = parentLayout;
|
||||
|
||||
let layoutTemplate = TemplateLayout.getTemplate(
|
||||
key,
|
||||
this.eleventyConfig,
|
||||
this.extensionMap,
|
||||
);
|
||||
|
||||
// The parent already includes the rest of the layout chain
|
||||
let upstreamFns = await layoutTemplate.getCompiledLayoutFunctions();
|
||||
for (let j = 0, k = upstreamFns.length; j < k; j++) {
|
||||
fns.push(upstreamFns[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return fns;
|
||||
} catch (e) {
|
||||
debugDev("Clearing TemplateCache after error.");
|
||||
templateCache.clear();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
throw new Error("Internal error: `render` was removed from TemplateLayout.js in Eleventy 3.0.");
|
||||
}
|
||||
|
||||
// Inefficient? We want to compile all the templatelayouts into a single reusable callback?
|
||||
// Trouble: layouts may need data variables present downstream/upstream
|
||||
// This is called from Template->renderPageEntry
|
||||
async renderPageEntry(pageEntry) {
|
||||
let templateContent = pageEntry.templateContent;
|
||||
let compiledFunctions = await this.getCompiledLayoutFunctions();
|
||||
for (let { render } of compiledFunctions) {
|
||||
let data = {
|
||||
content: templateContent,
|
||||
...pageEntry.data,
|
||||
};
|
||||
|
||||
templateContent = await render(data);
|
||||
}
|
||||
|
||||
// Don’t set `templateContent` on pageEntry because collection items should not have layout markup
|
||||
return templateContent;
|
||||
}
|
||||
|
||||
resetCaches(types) {
|
||||
super.resetCaches(types);
|
||||
|
||||
delete this.dataCache;
|
||||
delete this.layoutChain;
|
||||
delete this.cachedLayoutMap;
|
||||
delete this.cachedCompiledLayoutFunction;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateLayout;
|
||||
136
node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js
generated
vendored
Normal file
136
node_modules/@11ty/eleventy/src/TemplateLayoutPathResolver.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
import fs from "node:fs";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
// import debugUtil from "debug";
|
||||
// const debug = debugUtil("Eleventy:TemplateLayoutPathResolver");
|
||||
|
||||
class TemplateLayoutPathResolver {
|
||||
constructor(path, extensionMap, eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new Error("Expected `eleventyConfig` in TemplateLayoutPathResolver constructor");
|
||||
}
|
||||
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.originalPath = path;
|
||||
this.originalDisplayPath =
|
||||
TemplatePath.join(this.layoutsDir, this.originalPath) +
|
||||
` (via \`layout: ${this.originalPath}\`)`; // for error messaging
|
||||
|
||||
this.path = path;
|
||||
this.aliases = {};
|
||||
this.extensionMap = extensionMap;
|
||||
if (!extensionMap) {
|
||||
throw new Error("Expected `extensionMap` in TemplateLayoutPathResolver constructor.");
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
getVirtualTemplate(layoutPath) {
|
||||
let inputDirRelativePath =
|
||||
this.eleventyConfig.directories.getLayoutPathRelativeToInputDirectory(layoutPath);
|
||||
return this.config.virtualTemplates[inputDirRelativePath];
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get layoutsDir() {
|
||||
return this.dirs.layouts || this.dirs.includes;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getLayoutsDir() {
|
||||
return this.layoutsDir;
|
||||
}
|
||||
|
||||
setAliases() {
|
||||
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
|
||||
}
|
||||
|
||||
// for testing
|
||||
set config(cfg) {
|
||||
this._config = cfg;
|
||||
this.init();
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (this.eleventyConfig) {
|
||||
return this.eleventyConfig.getConfig();
|
||||
} else {
|
||||
throw new Error("Missing this.eleventyConfig");
|
||||
}
|
||||
}
|
||||
|
||||
exists(layoutPath) {
|
||||
if (this.getVirtualTemplate(layoutPath)) {
|
||||
return true;
|
||||
}
|
||||
let fullPath = this.eleventyConfig.directories.getLayoutPath(layoutPath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
init() {
|
||||
// we might be able to move this into the constructor?
|
||||
this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases);
|
||||
|
||||
if (this.aliases[this.path]) {
|
||||
this.path = this.aliases[this.path];
|
||||
}
|
||||
|
||||
let useLayoutResolution = this.config.layoutResolution;
|
||||
|
||||
if (this.path.split(".").length > 0 && this.exists(this.path)) {
|
||||
this.filename = this.path;
|
||||
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.path);
|
||||
} else if (useLayoutResolution) {
|
||||
this.filename = this.findFileName();
|
||||
this.fullPath = this.eleventyConfig.directories.getLayoutPath(this.filename || "");
|
||||
}
|
||||
}
|
||||
|
||||
addLayoutAlias(from, to) {
|
||||
this.aliases[from] = to;
|
||||
}
|
||||
|
||||
getFileName() {
|
||||
if (!this.filename) {
|
||||
throw new Error(
|
||||
`You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
getFullPath() {
|
||||
if (!this.filename) {
|
||||
throw new Error(
|
||||
`You’re trying to use a layout that does not exist: ${this.originalDisplayPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.fullPath;
|
||||
}
|
||||
|
||||
findFileName() {
|
||||
for (let filename of this.extensionMap.getFileList(this.path)) {
|
||||
if (this.exists(filename)) {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getNormalizedLayoutKey() {
|
||||
return TemplatePath.stripLeadingSubPath(this.fullPath, this.layoutsDir);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateLayoutPathResolver;
|
||||
828
node_modules/@11ty/eleventy/src/TemplateMap.js
generated
vendored
Normal file
828
node_modules/@11ty/eleventy/src/TemplateMap.js
generated
vendored
Normal file
@@ -0,0 +1,828 @@
|
||||
import { DepGraph as DependencyGraph } from "dependency-graph";
|
||||
import { isPlainObject, TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import TemplateCollection from "./TemplateCollection.js";
|
||||
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
|
||||
import UsingCircularTemplateContentReferenceError from "./Errors/UsingCircularTemplateContentReferenceError.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import DuplicatePermalinkOutputError from "./Errors/DuplicatePermalinkOutputError.js";
|
||||
import TemplateData from "./Data/TemplateData.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateMap");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplateMap");
|
||||
|
||||
class TemplateMapConfigError extends EleventyBaseError {}
|
||||
class EleventyDataSchemaError extends EleventyBaseError {}
|
||||
|
||||
// These template URL filenames are allowed to exclude file extensions
|
||||
const EXTENSIONLESS_URL_ALLOWLIST = [
|
||||
"/_redirects", // Netlify specific
|
||||
"/.htaccess", // Apache
|
||||
"/_headers", // Cloudflare
|
||||
];
|
||||
|
||||
class TemplateMap {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateMapConfigError("Missing config argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.map = [];
|
||||
this.collectionsData = null;
|
||||
this.cached = false;
|
||||
this.verboseOutput = true;
|
||||
this.collection = new TemplateCollection();
|
||||
}
|
||||
|
||||
set userConfig(config) {
|
||||
this._userConfig = config;
|
||||
}
|
||||
|
||||
get userConfig() {
|
||||
if (!this._userConfig) {
|
||||
// TODO use this.config for this, need to add collections to mergeable props in userconfig
|
||||
this._userConfig = this.eleventyConfig.userConfig;
|
||||
}
|
||||
|
||||
return this._userConfig;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this._config) {
|
||||
this._config = this.eleventyConfig.getConfig();
|
||||
}
|
||||
return this._config;
|
||||
}
|
||||
|
||||
static get tagPrefix() {
|
||||
return "___TAG___";
|
||||
}
|
||||
|
||||
async add(template) {
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = await template.getData();
|
||||
let entries = await template.getTemplateMapEntries(data);
|
||||
|
||||
for (let map of entries) {
|
||||
this.map.push(map);
|
||||
}
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
getTagTarget(str) {
|
||||
if (str.startsWith("collections.")) {
|
||||
return str.slice("collections.".length);
|
||||
}
|
||||
// Fixes #2851
|
||||
if (str.startsWith("collections['") || str.startsWith('collections["')) {
|
||||
return str.slice("collections['".length, -2);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---
|
||||
* pagination:
|
||||
* data: collections
|
||||
* ---
|
||||
*/
|
||||
isPaginationOverAllCollections(entry) {
|
||||
if (entry.data.pagination?.data) {
|
||||
return (
|
||||
entry.data.pagination.data === "collections" ||
|
||||
entry.data.pagination.data === "collections.all"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getPaginationTagTarget(entry) {
|
||||
if (entry.data.pagination?.data) {
|
||||
return this.getTagTarget(entry.data.pagination.data);
|
||||
}
|
||||
}
|
||||
|
||||
addTagsToGraph(graph, inputPath, tags) {
|
||||
if (!Array.isArray(tags)) {
|
||||
return;
|
||||
}
|
||||
for (let tag of tags) {
|
||||
let tagWithPrefix = TemplateMap.tagPrefix + tag;
|
||||
if (!graph.hasNode(tagWithPrefix)) {
|
||||
graph.addNode(tagWithPrefix);
|
||||
}
|
||||
|
||||
// Populates to collections.tagName
|
||||
// Dependency from tag to inputPath
|
||||
graph.addDependency(tagWithPrefix, inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
addDeclaredDependenciesToGraph(graph, inputPath, deps) {
|
||||
if (!Array.isArray(deps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tag of deps) {
|
||||
let tagWithPrefix = TemplateMap.tagPrefix + tag;
|
||||
if (!graph.hasNode(tagWithPrefix)) {
|
||||
graph.addNode(tagWithPrefix);
|
||||
}
|
||||
|
||||
// Dependency from inputPath to collection/tag
|
||||
graph.addDependency(inputPath, tagWithPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude: Pagination templates consuming `collections` or `collections.all`
|
||||
// Exclude: Pagination templates that consume config API collections
|
||||
|
||||
// Include: Pagination templates that don’t consume config API collections
|
||||
// Include: Templates that don’t use Pagination
|
||||
getMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
|
||||
graph.addNode(tagPrefix + "all");
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (this.isPaginationOverAllCollections(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// using Pagination (but not targeting a user config collection)
|
||||
let paginationTagTarget = this.getPaginationTagTarget(entry);
|
||||
if (paginationTagTarget) {
|
||||
if (this.isUserConfigCollectionName(paginationTagTarget)) {
|
||||
// delay this one to the second stage
|
||||
continue;
|
||||
} else {
|
||||
// using pagination but over a tagged collection
|
||||
graph.addNode(entry.inputPath);
|
||||
if (!graph.hasNode(tagPrefix + paginationTagTarget)) {
|
||||
graph.addNode(tagPrefix + paginationTagTarget);
|
||||
}
|
||||
graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget);
|
||||
}
|
||||
} else {
|
||||
// not using pagination
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
|
||||
let collections = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
this.addTagsToGraph(graph, entry.inputPath, collections);
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Exclude: Pagination templates consuming `collections` or `collections.all`
|
||||
// Include: Pagination templates that consume config API collections
|
||||
getDelayedMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
|
||||
graph.addNode(tagPrefix + "all");
|
||||
|
||||
let userConfigCollections = this.getUserConfigCollectionNames();
|
||||
|
||||
// Add tags from named user config collections
|
||||
for (let tag of userConfigCollections) {
|
||||
graph.addNode(tagPrefix + tag);
|
||||
}
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (this.isPaginationOverAllCollections(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let paginationTagTarget = this.getPaginationTagTarget(entry);
|
||||
if (paginationTagTarget && this.isUserConfigCollectionName(paginationTagTarget)) {
|
||||
if (!graph.hasNode(entry.inputPath)) {
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget);
|
||||
|
||||
let collections = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
this.addTagsToGraph(graph, entry.inputPath, collections);
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Exclude: Pagination templates consuming `collections.all`
|
||||
// Include: Pagination templates consuming `collections`
|
||||
getPaginatedOverCollectionsMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
let allNodeAdded = false;
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (this.isPaginationOverAllCollections(entry) && !this.getPaginationTagTarget(entry)) {
|
||||
if (!allNodeAdded) {
|
||||
graph.addNode(tagPrefix + "all");
|
||||
allNodeAdded = true;
|
||||
}
|
||||
|
||||
if (!graph.hasNode(entry.inputPath)) {
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
|
||||
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
if (collectionNames.includes("all")) {
|
||||
// collections.all
|
||||
graph.addDependency(tagPrefix + "all", entry.inputPath);
|
||||
|
||||
// Note that `tags` are otherwise ignored here
|
||||
}
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// Include: Pagination templates consuming `collections.all`
|
||||
getPaginatedOverAllCollectionMappedDependencies() {
|
||||
let graph = new DependencyGraph();
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
let allNodeAdded = false;
|
||||
|
||||
for (let entry of this.map) {
|
||||
if (
|
||||
this.isPaginationOverAllCollections(entry) &&
|
||||
this.getPaginationTagTarget(entry) === "all"
|
||||
) {
|
||||
if (!allNodeAdded) {
|
||||
graph.addNode(tagPrefix + "all");
|
||||
allNodeAdded = true;
|
||||
}
|
||||
|
||||
if (!graph.hasNode(entry.inputPath)) {
|
||||
graph.addNode(entry.inputPath);
|
||||
}
|
||||
|
||||
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
if (collectionNames.includes("all")) {
|
||||
// Populates into collections.all
|
||||
// This is circular!
|
||||
graph.addDependency(tagPrefix + "all", entry.inputPath);
|
||||
|
||||
// Note that `tags` are otherwise ignored here
|
||||
}
|
||||
|
||||
this.addDeclaredDependenciesToGraph(
|
||||
graph,
|
||||
entry.inputPath,
|
||||
entry.data.eleventyImport?.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
getTemplateMapDependencyGraph() {
|
||||
return [
|
||||
this.getMappedDependencies(),
|
||||
this.getDelayedMappedDependencies(),
|
||||
this.getPaginatedOverCollectionsMappedDependencies(),
|
||||
this.getPaginatedOverAllCollectionMappedDependencies(),
|
||||
];
|
||||
}
|
||||
|
||||
getFullTemplateMapOrder() {
|
||||
// convert dependency graphs to ordered arrays
|
||||
return this.getTemplateMapDependencyGraph().map((entry) => entry.overallOrder());
|
||||
}
|
||||
|
||||
#addEntryToGlobalDependencyGraph(entry) {
|
||||
let paginationTagTarget = this.getPaginationTagTarget(entry);
|
||||
if (paginationTagTarget) {
|
||||
this.config.uses.addDependencyConsumesCollection(entry.inputPath, paginationTagTarget);
|
||||
}
|
||||
|
||||
let collectionNames = TemplateData.getIncludedCollectionNames(entry.data);
|
||||
for (let name of collectionNames) {
|
||||
this.config.uses.addDependencyPublishesToCollection(entry.inputPath, name);
|
||||
}
|
||||
|
||||
if (Array.isArray(entry.data.eleventyImport?.collections)) {
|
||||
for (let tag of entry.data.eleventyImport.collections) {
|
||||
this.config.uses.addDependencyConsumesCollection(entry.inputPath, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addAllToGlobalDependencyGraph() {
|
||||
for (let entry of this.map) {
|
||||
this.#addEntryToGlobalDependencyGraph(entry);
|
||||
}
|
||||
}
|
||||
|
||||
async setCollectionByTagName(tagName) {
|
||||
if (this.isUserConfigCollectionName(tagName)) {
|
||||
// async
|
||||
this.collectionsData[tagName] = await this.getUserConfigCollection(tagName);
|
||||
} else {
|
||||
this.collectionsData[tagName] = this.getTaggedCollection(tagName);
|
||||
}
|
||||
|
||||
let precompiled = this.config.precompiledCollections;
|
||||
if (precompiled?.[tagName]) {
|
||||
if (
|
||||
tagName === "all" ||
|
||||
!Array.isArray(this.collectionsData[tagName]) ||
|
||||
this.collectionsData[tagName].length === 0
|
||||
) {
|
||||
this.collectionsData[tagName] = precompiled[tagName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(slightlyoff): major bottleneck
|
||||
async initDependencyMap(dependencyMap) {
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
for (let depEntry of dependencyMap) {
|
||||
if (depEntry.startsWith(tagPrefix)) {
|
||||
// is a tag (collection) entry
|
||||
let tagName = depEntry.slice(tagPrefix.length);
|
||||
await this.setCollectionByTagName(tagName);
|
||||
} else {
|
||||
// is a template entry
|
||||
let map = this.getMapEntryForInputPath(depEntry);
|
||||
map._pages = await map.template.getTemplates(map.data);
|
||||
|
||||
if (map._pages.length === 0) {
|
||||
// Reminder: a serverless code path was removed here.
|
||||
} else {
|
||||
let counter = 0;
|
||||
for (let page of map._pages) {
|
||||
// Copy outputPath to map entry
|
||||
// This is no longer used internally, just for backwards compatibility
|
||||
// Error added in v3 for https://github.com/11ty/eleventy/issues/3183
|
||||
if (map.data.pagination) {
|
||||
if (!Object.prototype.hasOwnProperty.call(map, "outputPath")) {
|
||||
Object.defineProperty(map, "outputPath", {
|
||||
get() {
|
||||
throw new Error(
|
||||
"Internal error: `.outputPath` on a paginated map entry is not consistent. Use `_pages[…].outputPath` instead.",
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (!map.outputPath) {
|
||||
map.outputPath = page.outputPath;
|
||||
}
|
||||
|
||||
if (counter === 0 || map.data.pagination?.addAllPagesToCollections) {
|
||||
if (map.data.eleventyExcludeFromCollections !== true) {
|
||||
// is in *some* collections
|
||||
this.collection.add(page);
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async cache() {
|
||||
debug("Caching collections objects.");
|
||||
this.collectionsData = {};
|
||||
|
||||
for (let entry of this.map) {
|
||||
entry.data.collections = this.collectionsData;
|
||||
}
|
||||
|
||||
let [dependencyMap, delayedDependencyMap, firstPaginatedDepMap, secondPaginatedDepMap] =
|
||||
this.getFullTemplateMapOrder();
|
||||
|
||||
await this.initDependencyMap(dependencyMap);
|
||||
await this.initDependencyMap(delayedDependencyMap);
|
||||
await this.initDependencyMap(firstPaginatedDepMap);
|
||||
await this.initDependencyMap(secondPaginatedDepMap);
|
||||
|
||||
await this.resolveRemainingComputedData();
|
||||
|
||||
let orderedPaths = this.getOrderedInputPaths(
|
||||
dependencyMap,
|
||||
delayedDependencyMap,
|
||||
firstPaginatedDepMap,
|
||||
secondPaginatedDepMap,
|
||||
);
|
||||
|
||||
let orderedMap = orderedPaths.map((inputPath) => {
|
||||
return this.getMapEntryForInputPath(inputPath);
|
||||
});
|
||||
|
||||
await this.config.events.emitLazy("eleventy.contentMap", () => {
|
||||
return {
|
||||
inputPathToUrl: this.generateInputUrlContentMap(orderedMap),
|
||||
urlToInputPath: this.generateUrlMap(orderedMap),
|
||||
};
|
||||
});
|
||||
|
||||
await this.runDataSchemas(orderedMap);
|
||||
await this.populateContentDataInMap(orderedMap);
|
||||
|
||||
this.populateCollectionsWithContent();
|
||||
this.cached = true;
|
||||
|
||||
this.checkForDuplicatePermalinks();
|
||||
this.checkForMissingFileExtensions();
|
||||
|
||||
await this.config.events.emitLazy("eleventy.layouts", () => this.generateLayoutsMap());
|
||||
}
|
||||
|
||||
generateInputUrlContentMap(orderedMap) {
|
||||
let entries = {};
|
||||
for (let entry of orderedMap) {
|
||||
entries[entry.inputPath] = entry._pages.map((entry) => entry.url);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
generateUrlMap(orderedMap) {
|
||||
let entries = {};
|
||||
for (let entry of orderedMap) {
|
||||
for (let page of entry._pages) {
|
||||
// duplicate urls throw an error, so we can return non array here
|
||||
entries[page.url] = {
|
||||
inputPath: entry.inputPath,
|
||||
groupNumber: page.groupNumber,
|
||||
};
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
// TODO(slightlyoff): hot inner loop?
|
||||
getMapEntryForInputPath(inputPath) {
|
||||
for (let map of this.map) {
|
||||
if (map.inputPath === inputPath) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out any tag nodes
|
||||
getOrderedInputPaths(...maps) {
|
||||
let orderedMap = [];
|
||||
let tagPrefix = TemplateMap.tagPrefix;
|
||||
|
||||
for (let map of maps) {
|
||||
for (let dep of map) {
|
||||
if (!dep.startsWith(tagPrefix)) {
|
||||
orderedMap.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
return orderedMap;
|
||||
}
|
||||
|
||||
async runDataSchemas(orderedMap) {
|
||||
for (let map of orderedMap) {
|
||||
if (!map._pages) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let pageEntry of map._pages) {
|
||||
// Data Schema callback #879
|
||||
if (typeof pageEntry.data[this.config.keys.dataSchema] === "function") {
|
||||
try {
|
||||
await pageEntry.data[this.config.keys.dataSchema](pageEntry.data);
|
||||
} catch (e) {
|
||||
throw new EleventyDataSchemaError(
|
||||
`Error in the data schema for: ${map.inputPath} (via \`eleventyDataSchema\`)`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async populateContentDataInMap(orderedMap) {
|
||||
let usedTemplateContentTooEarlyMap = [];
|
||||
|
||||
// Note that empty pagination templates will be skipped here as not renderable
|
||||
let filteredMap = orderedMap.filter((entry) => entry.template.isRenderable());
|
||||
|
||||
for (let map of filteredMap) {
|
||||
if (!map._pages) {
|
||||
throw new Error(`Internal error: _pages not found for ${map.inputPath}`);
|
||||
}
|
||||
|
||||
// IMPORTANT: this is where template content is rendered
|
||||
try {
|
||||
for (let pageEntry of map._pages) {
|
||||
pageEntry.templateContent =
|
||||
await pageEntry.template.renderPageEntryWithoutLayout(pageEntry);
|
||||
}
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
usedTemplateContentTooEarlyMap.push(map);
|
||||
|
||||
// Reset cached render promise
|
||||
for (let pageEntry of map._pages) {
|
||||
pageEntry.template.resetCaches({ render: true });
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
debugDev("Added this.map[...].templateContent, outputPath, et al for one map entry");
|
||||
}
|
||||
|
||||
for (let map of usedTemplateContentTooEarlyMap) {
|
||||
try {
|
||||
for (let pageEntry of map._pages) {
|
||||
pageEntry.templateContent =
|
||||
await pageEntry.template.renderPageEntryWithoutLayout(pageEntry);
|
||||
}
|
||||
} catch (e) {
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
throw new UsingCircularTemplateContentReferenceError(
|
||||
`${map.inputPath} contains a circular reference (using collections) to its own templateContent.`,
|
||||
);
|
||||
} else {
|
||||
// rethrow?
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTaggedCollection(tag) {
|
||||
let result;
|
||||
if (!tag || tag === "all") {
|
||||
result = this.collection.getAllSorted();
|
||||
} else {
|
||||
result = this.collection.getFilteredByTag(tag);
|
||||
}
|
||||
debug(`Collection: collections.${tag || "all"} size: ${result.length}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 3.0.0-alpha.1: setUserConfigCollections method removed (was only used for testing) */
|
||||
isUserConfigCollectionName(name) {
|
||||
let collections = this.userConfig.getCollections();
|
||||
return name && !!collections[name];
|
||||
}
|
||||
|
||||
getUserConfigCollectionNames() {
|
||||
return Object.keys(this.userConfig.getCollections());
|
||||
}
|
||||
|
||||
async getUserConfigCollection(name) {
|
||||
let configCollections = this.userConfig.getCollections();
|
||||
|
||||
// This works with async now
|
||||
let result = await configCollections[name](this.collection);
|
||||
|
||||
debug(`Collection: collections.${name} size: ${result.length}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
populateCollectionsWithContent() {
|
||||
for (let collectionName in this.collectionsData) {
|
||||
// skip custom collections set in configuration files that have arbitrary types
|
||||
if (!Array.isArray(this.collectionsData[collectionName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let item of this.collectionsData[collectionName]) {
|
||||
// skip custom collections set in configuration files that have arbitrary types
|
||||
if (!isPlainObject(item) || !("inputPath" in item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = this.getMapEntryForInputPath(item.inputPath);
|
||||
// This check skips precompiled collections
|
||||
if (entry) {
|
||||
let index = item.pageNumber || 0;
|
||||
let content = entry._pages[index]._templateContent;
|
||||
if (content !== undefined) {
|
||||
item.templateContent = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async resolveRemainingComputedData() {
|
||||
let promises = [];
|
||||
for (let entry of this.map) {
|
||||
for (let pageEntry of entry._pages) {
|
||||
if (this.config.keys.computed in pageEntry.data) {
|
||||
promises.push(await pageEntry.template.resolveRemainingComputedData(pageEntry.data));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async generateLayoutsMap() {
|
||||
let layouts = {};
|
||||
|
||||
for (let entry of this.map) {
|
||||
for (let page of entry._pages) {
|
||||
let tmpl = page.template;
|
||||
let layoutKey = page.data[this.config.keys.layout];
|
||||
if (layoutKey) {
|
||||
let layout = tmpl.getLayout(layoutKey);
|
||||
let layoutChain = await layout.getLayoutChain();
|
||||
let priors = [];
|
||||
for (let filepath of layoutChain) {
|
||||
if (!layouts[filepath]) {
|
||||
layouts[filepath] = new Set();
|
||||
}
|
||||
layouts[filepath].add(page.inputPath);
|
||||
for (let prior of priors) {
|
||||
layouts[filepath].add(prior);
|
||||
}
|
||||
priors.push(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in layouts) {
|
||||
layouts[key] = Array.from(layouts[key]);
|
||||
}
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
#onEachPage(callback) {
|
||||
for (let template of this.map) {
|
||||
for (let page of template._pages) {
|
||||
callback(page, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkForDuplicatePermalinks() {
|
||||
let inputs = {};
|
||||
let permalinks = {};
|
||||
let warnings = {};
|
||||
this.#onEachPage((page, template) => {
|
||||
if (page.outputPath === false || page.url === false) {
|
||||
// do nothing (also serverless)
|
||||
} else {
|
||||
// Make sure output doesn’t overwrite input (e.g. --input=. --output=.)
|
||||
// Related to https://github.com/11ty/eleventy/issues/3327
|
||||
if (page.outputPath === page.inputPath) {
|
||||
throw new DuplicatePermalinkOutputError(
|
||||
`The template at "${page.inputPath}" attempted to overwrite itself.`,
|
||||
);
|
||||
} else if (inputs[page.outputPath]) {
|
||||
throw new DuplicatePermalinkOutputError(
|
||||
`The template at "${page.inputPath}" attempted to overwrite an existing template at "${page.outputPath}".`,
|
||||
);
|
||||
}
|
||||
inputs[page.inputPath] = true;
|
||||
|
||||
if (!permalinks[page.outputPath]) {
|
||||
permalinks[page.outputPath] = [template.inputPath];
|
||||
} else {
|
||||
warnings[page.outputPath] = `Output conflict: multiple input files are writing to \`${
|
||||
page.outputPath
|
||||
}\`. Use distinct \`permalink\` values to resolve this conflict.
|
||||
1. ${template.inputPath}
|
||||
${permalinks[page.outputPath]
|
||||
.map(function (inputPath, index) {
|
||||
return ` ${index + 2}. ${inputPath}\n`;
|
||||
})
|
||||
.join("")}
|
||||
`;
|
||||
permalinks[page.outputPath].push(template.inputPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let warningList = Object.values(warnings);
|
||||
if (warningList.length) {
|
||||
// throw one at a time
|
||||
throw new DuplicatePermalinkOutputError(warningList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
checkForMissingFileExtensions() {
|
||||
// disabled in config
|
||||
if (this.userConfig?.errorReporting?.allowMissingExtensions === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#onEachPage((page) => {
|
||||
if (
|
||||
page.outputPath === false ||
|
||||
page.url === false ||
|
||||
page.data.eleventyAllowMissingExtension ||
|
||||
EXTENSIONLESS_URL_ALLOWLIST.some((url) => page.url.endsWith(url))
|
||||
) {
|
||||
// do nothing (also serverless)
|
||||
} else {
|
||||
if (TemplatePath.getExtension(page.outputPath) === "") {
|
||||
let e =
|
||||
new Error(`The template at '${page.inputPath}' attempted to write to '${page.outputPath}'${page.data.permalink ? ` (via \`permalink\` value: '${page.data.permalink}')` : ""}, which is a target on the file system that does not include a file extension.
|
||||
|
||||
You *probably* want to add a file extension to your permalink so that hosts will know how to correctly serve this file to web browsers. Without a file extension, this file may not be reliably deployed without additional hosting configuration (it won’t have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.
|
||||
|
||||
Learn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes
|
||||
|
||||
This is usually but not *always* an error so if you’d like to disable this error message, add \`eleventyAllowMissingExtension: true\` somewhere in the data cascade for this template or use \`eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });\` to disable this feature globally.`);
|
||||
e.skipOriginalStack = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO move these into TemplateMapTest.js
|
||||
_testGetAllTags() {
|
||||
let allTags = {};
|
||||
for (let map of this.map) {
|
||||
let tags = map.data.tags;
|
||||
if (Array.isArray(tags)) {
|
||||
for (let tag of tags) {
|
||||
allTags[tag] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(allTags);
|
||||
}
|
||||
|
||||
async _testGetUserConfigCollectionsData() {
|
||||
let collections = {};
|
||||
let configCollections = this.userConfig.getCollections();
|
||||
|
||||
for (let name in configCollections) {
|
||||
collections[name] = configCollections[name](this.collection);
|
||||
|
||||
debug(`Collection: collections.${name} size: ${collections[name].length}`);
|
||||
}
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
async _testGetTaggedCollectionsData() {
|
||||
let collections = {};
|
||||
collections.all = this.collection.getAllSorted();
|
||||
debug(`Collection: collections.all size: ${collections.all.length}`);
|
||||
|
||||
let tags = this._testGetAllTags();
|
||||
for (let tag of tags) {
|
||||
collections[tag] = this.collection.getFilteredByTag(tag);
|
||||
debug(`Collection: collections.${tag} size: ${collections[tag].length}`);
|
||||
}
|
||||
return collections;
|
||||
}
|
||||
|
||||
async _testGetAllCollectionsData() {
|
||||
let collections = {};
|
||||
let taggedCollections = await this._testGetTaggedCollectionsData();
|
||||
Object.assign(collections, taggedCollections);
|
||||
|
||||
let userConfigCollections = await this._testGetUserConfigCollectionsData();
|
||||
Object.assign(collections, userConfigCollections);
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
async _testGetCollectionsData() {
|
||||
if (!this.cached) {
|
||||
await this.cache();
|
||||
}
|
||||
|
||||
return this.collectionsData;
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateMap;
|
||||
339
node_modules/@11ty/eleventy/src/TemplatePassthrough.js
generated
vendored
Normal file
339
node_modules/@11ty/eleventy/src/TemplatePassthrough.js
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
import util from "node:util";
|
||||
import path from "node:path";
|
||||
import fs from "graceful-fs";
|
||||
|
||||
import isGlob from "is-glob";
|
||||
import copy from "@11ty/recursive-copy";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
import ProjectDirectories from "./Util/ProjectDirectories.js";
|
||||
|
||||
const fsExists = util.promisify(fs.exists);
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplatePassthrough");
|
||||
|
||||
class TemplatePassthroughError extends EleventyBaseError {}
|
||||
|
||||
class TemplatePassthrough {
|
||||
#isExistsCache = {};
|
||||
#isDirectoryCache = {};
|
||||
|
||||
constructor(path, eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplatePassthroughError(
|
||||
"Missing `eleventyConfig` or was not an instance of `TemplateConfig`.",
|
||||
);
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
|
||||
this.benchmarks = {
|
||||
aggregate: this.config.benchmarkManager.get("Aggregate"),
|
||||
};
|
||||
|
||||
this.rawPath = path;
|
||||
|
||||
// inputPath is relative to the root of your project and not your Eleventy input directory.
|
||||
// TODO normalize these with forward slashes
|
||||
this.inputPath = this.normalizeDirectory(path.inputPath);
|
||||
this.isInputPathGlob = isGlob(this.inputPath);
|
||||
|
||||
this.outputPath = path.outputPath;
|
||||
|
||||
this.copyOptions = path.copyOptions; // custom options for recursive-copy
|
||||
|
||||
this.isDryRun = false;
|
||||
this.isIncremental = false;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.eleventyConfig.getConfig();
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
// inputDir is used when stripping from output path in `getOutputPath`
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
/* { inputPath, outputPath } though outputPath is *not* the full path: just the output directory */
|
||||
getPath() {
|
||||
return this.rawPath;
|
||||
}
|
||||
|
||||
async getOutputPath(inputFileFromGlob) {
|
||||
let { inputDir, outputDir, outputPath, inputPath } = this;
|
||||
|
||||
if (outputPath === true) {
|
||||
// no explicit target, implied target
|
||||
if (this.isDirectory(inputPath)) {
|
||||
let inputRelativePath = TemplatePath.stripLeadingSubPath(
|
||||
inputFileFromGlob || inputPath,
|
||||
inputDir,
|
||||
);
|
||||
return ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(outputDir, inputRelativePath),
|
||||
);
|
||||
}
|
||||
|
||||
return TemplatePath.normalize(
|
||||
TemplatePath.join(
|
||||
outputDir,
|
||||
TemplatePath.stripLeadingSubPath(inputFileFromGlob || inputPath, inputDir),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (inputFileFromGlob) {
|
||||
return this.getOutputPathForGlobFile(inputFileFromGlob);
|
||||
}
|
||||
|
||||
// Has explicit target
|
||||
|
||||
// Bug when copying incremental file overwriting output directory (and making it a file)
|
||||
// e.g. public/test.css -> _site
|
||||
// https://github.com/11ty/eleventy/issues/2278
|
||||
let fullOutputPath = TemplatePath.normalize(TemplatePath.join(outputDir, outputPath));
|
||||
if (outputPath === "" || this.isDirectory(inputPath)) {
|
||||
fullOutputPath = ProjectDirectories.normalizeDirectory(fullOutputPath);
|
||||
}
|
||||
|
||||
// TODO room for improvement here:
|
||||
if (
|
||||
!this.isInputPathGlob &&
|
||||
(await fsExists(inputPath)) &&
|
||||
!this.isDirectory(inputPath) &&
|
||||
this.isDirectory(fullOutputPath)
|
||||
) {
|
||||
let filename = path.parse(inputPath).base;
|
||||
return TemplatePath.normalize(TemplatePath.join(fullOutputPath, filename));
|
||||
}
|
||||
|
||||
return fullOutputPath;
|
||||
}
|
||||
|
||||
async getOutputPathForGlobFile(inputFileFromGlob) {
|
||||
return TemplatePath.join(
|
||||
await this.getOutputPath(),
|
||||
TemplatePath.getLastPathSegment(inputFileFromGlob),
|
||||
);
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.runMode = runMode;
|
||||
}
|
||||
|
||||
setIsIncremental(isIncremental) {
|
||||
this.isIncremental = isIncremental;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
async getFiles(glob) {
|
||||
debug("Searching for: %o", glob);
|
||||
let b = this.benchmarks.aggregate.get("Searching the file system (passthrough)");
|
||||
b.before();
|
||||
let files = TemplatePath.addLeadingDotSlashArray(
|
||||
await this.fileSystemSearch.search("passthrough", glob),
|
||||
);
|
||||
b.after();
|
||||
return files;
|
||||
}
|
||||
|
||||
isExists(dir) {
|
||||
if (this.#isExistsCache[dir] === undefined) {
|
||||
this.#isExistsCache[dir] = fs.existsSync(dir);
|
||||
}
|
||||
return this.#isExistsCache[dir];
|
||||
}
|
||||
|
||||
isDirectory(dir) {
|
||||
if (this.#isDirectoryCache[dir] === undefined) {
|
||||
if (isGlob(this.inputPath)) {
|
||||
this.#isDirectoryCache[dir] = false;
|
||||
} else if (!this.isExists(dir)) {
|
||||
this.#isDirectoryCache[dir] = false;
|
||||
} else if (fs.statSync(dir).isDirectory()) {
|
||||
this.#isDirectoryCache[dir] = true;
|
||||
} else {
|
||||
this.#isDirectoryCache[dir] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return this.#isDirectoryCache[dir];
|
||||
}
|
||||
|
||||
// dir is guaranteed to exist by context
|
||||
// dir may not be a directory
|
||||
normalizeDirectory(dir) {
|
||||
if (dir && typeof dir === "string") {
|
||||
if (dir.endsWith(path.sep) || dir.endsWith("/")) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
// When inputPath is a directory, make sure it has a slash for passthrough copy aliasing
|
||||
// https://github.com/11ty/eleventy/issues/2709
|
||||
if (this.isDirectory(dir)) {
|
||||
return `${dir}/`;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
// maps input paths to output paths
|
||||
async getFileMap() {
|
||||
// TODO VirtualFileSystem candidate
|
||||
if (!isGlob(this.inputPath) && this.isExists(this.inputPath)) {
|
||||
return [
|
||||
{
|
||||
inputPath: this.inputPath,
|
||||
outputPath: await this.getOutputPath(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let paths = [];
|
||||
// If not directory or file, attempt to get globs
|
||||
let files = await this.getFiles(this.inputPath);
|
||||
for (let filePathFromGlob of files) {
|
||||
paths.push({
|
||||
inputPath: filePathFromGlob,
|
||||
outputPath: await this.getOutputPath(filePathFromGlob),
|
||||
});
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/* Types:
|
||||
* 1. via glob, individual files found
|
||||
* 2. directory, triggers an event for each file
|
||||
* 3. individual file
|
||||
*/
|
||||
async copy(src, dest, copyOptions) {
|
||||
if (
|
||||
!TemplatePath.stripLeadingDotSlash(dest).startsWith(
|
||||
TemplatePath.stripLeadingDotSlash(this.outputDir),
|
||||
)
|
||||
) {
|
||||
return Promise.reject(
|
||||
new TemplatePassthroughError(
|
||||
"Destination is not in the site output directory. Check your passthrough paths.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let fileCopyCount = 0;
|
||||
let fileSizeCount = 0;
|
||||
let map = {};
|
||||
let b = this.benchmarks.aggregate.get("Passthrough Copy File");
|
||||
// returns a promise
|
||||
return copy(src, dest, copyOptions)
|
||||
.on(copy.events.COPY_FILE_START, (copyOp) => {
|
||||
// Access to individual files at `copyOp.src`
|
||||
debug("Copying individual file %o", copyOp.src);
|
||||
map[copyOp.src] = copyOp.dest;
|
||||
b.before();
|
||||
})
|
||||
.on(copy.events.COPY_FILE_COMPLETE, (copyOp) => {
|
||||
fileCopyCount++;
|
||||
fileSizeCount += copyOp.stats.size;
|
||||
b.after();
|
||||
})
|
||||
.then(() => {
|
||||
return {
|
||||
count: fileCopyCount,
|
||||
size: fileSizeCount,
|
||||
map,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async write() {
|
||||
if (this.isDryRun) {
|
||||
return Promise.resolve({
|
||||
count: 0,
|
||||
map: {},
|
||||
});
|
||||
}
|
||||
|
||||
debug("Copying %o", this.inputPath);
|
||||
let fileMap = await this.getFileMap();
|
||||
|
||||
// default options for recursive-copy
|
||||
// see https://www.npmjs.com/package/recursive-copy#arguments
|
||||
let copyOptionsDefault = {
|
||||
overwrite: true, // overwrite output. fails when input is directory (mkdir) and output is file
|
||||
dot: true, // copy dotfiles
|
||||
junk: false, // copy cache files like Thumbs.db
|
||||
results: false,
|
||||
expand: false, // follow symlinks (matches recursive-copy default)
|
||||
debug: false, // (matches recursive-copy default)
|
||||
|
||||
// Note: `filter` callback function only passes in a relative path, which is unreliable
|
||||
// See https://github.com/timkendrick/recursive-copy/blob/4c9a8b8a4bf573285e9c4a649a30a2b59ccf441c/lib/copy.js#L59
|
||||
// e.g. `{ filePaths: [ './img/coolkid.jpg' ], relativePaths: [ '' ] }`
|
||||
};
|
||||
|
||||
let copyOptions = Object.assign(copyOptionsDefault, this.copyOptions);
|
||||
|
||||
let promises = fileMap.map((entry) => {
|
||||
// For-free passthrough copy
|
||||
if (checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
||||
let aliasMap = {};
|
||||
aliasMap[entry.inputPath] = entry.outputPath;
|
||||
|
||||
return Promise.resolve({
|
||||
count: 0,
|
||||
map: aliasMap,
|
||||
});
|
||||
}
|
||||
|
||||
// Copy the files (only in build mode)
|
||||
return this.copy(entry.inputPath, entry.outputPath, copyOptions);
|
||||
});
|
||||
|
||||
// IMPORTANT: this returns an array of promises, does not await for promise to finish
|
||||
return Promise.all(promises).then(
|
||||
(results) => {
|
||||
// collate the count and input/output map results from the array.
|
||||
let count = 0;
|
||||
let size = 0;
|
||||
let map = {};
|
||||
|
||||
for (let result of results) {
|
||||
count += result.count;
|
||||
size += result.size;
|
||||
Object.assign(map, result.map);
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
size,
|
||||
map,
|
||||
};
|
||||
},
|
||||
(err) => {
|
||||
throw new TemplatePassthroughError(`Error copying passthrough files: ${err.message}`, err);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePassthrough;
|
||||
311
node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js
generated
vendored
Normal file
311
node_modules/@11ty/eleventy/src/TemplatePassthroughManager.js
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
import isGlob from "is-glob";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import TemplatePassthrough from "./TemplatePassthrough.js";
|
||||
import checkPassthroughCopyBehavior from "./Util/PassthroughCopyBehaviorCheck.js";
|
||||
import { isGlobMatch } from "./Util/GlobMatcher.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplatePassthroughManager");
|
||||
const debugDev = debugUtil("Dev:Eleventy:TemplatePassthroughManager");
|
||||
|
||||
class TemplatePassthroughManagerConfigError extends EleventyBaseError {}
|
||||
class TemplatePassthroughManagerCopyError extends EleventyBaseError {}
|
||||
|
||||
class TemplatePassthroughManager {
|
||||
constructor(eleventyConfig) {
|
||||
if (!eleventyConfig || eleventyConfig.constructor.name !== "TemplateConfig") {
|
||||
throw new TemplatePassthroughManagerConfigError("Missing or invalid `config` argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = eleventyConfig.getConfig();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.count = 0;
|
||||
this.size = 0;
|
||||
this.conflictMap = {};
|
||||
this.incrementalFile = null;
|
||||
debug("Resetting counts to 0");
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
}
|
||||
|
||||
setRunMode(runMode) {
|
||||
this.runMode = runMode;
|
||||
}
|
||||
|
||||
setIncrementalFile(path) {
|
||||
if (path) {
|
||||
this.incrementalFile = path;
|
||||
}
|
||||
}
|
||||
|
||||
_normalizePaths(path, outputPath, copyOptions = {}) {
|
||||
return {
|
||||
inputPath: TemplatePath.addLeadingDotSlash(path),
|
||||
outputPath: outputPath ? TemplatePath.stripLeadingDotSlash(outputPath) : true,
|
||||
copyOptions,
|
||||
};
|
||||
}
|
||||
|
||||
getConfigPaths() {
|
||||
let paths = [];
|
||||
let pathsRaw = this.config.passthroughCopies || {};
|
||||
debug("`addPassthroughCopy` config API paths: %o", pathsRaw);
|
||||
for (let [inputPath, { outputPath, copyOptions }] of Object.entries(pathsRaw)) {
|
||||
paths.push(this._normalizePaths(inputPath, outputPath, copyOptions));
|
||||
}
|
||||
debug("`addPassthroughCopy` config API normalized paths: %o", paths);
|
||||
return paths;
|
||||
}
|
||||
|
||||
getConfigPathGlobs() {
|
||||
return this.getConfigPaths().map((path) => {
|
||||
return TemplatePath.convertToRecursiveGlobSync(path.inputPath);
|
||||
});
|
||||
}
|
||||
|
||||
getNonTemplatePaths(paths) {
|
||||
let matches = [];
|
||||
for (let path of paths) {
|
||||
if (!this.extensionMap.hasEngine(path)) {
|
||||
matches.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
getCopyCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
getCopySize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
setFileSystemSearch(fileSystemSearch) {
|
||||
this.fileSystemSearch = fileSystemSearch;
|
||||
}
|
||||
|
||||
getTemplatePassthroughForPath(path, isIncremental = false) {
|
||||
let inst = new TemplatePassthrough(path, this.eleventyConfig);
|
||||
|
||||
inst.setFileSystemSearch(this.fileSystemSearch);
|
||||
inst.setIsIncremental(isIncremental);
|
||||
inst.setDryRun(this.isDryRun);
|
||||
inst.setRunMode(this.runMode);
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
async copyPassthrough(pass) {
|
||||
if (!(pass instanceof TemplatePassthrough)) {
|
||||
throw new TemplatePassthroughManagerCopyError(
|
||||
"copyPassthrough expects an instance of TemplatePassthrough",
|
||||
);
|
||||
}
|
||||
|
||||
let { inputPath } = pass.getPath();
|
||||
|
||||
// TODO https://github.com/11ty/eleventy/issues/2452
|
||||
// De-dupe both the input and output paired together to avoid the case
|
||||
// where an input/output pair has been added via multiple passthrough methods (glob, file suffix, etc)
|
||||
// Probably start with the `filter` callback in recursive-copy but it only passes relative paths
|
||||
// See the note in TemplatePassthrough.js->write()
|
||||
|
||||
// Also note that `recursive-copy` handles repeated overwrite copy to the same destination just fine.
|
||||
// e.g. `for(let j=0, k=1000; j<k; j++) { copy("coolkid.jpg", "_site/coolkid.jpg"); }`
|
||||
|
||||
// Eventually we’ll want to move all of this to use Node’s fs.cp, which is experimental and only on Node 16+
|
||||
|
||||
return pass.write().then(
|
||||
({ size, count, map }) => {
|
||||
for (let src in map) {
|
||||
let dest = map[src];
|
||||
if (this.conflictMap[dest]) {
|
||||
if (src !== this.conflictMap[dest]) {
|
||||
throw new TemplatePassthroughManagerCopyError(
|
||||
`Multiple passthrough copy files are trying to write to the same output file (${dest}). ${src} and ${this.conflictMap[dest]}`,
|
||||
);
|
||||
} else {
|
||||
// Multiple entries from the same source
|
||||
debug(
|
||||
"A passthrough copy entry (%o) caused the same file (%o) to be copied more than once to the output (%o). This is atomically safe but a waste of build resources.",
|
||||
inputPath,
|
||||
src,
|
||||
dest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
debugDev("Adding %o to passthrough copy conflict map, from %o", dest, src);
|
||||
|
||||
this.conflictMap[dest] = src;
|
||||
}
|
||||
|
||||
if (pass.isDryRun) {
|
||||
// We don’t count the skipped files as we need to iterate over them
|
||||
debug(
|
||||
"Skipped %o (either from --dryrun or --incremental or for-free passthrough copy)",
|
||||
inputPath,
|
||||
);
|
||||
} else {
|
||||
if (count) {
|
||||
this.count += count;
|
||||
this.size += size;
|
||||
debug("Copied %o (%d files, %d size)", inputPath, count || 0, size || 0);
|
||||
} else {
|
||||
debug("Skipped copying %o (emulated passthrough copy)", inputPath);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
map,
|
||||
};
|
||||
},
|
||||
function (e) {
|
||||
return Promise.reject(
|
||||
new TemplatePassthroughManagerCopyError(`Having trouble copying '${inputPath}'`, e),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
isPassthroughCopyFile(paths, changedFile) {
|
||||
if (!changedFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// passthrough copy by non-matching engine extension (via templateFormats)
|
||||
for (let path of paths) {
|
||||
if (path === changedFile && !this.extensionMap.hasEngine(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let path of this.getConfigPaths()) {
|
||||
if (TemplatePath.startsWithSubPath(changedFile, path.inputPath)) {
|
||||
return path;
|
||||
}
|
||||
if (changedFile && isGlob(path.inputPath) && isGlobMatch(changedFile, [path.inputPath])) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getAllNormalizedPaths(paths) {
|
||||
if (this.incrementalFile) {
|
||||
let isPassthrough = this.isPassthroughCopyFile(paths, this.incrementalFile);
|
||||
|
||||
if (isPassthrough) {
|
||||
if (isPassthrough.outputPath) {
|
||||
return [this._normalizePaths(this.incrementalFile, isPassthrough.outputPath)];
|
||||
}
|
||||
|
||||
return [this._normalizePaths(this.incrementalFile)];
|
||||
}
|
||||
|
||||
// Fixes https://github.com/11ty/eleventy/issues/2491
|
||||
if (!checkPassthroughCopyBehavior(this.config, this.runMode)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let normalizedPaths = this.getConfigPaths();
|
||||
if (debug.enabled) {
|
||||
for (let path of normalizedPaths) {
|
||||
debug("TemplatePassthrough copying from config: %o", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (paths?.length) {
|
||||
let passthroughPaths = this.getNonTemplatePaths(paths);
|
||||
for (let path of passthroughPaths) {
|
||||
let normalizedPath = this._normalizePaths(path);
|
||||
|
||||
debug(
|
||||
`TemplatePassthrough copying from non-matching file extension: ${normalizedPath.inputPath}`,
|
||||
);
|
||||
|
||||
normalizedPaths.push(normalizedPath);
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedPaths;
|
||||
}
|
||||
|
||||
// keys: output
|
||||
// values: input
|
||||
getAliasesFromPassthroughResults(result) {
|
||||
let entries = {};
|
||||
for (let entry of result) {
|
||||
for (let src in entry.map) {
|
||||
let dest = TemplatePath.stripLeadingSubPath(entry.map[src], this.outputDir);
|
||||
entries["/" + encodeURI(dest)] = src;
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Performance note: these can actually take a fair bit of time, but aren’t a
|
||||
// bottleneck to eleventy. The copies are performed asynchronously and don’t affect eleventy
|
||||
// write times in a significant way.
|
||||
async copyAll(templateExtensionPaths) {
|
||||
debug("TemplatePassthrough copy started.");
|
||||
let normalizedPaths = this.getAllNormalizedPaths(templateExtensionPaths);
|
||||
|
||||
let passthroughs = normalizedPaths.map((path) => {
|
||||
// if incrementalFile is set but it isn’t a passthrough copy, normalizedPaths will be an empty array
|
||||
let isIncremental = !!this.incrementalFile;
|
||||
|
||||
return this.getTemplatePassthroughForPath(path, isIncremental);
|
||||
});
|
||||
|
||||
let promises = passthroughs.map((pass) => this.copyPassthrough(pass));
|
||||
return Promise.all(promises).then(async (results) => {
|
||||
let aliases = this.getAliasesFromPassthroughResults(results);
|
||||
await this.config.events.emit("eleventy.passthrough", {
|
||||
map: aliases,
|
||||
});
|
||||
|
||||
debug(`TemplatePassthrough copy finished. Current count: ${this.count} (size: ${this.size})`);
|
||||
return results;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePassthroughManager;
|
||||
189
node_modules/@11ty/eleventy/src/TemplatePermalink.js
generated
vendored
Normal file
189
node_modules/@11ty/eleventy/src/TemplatePermalink.js
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
import path from "node:path";
|
||||
import normalize from "normalize-path";
|
||||
import { TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
class TemplatePermalink {
|
||||
// `link` with template syntax should have already been rendered in Template.js
|
||||
constructor(link, extraSubdir) {
|
||||
let isLinkAnObject = isPlainObject(link);
|
||||
|
||||
this._isRendered = true;
|
||||
this._writeToFileSystem = true;
|
||||
|
||||
let buildLink;
|
||||
|
||||
if (isLinkAnObject) {
|
||||
if ("build" in link) {
|
||||
buildLink = link.build;
|
||||
}
|
||||
|
||||
// find the first string key
|
||||
for (let key in link) {
|
||||
if (typeof key !== "string") {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
buildLink = link;
|
||||
}
|
||||
|
||||
// permalink: false and permalink: build: false
|
||||
if (typeof buildLink === "boolean") {
|
||||
if (buildLink === false) {
|
||||
this._writeToFileSystem = false;
|
||||
} else {
|
||||
throw new Error(
|
||||
`\`permalink: ${
|
||||
isLinkAnObject ? "build: " : ""
|
||||
}true\` is not a supported feature in Eleventy. Did you mean \`permalink: ${
|
||||
isLinkAnObject ? "build: " : ""
|
||||
}false\`?`,
|
||||
);
|
||||
}
|
||||
} else if (buildLink) {
|
||||
this.buildLink = buildLink;
|
||||
}
|
||||
|
||||
if (isLinkAnObject) {
|
||||
// default if permalink is an Object but does not have a `build` prop
|
||||
if (!("build" in link)) {
|
||||
this._writeToFileSystem = false;
|
||||
this._isRendered = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.extraPaginationSubdir = extraSubdir || "";
|
||||
}
|
||||
|
||||
setUrlTransforms(transforms) {
|
||||
this._urlTransforms = transforms;
|
||||
}
|
||||
|
||||
get urlTransforms() {
|
||||
return this._urlTransforms || [];
|
||||
}
|
||||
|
||||
_addDefaultLinkFilename(link) {
|
||||
return link + (link.slice(-1) === "/" ? "index.html" : "");
|
||||
}
|
||||
|
||||
toOutputPath() {
|
||||
if (!this.buildLink) {
|
||||
// empty or false
|
||||
return false;
|
||||
}
|
||||
|
||||
let cleanLink = this._addDefaultLinkFilename(this.buildLink);
|
||||
let parsed = path.parse(cleanLink);
|
||||
|
||||
return TemplatePath.join(parsed.dir, this.extraPaginationSubdir, parsed.base);
|
||||
}
|
||||
|
||||
// Used in url transforms feature
|
||||
static getUrlStem(original) {
|
||||
let subject = original;
|
||||
if (original.endsWith(".html")) {
|
||||
subject = original.slice(0, -1 * ".html".length);
|
||||
}
|
||||
return TemplatePermalink.normalizePathToUrl(subject);
|
||||
}
|
||||
|
||||
static normalizePathToUrl(original) {
|
||||
let compare = original || "";
|
||||
|
||||
let needleHtml = "/index.html";
|
||||
let needleBareTrailingSlash = "/index/";
|
||||
let needleBare = "/index";
|
||||
if (compare.endsWith(needleHtml)) {
|
||||
return compare.slice(0, compare.length - needleHtml.length) + "/";
|
||||
} else if (compare.endsWith(needleBareTrailingSlash)) {
|
||||
return compare.slice(0, compare.length - needleBareTrailingSlash.length) + "/";
|
||||
} else if (compare.endsWith(needleBare)) {
|
||||
return compare.slice(0, compare.length - needleBare.length) + "/";
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
// This method is used to generate the `page.url` variable.
|
||||
|
||||
// remove all index.html’s from links
|
||||
// index.html becomes /
|
||||
// test/index.html becomes test/
|
||||
toHref() {
|
||||
if (!this.buildLink) {
|
||||
// empty or false
|
||||
return false;
|
||||
}
|
||||
|
||||
let transformedLink = this.toOutputPath();
|
||||
let original = (transformedLink.charAt(0) !== "/" ? "/" : "") + transformedLink;
|
||||
|
||||
let normalized = TemplatePermalink.normalizePathToUrl(original) || "";
|
||||
for (let transform of this.urlTransforms) {
|
||||
original =
|
||||
transform({
|
||||
url: normalized,
|
||||
urlStem: TemplatePermalink.getUrlStem(original),
|
||||
}) ?? original;
|
||||
}
|
||||
|
||||
return TemplatePermalink.normalizePathToUrl(original);
|
||||
}
|
||||
|
||||
toPath(outputDir) {
|
||||
if (!this.buildLink) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let uri = this.toOutputPath();
|
||||
|
||||
if (uri === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return normalize(outputDir + "/" + uri);
|
||||
}
|
||||
|
||||
toPathFromRoot() {
|
||||
if (!this.buildLink) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let uri = this.toOutputPath();
|
||||
|
||||
if (uri === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return normalize(uri);
|
||||
}
|
||||
|
||||
static _hasDuplicateFolder(dir, base) {
|
||||
let folders = dir.split("/");
|
||||
if (!folders[folders.length - 1]) {
|
||||
folders.pop();
|
||||
}
|
||||
return folders[folders.length - 1] === base;
|
||||
}
|
||||
|
||||
static generate(dir, filenameNoExt, extraSubdir, fileExtension = "html") {
|
||||
let path;
|
||||
if (fileExtension === "html") {
|
||||
let hasDupeFolder = TemplatePermalink._hasDuplicateFolder(dir, filenameNoExt);
|
||||
|
||||
path =
|
||||
(dir ? dir + "/" : "") +
|
||||
(filenameNoExt !== "index" && !hasDupeFolder ? filenameNoExt + "/" : "") +
|
||||
"index" +
|
||||
".html";
|
||||
} else {
|
||||
path = (dir ? dir + "/" : "") + filenameNoExt + "." + fileExtension;
|
||||
}
|
||||
|
||||
return new TemplatePermalink(path, extraSubdir);
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePermalink;
|
||||
294
node_modules/@11ty/eleventy/src/TemplateRender.js
generated
vendored
Normal file
294
node_modules/@11ty/eleventy/src/TemplateRender.js
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import TemplateEngineManager from "./Engines/TemplateEngineManager.js";
|
||||
import CustomEngine from "./Engines/Custom.js";
|
||||
|
||||
// import debugUtil from "debug";
|
||||
// const debug = debugUtil("Eleventy:TemplateRender");
|
||||
|
||||
class TemplateRenderConfigError extends EleventyBaseError {}
|
||||
class TemplateRenderUnknownEngineError extends EleventyBaseError {}
|
||||
|
||||
// works with full path names or short engine name
|
||||
class TemplateRender {
|
||||
constructor(tmplPath, config) {
|
||||
if (!tmplPath) {
|
||||
throw new Error(`TemplateRender requires a tmplPath argument, instead of ${tmplPath}`);
|
||||
}
|
||||
if (!config) {
|
||||
throw new TemplateRenderConfigError("Missing `config` argument.");
|
||||
}
|
||||
if (config.constructor.name === "TemplateConfig") {
|
||||
this.eleventyConfig = config;
|
||||
this.config = config.getConfig();
|
||||
} else {
|
||||
throw new Error("Third argument to TemplateRender must be a TemplateConfig instance.");
|
||||
}
|
||||
|
||||
this.engineNameOrPath = tmplPath;
|
||||
|
||||
this.parseMarkdownWith = this.config.markdownTemplateEngine;
|
||||
this.parseHtmlWith = this.config.htmlTemplateEngine;
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get includesDir() {
|
||||
return this.dirs.includes;
|
||||
}
|
||||
|
||||
/* Backwards compat */
|
||||
getIncludesDir() {
|
||||
return this.includesDir;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
set config(config) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats([]);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
async getEngineByName(name) {
|
||||
let engine = await this.extensionMap.engineManager.getEngine(name, this.extensionMap);
|
||||
engine.eleventyConfig = this.eleventyConfig;
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
// Runs once per template
|
||||
async init(engineNameOrPath) {
|
||||
let name = engineNameOrPath || this.engineNameOrPath;
|
||||
this.extensionMap.config = this.eleventyConfig;
|
||||
|
||||
let extensionEntry = this.extensionMap.getExtensionEntry(name);
|
||||
let engineName = extensionEntry?.aliasKey || extensionEntry?.key;
|
||||
if (TemplateEngineManager.isSimpleAlias(extensionEntry)) {
|
||||
engineName = extensionEntry?.key;
|
||||
}
|
||||
this._engineName = engineName;
|
||||
|
||||
if (!extensionEntry || !this._engineName) {
|
||||
throw new TemplateRenderUnknownEngineError(
|
||||
`Unknown engine for ${name} (supported extensions: ${this.extensionMap.getReadableFileExtensions()})`,
|
||||
);
|
||||
}
|
||||
|
||||
this._engine = await this.getEngineByName(this._engineName);
|
||||
|
||||
if (this.useMarkdown === undefined) {
|
||||
this.setUseMarkdown(this._engineName === "md");
|
||||
}
|
||||
}
|
||||
|
||||
get engineName() {
|
||||
if (!this._engineName) {
|
||||
throw new Error("TemplateRender needs a call to the init() method.");
|
||||
}
|
||||
return this._engineName;
|
||||
}
|
||||
|
||||
get engine() {
|
||||
if (!this._engine) {
|
||||
throw new Error("TemplateRender needs a call to the init() method.");
|
||||
}
|
||||
return this._engine;
|
||||
}
|
||||
|
||||
static parseEngineOverrides(engineName) {
|
||||
if (typeof (engineName || "") !== "string") {
|
||||
throw new Error("Expected String passed to parseEngineOverrides. Received: " + engineName);
|
||||
}
|
||||
|
||||
let overlappingEngineWarningCount = 0;
|
||||
let engines = [];
|
||||
let uniqueLookup = {};
|
||||
let usingMarkdown = false;
|
||||
(engineName || "")
|
||||
.split(",")
|
||||
.map((name) => {
|
||||
return name.toLowerCase().trim();
|
||||
})
|
||||
.forEach((name) => {
|
||||
// html is assumed (treated as plaintext by the system)
|
||||
if (!name || name === "html") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === "md") {
|
||||
usingMarkdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!uniqueLookup[name]) {
|
||||
engines.push(name);
|
||||
uniqueLookup[name] = true;
|
||||
|
||||
// we already short circuit md and html types above
|
||||
overlappingEngineWarningCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (overlappingEngineWarningCount > 1) {
|
||||
throw new Error(
|
||||
`Don’t mix multiple templating engines in your front matter overrides (exceptions for HTML and Markdown). You used: ${engineName}`,
|
||||
);
|
||||
}
|
||||
|
||||
// markdown should always be first
|
||||
if (usingMarkdown) {
|
||||
engines.unshift("md");
|
||||
}
|
||||
|
||||
return engines;
|
||||
}
|
||||
|
||||
// used for error logging and console output.
|
||||
getReadableEnginesList() {
|
||||
return this.getReadableEnginesListDifferingFromFileExtension() || this.engineName;
|
||||
}
|
||||
|
||||
getReadableEnginesListDifferingFromFileExtension() {
|
||||
let keyFromFilename = this.extensionMap.getKey(this.engineNameOrPath);
|
||||
if (this.engine instanceof CustomEngine) {
|
||||
if (
|
||||
this.engine.entry &&
|
||||
this.engine.entry.name &&
|
||||
keyFromFilename !== this.engine.entry.name
|
||||
) {
|
||||
return this.engine.entry.name;
|
||||
} else {
|
||||
// We don’t have a name for it so we return nothing so we don’t misreport (per #2386)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.engineName === "md" && this.useMarkdown && this.parseMarkdownWith) {
|
||||
return this.parseMarkdownWith;
|
||||
}
|
||||
if (this.engineName === "html" && this.parseHtmlWith) {
|
||||
return this.parseHtmlWith;
|
||||
}
|
||||
|
||||
// templateEngineOverride in play and template language differs from file extension
|
||||
if (keyFromFilename !== this.engineName) {
|
||||
return this.engineName;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO templateEngineOverride
|
||||
getPreprocessorEngine() {
|
||||
if (this.engineName === "md" && this.parseMarkdownWith) {
|
||||
return this.parseMarkdownWith;
|
||||
}
|
||||
if (this.engineName === "html" && this.parseHtmlWith) {
|
||||
return this.parseHtmlWith;
|
||||
}
|
||||
return this.extensionMap.getKey(this.engineNameOrPath);
|
||||
}
|
||||
|
||||
// We pass in templateEngineOverride here because it isn’t yet applied to templateRender
|
||||
getEnginesList(engineOverride) {
|
||||
if (engineOverride) {
|
||||
let engines = TemplateRender.parseEngineOverrides(engineOverride).reverse();
|
||||
return engines.join(",");
|
||||
}
|
||||
|
||||
if (this.engineName === "md" && this.useMarkdown && this.parseMarkdownWith) {
|
||||
return `${this.parseMarkdownWith},md`;
|
||||
}
|
||||
if (this.engineName === "html" && this.parseHtmlWith) {
|
||||
return this.parseHtmlWith;
|
||||
}
|
||||
|
||||
// templateEngineOverride in play
|
||||
return this.extensionMap.getKey(this.engineNameOrPath);
|
||||
}
|
||||
|
||||
async setEngineOverride(engineName, bypassMarkdown) {
|
||||
let engines = TemplateRender.parseEngineOverrides(engineName);
|
||||
|
||||
// when overriding, Template Engines with HTML will instead use the Template Engine as primary and output HTML
|
||||
// So any HTML engine usage here will never use a preprocessor templating engine.
|
||||
this.setHtmlEngine(false);
|
||||
|
||||
if (!engines.length) {
|
||||
await this.init("html");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.init(engines[0]);
|
||||
|
||||
let usingMarkdown = engines[0] === "md" && !bypassMarkdown;
|
||||
|
||||
this.setUseMarkdown(usingMarkdown);
|
||||
|
||||
if (usingMarkdown) {
|
||||
// false means only parse markdown and not with a preprocessor template engine
|
||||
this.setMarkdownEngine(engines.length > 1 ? engines[1] : false);
|
||||
}
|
||||
}
|
||||
|
||||
getEngineName() {
|
||||
return this.engineName;
|
||||
}
|
||||
|
||||
isEngine(engine) {
|
||||
return this.engineName === engine;
|
||||
}
|
||||
|
||||
setUseMarkdown(useMarkdown) {
|
||||
this.useMarkdown = !!useMarkdown;
|
||||
}
|
||||
|
||||
// this is only called for templateEngineOverride
|
||||
setMarkdownEngine(markdownEngine) {
|
||||
this.parseMarkdownWith = markdownEngine;
|
||||
}
|
||||
|
||||
// this is only called for templateEngineOverride
|
||||
setHtmlEngine(htmlEngineName) {
|
||||
this.parseHtmlWith = htmlEngineName;
|
||||
}
|
||||
|
||||
async _testRender(str, data) {
|
||||
return this.engine._testRender(str, data);
|
||||
}
|
||||
|
||||
async getCompiledTemplate(str) {
|
||||
// TODO refactor better, move into TemplateEngine logic
|
||||
if (this.engineName === "md") {
|
||||
return this.engine.compile(
|
||||
str,
|
||||
this.engineNameOrPath,
|
||||
this.parseMarkdownWith,
|
||||
!this.useMarkdown,
|
||||
);
|
||||
} else if (this.engineName === "html") {
|
||||
return this.engine.compile(str, this.engineNameOrPath, this.parseHtmlWith);
|
||||
} else {
|
||||
return this.engine.compile(str, this.engineNameOrPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateRender;
|
||||
518
node_modules/@11ty/eleventy/src/TemplateWriter.js
generated
vendored
Executable file
518
node_modules/@11ty/eleventy/src/TemplateWriter.js
generated
vendored
Executable file
@@ -0,0 +1,518 @@
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import Template from "./Template.js";
|
||||
import TemplateMap from "./TemplateMap.js";
|
||||
import EleventyFiles from "./EleventyFiles.js";
|
||||
import EleventyExtensionMap from "./EleventyExtensionMap.js";
|
||||
import EleventyBaseError from "./Errors/EleventyBaseError.js";
|
||||
import { EleventyErrorHandler } from "./Errors/EleventyErrorHandler.js";
|
||||
import EleventyErrorUtil from "./Errors/EleventyErrorUtil.js";
|
||||
import FileSystemSearch from "./FileSystemSearch.js";
|
||||
import ConsoleLogger from "./Util/ConsoleLogger.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:TemplateWriter");
|
||||
|
||||
class TemplateWriterMissingConfigArgError extends EleventyBaseError {}
|
||||
class EleventyPassthroughCopyError extends EleventyBaseError {}
|
||||
class EleventyTemplateError extends EleventyBaseError {}
|
||||
|
||||
class TemplateWriter {
|
||||
#eleventyFiles;
|
||||
|
||||
constructor(
|
||||
templateFormats, // TODO remove this, see `get eleventyFiles` first
|
||||
templateData,
|
||||
eleventyConfig,
|
||||
) {
|
||||
if (!eleventyConfig) {
|
||||
throw new TemplateWriterMissingConfigArgError("Missing config argument.");
|
||||
}
|
||||
this.eleventyConfig = eleventyConfig;
|
||||
this.config = eleventyConfig.getConfig();
|
||||
this.userConfig = eleventyConfig.userConfig;
|
||||
|
||||
this.templateFormats = templateFormats;
|
||||
|
||||
this.templateData = templateData;
|
||||
this.isVerbose = true;
|
||||
this.isDryRun = false;
|
||||
this.writeCount = 0;
|
||||
this.renderCount = 0;
|
||||
this.skippedCount = 0;
|
||||
this.isRunInitialBuild = true;
|
||||
|
||||
this._templatePathCache = new Map();
|
||||
}
|
||||
|
||||
get dirs() {
|
||||
return this.eleventyConfig.directories;
|
||||
}
|
||||
|
||||
get inputDir() {
|
||||
return this.dirs.input;
|
||||
}
|
||||
|
||||
get outputDir() {
|
||||
return this.dirs.output;
|
||||
}
|
||||
|
||||
get templateFormats() {
|
||||
return this._templateFormats;
|
||||
}
|
||||
|
||||
set templateFormats(value) {
|
||||
this._templateFormats = value;
|
||||
}
|
||||
|
||||
/* Getter for error handler */
|
||||
get errorHandler() {
|
||||
if (!this._errorHandler) {
|
||||
this._errorHandler = new EleventyErrorHandler();
|
||||
this._errorHandler.isVerbose = this.verboseMode;
|
||||
this._errorHandler.logger = this.logger;
|
||||
}
|
||||
|
||||
return this._errorHandler;
|
||||
}
|
||||
|
||||
/* Getter for Logger */
|
||||
get logger() {
|
||||
if (!this._logger) {
|
||||
this._logger = new ConsoleLogger();
|
||||
this._logger.isVerbose = this.verboseMode;
|
||||
}
|
||||
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
/* Setter for Logger */
|
||||
set logger(logger) {
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/* For testing */
|
||||
overrideConfig(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
restart() {
|
||||
this.writeCount = 0;
|
||||
this.renderCount = 0;
|
||||
this.skippedCount = 0;
|
||||
}
|
||||
|
||||
set extensionMap(extensionMap) {
|
||||
this._extensionMap = extensionMap;
|
||||
}
|
||||
|
||||
get extensionMap() {
|
||||
if (!this._extensionMap) {
|
||||
this._extensionMap = new EleventyExtensionMap(this.eleventyConfig);
|
||||
this._extensionMap.setFormats(this.templateFormats);
|
||||
}
|
||||
return this._extensionMap;
|
||||
}
|
||||
|
||||
setEleventyFiles(eleventyFiles) {
|
||||
this.#eleventyFiles = eleventyFiles;
|
||||
}
|
||||
|
||||
set eleventyFiles(eleventyFiles) {
|
||||
this.#eleventyFiles = eleventyFiles;
|
||||
}
|
||||
|
||||
get eleventyFiles() {
|
||||
// usually Eleventy.js will setEleventyFiles with the EleventyFiles manager
|
||||
if (!this.#eleventyFiles) {
|
||||
// if not, we can create one (used only by tests)
|
||||
this.#eleventyFiles = new EleventyFiles(this.templateFormats, this.eleventyConfig);
|
||||
|
||||
this.#eleventyFiles.setFileSystemSearch(new FileSystemSearch());
|
||||
this.#eleventyFiles.init();
|
||||
}
|
||||
|
||||
return this.#eleventyFiles;
|
||||
}
|
||||
|
||||
async _getAllPaths() {
|
||||
// this is now cached upstream by FileSystemSearch
|
||||
return this.eleventyFiles.getFiles();
|
||||
}
|
||||
|
||||
_createTemplate(path, to = "fs") {
|
||||
let tmpl = this._templatePathCache.get(path);
|
||||
let wasCached = false;
|
||||
if (tmpl) {
|
||||
wasCached = true;
|
||||
// Update config for https://github.com/11ty/eleventy/issues/3468
|
||||
tmpl.eleventyConfig = this.eleventyConfig;
|
||||
|
||||
// TODO reset other constructor things here like inputDir/outputDir/extensionMap/
|
||||
tmpl.setTemplateData(this.templateData);
|
||||
} else {
|
||||
tmpl = new Template(path, this.templateData, this.extensionMap, this.eleventyConfig);
|
||||
|
||||
tmpl.setOutputFormat(to);
|
||||
|
||||
tmpl.logger = this.logger;
|
||||
this._templatePathCache.set(path, tmpl);
|
||||
|
||||
/*
|
||||
* Sample filter: arg str, return pretty HTML string
|
||||
* function(str) {
|
||||
* return pretty(str, { ocd: true });
|
||||
* }
|
||||
*/
|
||||
tmpl.setTransforms(this.config.transforms);
|
||||
|
||||
for (let linterName in this.config.linters) {
|
||||
let linter = this.config.linters[linterName];
|
||||
if (typeof linter === "function") {
|
||||
tmpl.addLinter(linter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmpl.setDryRun(this.isDryRun);
|
||||
tmpl.setIsVerbose(this.isVerbose);
|
||||
tmpl.reset();
|
||||
|
||||
return {
|
||||
template: tmpl,
|
||||
wasCached,
|
||||
};
|
||||
}
|
||||
|
||||
// incrementalFileShape is `template` or `copy` (for passthrough file copy)
|
||||
async _addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to = "fs") {
|
||||
// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run
|
||||
let ignoreInitialBuild = !this.isRunInitialBuild;
|
||||
let secondOrderRelevantLookup = {};
|
||||
let templates = [];
|
||||
|
||||
let promises = [];
|
||||
for (let path of paths) {
|
||||
let { template: tmpl } = this._createTemplate(path, to);
|
||||
|
||||
// Note: removed a fix here to fetch missing templateRender instances
|
||||
// that was tested as no longer needed (Issue #3170).
|
||||
|
||||
templates.push(tmpl);
|
||||
|
||||
// This must happen before data is generated for the incremental file only
|
||||
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
|
||||
tmpl.resetCaches();
|
||||
}
|
||||
|
||||
// IMPORTANT: This is where the data is first generated for the template
|
||||
promises.push(this.templateMap.add(tmpl));
|
||||
}
|
||||
|
||||
// Important to set up template dependency relationships first
|
||||
await Promise.all(promises);
|
||||
|
||||
// Delete incremental file from the dependency graph so we get fresh entries!
|
||||
// This _must_ happen before any additions, the other ones are in Custom.js and GlobalDependencyMap.js (from the eleventy.layouts Event)
|
||||
this.config.uses.resetNode(this.incrementalFile);
|
||||
|
||||
// write new template relationships to the global dependency graph for next time
|
||||
this.templateMap.addAllToGlobalDependencyGraph();
|
||||
|
||||
// Always disable render for --ignore-initial
|
||||
if (ignoreInitialBuild) {
|
||||
for (let tmpl of templates) {
|
||||
tmpl.setRenderableOverride(false); // disable render
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tmpl of templates) {
|
||||
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
|
||||
tmpl.setRenderableOverride(undefined); // unset, probably render
|
||||
} else if (
|
||||
tmpl.isFileRelevantToThisTemplate(this.incrementalFile, {
|
||||
isFullTemplate: incrementalFileShape === "template",
|
||||
})
|
||||
) {
|
||||
// changed file is used by template
|
||||
// template uses the changed file
|
||||
tmpl.setRenderableOverride(undefined); // unset, probably render
|
||||
secondOrderRelevantLookup[tmpl.inputPath] = true;
|
||||
} else if (this.config.uses.isFileUsedBy(this.incrementalFile, tmpl.inputPath)) {
|
||||
// changed file uses this template
|
||||
tmpl.setRenderableOverride("optional");
|
||||
} else {
|
||||
// For incremental, always disable render on irrelevant templates
|
||||
tmpl.setRenderableOverride(false); // disable render
|
||||
}
|
||||
}
|
||||
|
||||
let secondOrderRelevantArray = this.config.uses
|
||||
.getTemplatesRelevantToTemplateList(Object.keys(secondOrderRelevantLookup))
|
||||
.map((entry) => TemplatePath.addLeadingDotSlash(entry));
|
||||
let secondOrderTemplates = Object.fromEntries(
|
||||
Object.entries(secondOrderRelevantArray).map(([index, value]) => [value, true]),
|
||||
);
|
||||
|
||||
for (let tmpl of templates) {
|
||||
// second order templates must also be rendered if not yet already rendered at least once and available in cache.
|
||||
if (secondOrderTemplates[tmpl.inputPath]) {
|
||||
if (tmpl.isRenderableDisabled()) {
|
||||
tmpl.setRenderableOverride("optional");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Order of templates does not matter here, they’re reordered later based on dependencies in TemplateMap.js
|
||||
for (let tmpl of templates) {
|
||||
if (incrementalFileShape === "template" && tmpl.inputPath === this.incrementalFile) {
|
||||
// Cache is reset above (to invalidate data cache at the right time)
|
||||
tmpl.setDryRunViaIncremental(false);
|
||||
} else if (!tmpl.isRenderableDisabled() && !tmpl.isRenderableOptional()) {
|
||||
// Related to the template but not the template (reset the render cache, not the read cache)
|
||||
tmpl.resetCaches({
|
||||
data: true,
|
||||
render: true,
|
||||
});
|
||||
|
||||
tmpl.setDryRunViaIncremental(false);
|
||||
} else {
|
||||
// During incremental we only reset the data cache for non-matching templates, see https://github.com/11ty/eleventy/issues/2710
|
||||
// Keep caches for read/render
|
||||
tmpl.resetCaches({
|
||||
data: true,
|
||||
});
|
||||
|
||||
tmpl.setDryRunViaIncremental(true);
|
||||
|
||||
this.skippedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addToTemplateMapFullBuild(paths, to = "fs") {
|
||||
if (this.incrementalFile) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let ignoreInitialBuild = !this.isRunInitialBuild;
|
||||
let promises = [];
|
||||
for (let path of paths) {
|
||||
let { template: tmpl, wasCached } = this._createTemplate(path, to);
|
||||
|
||||
// Render overrides are only used when `--ignore-initial` is in play and an initial build is not run
|
||||
if (ignoreInitialBuild) {
|
||||
tmpl.setRenderableOverride(false); // disable render
|
||||
} else {
|
||||
tmpl.setRenderableOverride(undefined); // unset, render
|
||||
}
|
||||
|
||||
if (wasCached) {
|
||||
tmpl.resetCaches();
|
||||
}
|
||||
|
||||
// IMPORTANT: This is where the data is first generated for the template
|
||||
promises.push(this.templateMap.add(tmpl));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async _addToTemplateMap(paths, to = "fs") {
|
||||
let incrementalFileShape = this.eleventyFiles.getFileShape(paths, this.incrementalFile);
|
||||
|
||||
// Filter out passthrough copy files
|
||||
paths = paths.filter((path) => {
|
||||
if (!this.extensionMap.hasEngine(path)) {
|
||||
return false;
|
||||
}
|
||||
if (incrementalFileShape === "copy") {
|
||||
this.skippedCount++;
|
||||
// Filters out templates if the incremental file is a passthrough copy file
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Full Build
|
||||
if (!this.incrementalFile) {
|
||||
let ret = await this._addToTemplateMapFullBuild(paths, to);
|
||||
|
||||
// write new template relationships to the global dependency graph for next time
|
||||
this.templateMap.addAllToGlobalDependencyGraph();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Top level async to get at the promises returned.
|
||||
return await this._addToTemplateMapIncrementalBuild(incrementalFileShape, paths, to);
|
||||
}
|
||||
|
||||
async _createTemplateMap(paths, to) {
|
||||
this.templateMap = new TemplateMap(this.eleventyConfig);
|
||||
|
||||
await this._addToTemplateMap(paths, to);
|
||||
await this.templateMap.cache();
|
||||
|
||||
return this.templateMap;
|
||||
}
|
||||
|
||||
async _generateTemplate(mapEntry, to) {
|
||||
let tmpl = mapEntry.template;
|
||||
|
||||
return tmpl.generateMapEntry(mapEntry, to).then((pages) => {
|
||||
this.renderCount += tmpl.getRenderCount();
|
||||
this.writeCount += tmpl.getWriteCount();
|
||||
return pages;
|
||||
});
|
||||
}
|
||||
|
||||
async writePassthroughCopy(templateExtensionPaths) {
|
||||
let passthroughManager = this.eleventyFiles.getPassthroughManager();
|
||||
passthroughManager.setIncrementalFile(this.incrementalFile);
|
||||
|
||||
return passthroughManager.copyAll(templateExtensionPaths).catch((e) => {
|
||||
this.errorHandler.warn(e, "Error with passthrough copy");
|
||||
return Promise.reject(new EleventyPassthroughCopyError("Having trouble copying", e));
|
||||
});
|
||||
}
|
||||
|
||||
async generateTemplates(paths, to = "fs") {
|
||||
let promises = [];
|
||||
|
||||
// console.time("generateTemplates:_createTemplateMap");
|
||||
// TODO optimize await here
|
||||
await this._createTemplateMap(paths, to);
|
||||
// console.timeEnd("generateTemplates:_createTemplateMap");
|
||||
debug("Template map created.");
|
||||
|
||||
let usedTemplateContentTooEarlyMap = [];
|
||||
for (let mapEntry of this.templateMap.getMap()) {
|
||||
promises.push(
|
||||
this._generateTemplate(mapEntry, to).catch(function (e) {
|
||||
// Premature templateContent in layout render, this also happens in
|
||||
// TemplateMap.populateContentDataInMap for non-layout content
|
||||
if (EleventyErrorUtil.isPrematureTemplateContentError(e)) {
|
||||
usedTemplateContentTooEarlyMap.push(mapEntry);
|
||||
} else {
|
||||
let outputPaths = `"${mapEntry._pages.map((page) => page.outputPath).join(`", "`)}"`;
|
||||
return Promise.reject(
|
||||
new EleventyTemplateError(
|
||||
`Having trouble writing to ${outputPaths} from "${mapEntry.inputPath}"`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
for (let mapEntry of usedTemplateContentTooEarlyMap) {
|
||||
promises.push(
|
||||
this._generateTemplate(mapEntry, to).catch(function (e) {
|
||||
return Promise.reject(
|
||||
new EleventyTemplateError(
|
||||
`Having trouble writing to (second pass) "${mapEntry.outputPath}" from "${mapEntry.inputPath}"`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return promises;
|
||||
}
|
||||
|
||||
async write() {
|
||||
let paths = await this._getAllPaths();
|
||||
let promises = [];
|
||||
|
||||
// The ordering here is important to destructuring in Eleventy->_watch
|
||||
promises.push(this.writePassthroughCopy(paths));
|
||||
|
||||
promises.push(...(await this.generateTemplates(paths)));
|
||||
|
||||
return Promise.all(promises).then(
|
||||
([passthroughCopyResults, ...templateResults]) => {
|
||||
return {
|
||||
passthroughCopy: passthroughCopyResults,
|
||||
// New in 3.0: flatten and filter out falsy templates
|
||||
templates: templateResults.flat().filter(Boolean),
|
||||
};
|
||||
},
|
||||
(e) => {
|
||||
return Promise.reject(e);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Passthrough copy not supported in JSON output.
|
||||
// --incremental not supported in JSON output.
|
||||
async getJSON(to = "json") {
|
||||
let paths = await this._getAllPaths();
|
||||
let promises = await this.generateTemplates(paths, to);
|
||||
|
||||
return Promise.all(promises).then(
|
||||
(templateResults) => {
|
||||
return {
|
||||
// New in 3.0: flatten and filter out falsy templates
|
||||
templates: templateResults.flat().filter(Boolean),
|
||||
};
|
||||
},
|
||||
(e) => {
|
||||
return Promise.reject(e);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setVerboseOutput(isVerbose) {
|
||||
this.isVerbose = isVerbose;
|
||||
this.errorHandler.isVerbose = isVerbose;
|
||||
}
|
||||
|
||||
setDryRun(isDryRun) {
|
||||
this.isDryRun = !!isDryRun;
|
||||
|
||||
this.eleventyFiles.getPassthroughManager().setDryRun(this.isDryRun);
|
||||
}
|
||||
|
||||
setRunInitialBuild(runInitialBuild) {
|
||||
this.isRunInitialBuild = runInitialBuild;
|
||||
}
|
||||
setIncrementalBuild(isIncremental) {
|
||||
this.isIncremental = isIncremental;
|
||||
}
|
||||
setIncrementalFile(incrementalFile) {
|
||||
this.incrementalFile = incrementalFile;
|
||||
}
|
||||
resetIncrementalFile() {
|
||||
this.incrementalFile = null;
|
||||
}
|
||||
|
||||
getCopyCount() {
|
||||
return this.eleventyFiles.getPassthroughManager().getCopyCount();
|
||||
}
|
||||
|
||||
getCopySize() {
|
||||
return this.eleventyFiles.getPassthroughManager().getCopySize();
|
||||
}
|
||||
|
||||
getRenderCount() {
|
||||
return this.renderCount;
|
||||
}
|
||||
|
||||
getWriteCount() {
|
||||
return this.writeCount;
|
||||
}
|
||||
|
||||
getSkippedCount() {
|
||||
return this.skippedCount;
|
||||
}
|
||||
|
||||
get caches() {
|
||||
return ["_templatePathCache"];
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateWriter;
|
||||
1292
node_modules/@11ty/eleventy/src/UserConfig.js
generated
vendored
Normal file
1292
node_modules/@11ty/eleventy/src/UserConfig.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
75
node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js
generated
vendored
Normal file
75
node_modules/@11ty/eleventy/src/Util/AsyncEventEmitter.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
/**
|
||||
* This class emits events asynchronously.
|
||||
* It can be used for time measurements during a build.
|
||||
*/
|
||||
class AsyncEventEmitter extends EventEmitter {
|
||||
#handlerMode = "parallel";
|
||||
|
||||
// TypeScript slop
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - The event name to emit.
|
||||
* @param {...*} args - Additional arguments that get passed to listeners.
|
||||
* @returns {Promise} - Promise resolves once all listeners were invoked
|
||||
*/
|
||||
/** @ts-expect-error */
|
||||
async emit(type, ...args) {
|
||||
let listeners = this.listeners(type);
|
||||
if (listeners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.#handlerMode == "sequential") {
|
||||
const result = [];
|
||||
for (const listener of listeners) {
|
||||
const returnValue = await listener.apply(this, args);
|
||||
result.push(returnValue);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return Promise.all(
|
||||
listeners.map((listener) => {
|
||||
return listener.apply(this, args);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type - The event name to emit.
|
||||
* @param {...*} args - Additional lazy-executed function arguments that get passed to listeners.
|
||||
* @returns {Promise} - Promise resolves once all listeners were invoked
|
||||
*/
|
||||
async emitLazy(type, ...args) {
|
||||
let listeners = this.listeners(type);
|
||||
if (listeners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let argsMap = [];
|
||||
for (let arg of args) {
|
||||
if (typeof arg === "function") {
|
||||
let r = arg();
|
||||
if (r instanceof Promise) {
|
||||
r = await r;
|
||||
}
|
||||
argsMap.push(r);
|
||||
} else {
|
||||
argsMap.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return this.emit.call(this, type, ...argsMap);
|
||||
}
|
||||
|
||||
setHandlerMode(mode) {
|
||||
this.#handlerMode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
export default AsyncEventEmitter;
|
||||
55
node_modules/@11ty/eleventy/src/Util/Compatibility.js
generated
vendored
Normal file
55
node_modules/@11ty/eleventy/src/Util/Compatibility.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import semver from "semver";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { getEleventyPackageJson, getWorkingProjectPackageJson } from "./ImportJsonSync.js";
|
||||
|
||||
const pkg = getEleventyPackageJson();
|
||||
const debug = debugUtil("Eleventy:Compatibility");
|
||||
|
||||
// Used in user config versionCheck method.
|
||||
class Compatibility {
|
||||
static NORMALIZE_PRERELEASE_REGEX = /-canary\b/g;
|
||||
|
||||
constructor(compatibleRange) {
|
||||
this.compatibleRange = Compatibility.getCompatibilityValue(compatibleRange);
|
||||
}
|
||||
|
||||
static normalizeIdentifier(identifier) {
|
||||
return identifier.replace(Compatibility.NORMALIZE_PRERELEASE_REGEX, "-alpha");
|
||||
}
|
||||
|
||||
static getCompatibilityValue(compatibleRange) {
|
||||
if (compatibleRange) {
|
||||
return compatibleRange;
|
||||
}
|
||||
|
||||
try {
|
||||
// fetch from project’s package.json
|
||||
let projectPackageJson = getWorkingProjectPackageJson();
|
||||
return projectPackageJson["11ty"]?.compatibility;
|
||||
} catch (e) {
|
||||
debug("Could not find a project package.json for compatibility version check: %O", e);
|
||||
return; // do nothing, no compatibility information to check
|
||||
}
|
||||
}
|
||||
|
||||
isCompatible() {
|
||||
return Compatibility.satisfies(pkg.version, this.compatibleRange);
|
||||
}
|
||||
|
||||
static satisfies(version, compatibleRange) {
|
||||
return semver.satisfies(
|
||||
Compatibility.normalizeIdentifier(version),
|
||||
Compatibility.normalizeIdentifier(compatibleRange),
|
||||
{
|
||||
includePrerelease: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getErrorMessage() {
|
||||
return `We found Eleventy version '${pkg.version}' which does not meet the required version range: '${this.compatibleRange}'. Use \`npm install @11ty/eleventy\` to upgrade your local project to the latest Eleventy version (or \`npm install @11ty/eleventy -g\` to upgrade the globally installed version).`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Compatibility;
|
||||
126
node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js
generated
vendored
Normal file
126
node_modules/@11ty/eleventy/src/Util/ConsoleLogger.js
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Readable } from "node:stream";
|
||||
import chalk from "kleur";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:Logger");
|
||||
|
||||
/**
|
||||
* Logger implementation that logs to STDOUT.
|
||||
* @typedef {'error'|'log'|'warn'|'info'} LogType
|
||||
*/
|
||||
class ConsoleLogger {
|
||||
/** @type {boolean} */
|
||||
#isVerbose = true;
|
||||
/** @type {boolean} */
|
||||
#isChalkEnabled = true;
|
||||
/** @type {object|boolean|undefined} */
|
||||
#logger;
|
||||
|
||||
constructor() {
|
||||
this.outputStream = new Readable({
|
||||
read() {},
|
||||
});
|
||||
}
|
||||
|
||||
get isVerbose() {
|
||||
return this.#isVerbose;
|
||||
}
|
||||
|
||||
set isVerbose(verbose) {
|
||||
this.#isVerbose = !!verbose;
|
||||
}
|
||||
|
||||
get isChalkEnabled() {
|
||||
return this.#isChalkEnabled;
|
||||
}
|
||||
|
||||
set isChalkEnabled(enabled) {
|
||||
this.#isChalkEnabled = !!enabled;
|
||||
}
|
||||
|
||||
overrideLogger(logger) {
|
||||
this.#logger = logger;
|
||||
}
|
||||
|
||||
get logger() {
|
||||
return this.#logger || console;
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
log(msg) {
|
||||
this.message(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef LogOptions
|
||||
* @property {string} message
|
||||
* @property {string=} prefix
|
||||
* @property {LogType=} type
|
||||
* @property {string=} color
|
||||
* @property {boolean=} force
|
||||
* @param {LogOptions} options
|
||||
*/
|
||||
logWithOptions({ message, type, prefix, color, force }) {
|
||||
this.message(message, type, color, force, prefix);
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
forceLog(msg) {
|
||||
this.message(msg, undefined, undefined, true);
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
info(msg) {
|
||||
this.message(msg, "warn", "blue");
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
warn(msg) {
|
||||
this.message(msg, "warn", "yellow");
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
error(msg) {
|
||||
this.message(msg, "error", "red");
|
||||
}
|
||||
|
||||
/** @param {string} msg */
|
||||
toStream(msg) {
|
||||
this.outputStream.push(msg);
|
||||
}
|
||||
|
||||
closeStream() {
|
||||
this.outputStream.push(null);
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the message to log.
|
||||
*
|
||||
* @param {string} message - The raw message to log.
|
||||
* @param {LogType} [type='log'] - The error level to log.
|
||||
* @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable
|
||||
* @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.
|
||||
*/
|
||||
message(
|
||||
message,
|
||||
type = "log",
|
||||
chalkColor = undefined,
|
||||
forceToConsole = false,
|
||||
prefix = "[11ty]",
|
||||
) {
|
||||
if (!forceToConsole && (!this.isVerbose || process.env.DEBUG)) {
|
||||
debug(message);
|
||||
} else if (this.#logger !== false) {
|
||||
message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`;
|
||||
|
||||
if (chalkColor && this.isChalkEnabled) {
|
||||
this.logger[type](chalk[chalkColor](message));
|
||||
} else {
|
||||
this.logger[type](message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConsoleLogger;
|
||||
24
node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js
generated
vendored
Normal file
24
node_modules/@11ty/eleventy/src/Util/DateGitFirstAdded.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
function getGitFirstAddedTimeStamp(filePath) {
|
||||
return (
|
||||
parseInt(
|
||||
spawn
|
||||
.sync(
|
||||
"git",
|
||||
// Formats https://www.git-scm.com/docs/git-log#_pretty_formats
|
||||
// %at author date, UNIX timestamp
|
||||
["log", "--diff-filter=A", "--follow", "-1", "--format=%at", filePath],
|
||||
)
|
||||
.stdout.toString("utf-8"),
|
||||
) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
// return a Date
|
||||
export default function (inputPath) {
|
||||
let timestamp = getGitFirstAddedTimeStamp(inputPath);
|
||||
if (timestamp) {
|
||||
return new Date(timestamp);
|
||||
}
|
||||
}
|
||||
28
node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js
generated
vendored
Normal file
28
node_modules/@11ty/eleventy/src/Util/DateGitLastUpdated.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
/* Thank you to Vuepress!
|
||||
* https://github.com/vuejs/vuepress/blob/89440ce552675859189ed4ab254ce19c4bba5447/packages/%40vuepress/plugin-last-updated/index.js
|
||||
* MIT licensed: https://github.com/vuejs/vuepress/blob/89440ce552675859189ed4ab254ce19c4bba5447/LICENSE
|
||||
*/
|
||||
function getGitLastUpdatedTimeStamp(filePath) {
|
||||
return (
|
||||
parseInt(
|
||||
spawn
|
||||
.sync(
|
||||
"git",
|
||||
// Formats https://www.git-scm.com/docs/git-log#_pretty_formats
|
||||
// %at author date, UNIX timestamp
|
||||
["log", "-1", "--format=%at", filePath],
|
||||
)
|
||||
.stdout.toString("utf-8"),
|
||||
) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
// return a Date
|
||||
export default function (inputPath) {
|
||||
let timestamp = getGitLastUpdatedTimeStamp(inputPath);
|
||||
if (timestamp) {
|
||||
return new Date(timestamp);
|
||||
}
|
||||
}
|
||||
9
node_modules/@11ty/eleventy/src/Util/DirContains.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Util/DirContains.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import path from "node:path";
|
||||
|
||||
// Returns true if subfolder is in parent (accepts absolute or relative paths for both)
|
||||
export default function (parent, subfolder) {
|
||||
if (path.resolve(subfolder).startsWith(path.resolve(parent))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
51
node_modules/@11ty/eleventy/src/Util/EsmResolver.js
generated
vendored
Normal file
51
node_modules/@11ty/eleventy/src/Util/EsmResolver.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:EsmResolver");
|
||||
|
||||
let lastModifiedPaths = new Map();
|
||||
export async function initialize({ port }) {
|
||||
// From `eleventy.importCacheReset` event in Require.js
|
||||
port.on("message", ({ path, newDate }) => {
|
||||
lastModifiedPaths.set(path, newDate);
|
||||
});
|
||||
}
|
||||
|
||||
// Fixes issue https://github.com/11ty/eleventy/issues/3270
|
||||
// Docs: https://nodejs.org/docs/latest/api/module.html#resolvespecifier-context-nextresolve
|
||||
export async function resolve(specifier, context, nextResolve) {
|
||||
try {
|
||||
// Not a relative import and not a file import
|
||||
// Or from node_modules (perhaps better to check if the specifier is in the project directory instead)
|
||||
if (
|
||||
(!specifier.startsWith("../") &&
|
||||
!specifier.startsWith("./") &&
|
||||
!specifier.startsWith("file:")) ||
|
||||
context.parentURL.includes("/node_modules/")
|
||||
) {
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
let fileUrl = new URL(specifier, context.parentURL);
|
||||
if (fileUrl.searchParams.has("_cache_bust")) {
|
||||
// already is cache busted outside resolver (wider compat, url was changed prior to import, probably in Require.js)
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
let absolutePath = fileUrl.pathname;
|
||||
// Bust the import cache if this is a recently modified file
|
||||
if (lastModifiedPaths.has(absolutePath)) {
|
||||
fileUrl.search = ""; // delete existing searchparams
|
||||
fileUrl.searchParams.set("_cache_bust", lastModifiedPaths.get(absolutePath));
|
||||
debug("Cache busting %o to %o", specifier, fileUrl.toString());
|
||||
|
||||
return nextResolve(fileUrl.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
debug("EsmResolver Error parsing specifier (%o): %o", specifier, e);
|
||||
}
|
||||
|
||||
return nextResolve(specifier);
|
||||
}
|
||||
|
||||
// export async function load(url, context, nextLoad) {
|
||||
// }
|
||||
26
node_modules/@11ty/eleventy/src/Util/EventBusUtil.js
generated
vendored
Normal file
26
node_modules/@11ty/eleventy/src/Util/EventBusUtil.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import eventBus from "../EventBus.js";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:EventBus");
|
||||
|
||||
class EventBusUtil {
|
||||
// Used for non-global subscriptions that will blow away the previous listener
|
||||
static soloOn(name, callback) {
|
||||
eventBus.off(name, callback);
|
||||
eventBus.on(name, callback);
|
||||
}
|
||||
|
||||
static resetForConfig() {
|
||||
this.debug();
|
||||
debug("Config reset (removing eleventy.templateModified listeners).");
|
||||
eventBus.removeAllListeners("eleventy.templateModified");
|
||||
}
|
||||
|
||||
static debug() {
|
||||
for (let name of eventBus.eventNames()) {
|
||||
debug("Listeners for %o: %o", name, eventBus.listenerCount(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EventBusUtil;
|
||||
82
node_modules/@11ty/eleventy/src/Util/ExistsCache.js
generated
vendored
Normal file
82
node_modules/@11ty/eleventy/src/Util/ExistsCache.js
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import fs from "graceful-fs";
|
||||
import PathNormalizer from "./PathNormalizer.js";
|
||||
|
||||
// Checks both files and directories
|
||||
class ExistsCache {
|
||||
constructor() {
|
||||
this._cache = new Map();
|
||||
this.lookupCount = 0;
|
||||
}
|
||||
|
||||
setDirectoryCheck(check) {
|
||||
this.cacheDirectories = !!check;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._cache.size;
|
||||
}
|
||||
|
||||
parentsDoNotExist(path) {
|
||||
if (!this.cacheDirectories) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let allPaths = PathNormalizer.getAllPaths(path).filter((entry) => entry !== path);
|
||||
for (let parentPath of allPaths) {
|
||||
if (this._cache.has(parentPath)) {
|
||||
if (this._cache.get(parentPath) === false) {
|
||||
return true; // we know this parent doesn’t exist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if you’ve made it here: we don’t know if the parents exist or not
|
||||
return false;
|
||||
}
|
||||
|
||||
has(path) {
|
||||
return this._cache.has(path);
|
||||
}
|
||||
|
||||
exists(path) {
|
||||
path = PathNormalizer.fullNormalization(path);
|
||||
|
||||
let exists = this._cache.get(path);
|
||||
if (this.parentsDoNotExist(path)) {
|
||||
// we don’t need to check if a parent directory does not exist
|
||||
exists = false;
|
||||
} else if (!this.has(path)) {
|
||||
exists = fs.existsSync(path);
|
||||
this.markExistsWithParentDirectories(path, exists);
|
||||
this.lookupCount++;
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
// if a file exists, we can mark the parent directories as existing also
|
||||
// if a file does not exist, we don’t know if the parent directories exist or not (yet)
|
||||
markExistsWithParentDirectories(path, exists = true) {
|
||||
path = PathNormalizer.fullNormalization(path);
|
||||
|
||||
if (!this.cacheDirectories || !exists) {
|
||||
this.markExists(path, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let paths = PathNormalizer.getAllPaths(path);
|
||||
for (let fullpath of paths) {
|
||||
this.markExists(fullpath, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
markExists(path, exists = true, alreadyNormalized = false) {
|
||||
if (!alreadyNormalized) {
|
||||
path = PathNormalizer.fullNormalization(path);
|
||||
}
|
||||
|
||||
this._cache.set(path, !!exists);
|
||||
}
|
||||
}
|
||||
|
||||
export default ExistsCache;
|
||||
19
node_modules/@11ty/eleventy/src/Util/FilePathUtil.js
generated
vendored
Normal file
19
node_modules/@11ty/eleventy/src/Util/FilePathUtil.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
class FilePathUtil {
|
||||
static isMatchingExtension(filepath, fileExtension) {
|
||||
if (!fileExtension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(fileExtension || "").startsWith(".")) {
|
||||
fileExtension = "." + fileExtension;
|
||||
}
|
||||
|
||||
return filepath.endsWith(fileExtension);
|
||||
}
|
||||
|
||||
static getFileExtension(filepath) {
|
||||
return (filepath || "").split(".").pop();
|
||||
}
|
||||
}
|
||||
|
||||
export { FilePathUtil };
|
||||
30
node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js
generated
vendored
Normal file
30
node_modules/@11ty/eleventy/src/Util/GetJavaScriptData.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
class JavaScriptInvalidDataFormatError extends EleventyBaseError {}
|
||||
|
||||
export default async function (inst, inputPath, key = "data", options = {}) {
|
||||
let { mixins, isObjectRequired } = Object.assign(
|
||||
{
|
||||
mixins: {},
|
||||
isObjectRequired: true,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
if (inst && key in inst) {
|
||||
// get extra data from `data` method,
|
||||
// either as a function or getter or object literal
|
||||
let result = await (typeof inst[key] === "function"
|
||||
? Object.keys(mixins).length > 0
|
||||
? inst[key].call(mixins)
|
||||
: inst[key]()
|
||||
: inst[key]);
|
||||
|
||||
if (isObjectRequired && typeof result !== "object") {
|
||||
throw new JavaScriptInvalidDataFormatError(
|
||||
`Invalid data format returned from ${inputPath}: typeof ${typeof result}`,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
21
node_modules/@11ty/eleventy/src/Util/GlobMatcher.js
generated
vendored
Normal file
21
node_modules/@11ty/eleventy/src/Util/GlobMatcher.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import micromatch from "micromatch";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
function isGlobMatch(filepath, globs = [], options = undefined) {
|
||||
if (!filepath || !Array.isArray(globs) || globs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let inputPath = TemplatePath.stripLeadingDotSlash(filepath);
|
||||
let opts = Object.assign(
|
||||
{
|
||||
dot: true,
|
||||
nocase: true, // insensitive
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return micromatch.isMatch(inputPath, globs, opts);
|
||||
}
|
||||
|
||||
export { isGlobMatch };
|
||||
158
node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js
generated
vendored
Normal file
158
node_modules/@11ty/eleventy/src/Util/HtmlTransformer.js
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
import posthtml from "posthtml";
|
||||
import urls from "@11ty/posthtml-urls";
|
||||
import { FilePathUtil } from "./FilePathUtil.js";
|
||||
|
||||
class HtmlTransformer {
|
||||
// feature test for Eleventy Bundle Plugin
|
||||
static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true;
|
||||
|
||||
constructor() {
|
||||
// execution order is important (not order of addition/object key order)
|
||||
this.callbacks = {};
|
||||
this.posthtmlProcessOptions = {};
|
||||
this.plugins = {};
|
||||
}
|
||||
|
||||
get aggregateBench() {
|
||||
if (!this.userConfig) {
|
||||
throw new Error("Internal error: Missing `userConfig` in HtmlTransformer.");
|
||||
}
|
||||
return this.userConfig.benchmarkManager.get("Aggregate");
|
||||
}
|
||||
|
||||
setUserConfig(config) {
|
||||
this.userConfig = config;
|
||||
}
|
||||
|
||||
static prioritySort(a, b) {
|
||||
if (b.priority > a.priority) {
|
||||
return 1;
|
||||
}
|
||||
if (a.priority > b.priority) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// context is important as it is used in html base plugin for page specific URL
|
||||
static _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) {
|
||||
let inst = posthtml();
|
||||
|
||||
// already sorted by priority when added
|
||||
for (let { fn: plugin, options } of plugins) {
|
||||
inst.use(plugin(Object.assign({}, context, options)));
|
||||
}
|
||||
|
||||
// Run the built-ins last
|
||||
if (callbacks.length > 0) {
|
||||
inst.use(
|
||||
urls({
|
||||
eachURL: (url) => {
|
||||
// and: attrName, tagName
|
||||
for (let { fn: callback } of callbacks) {
|
||||
// already sorted by priority when added
|
||||
url = callback.call(context, url);
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
_add(extensions, addType, value, options = {}) {
|
||||
options = Object.assign(
|
||||
{
|
||||
priority: 0,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
let extensionsArray = (extensions || "").split(",");
|
||||
for (let ext of extensionsArray) {
|
||||
let target = this[addType];
|
||||
if (!target[ext]) {
|
||||
target[ext] = [];
|
||||
}
|
||||
|
||||
target[ext].push({
|
||||
fn: value, // callback or plugin
|
||||
priority: options.priority,
|
||||
enabled: options.enabled || (() => true),
|
||||
options: options.pluginOptions,
|
||||
});
|
||||
|
||||
target[ext].sort(HtmlTransformer.prioritySort);
|
||||
}
|
||||
}
|
||||
|
||||
addPosthtmlPlugin(extensions, plugin, options = {}) {
|
||||
this._add(extensions, "plugins", plugin, options);
|
||||
}
|
||||
|
||||
addUrlTransform(extensions, callback, options = {}) {
|
||||
this._add(extensions, "callbacks", callback, options);
|
||||
}
|
||||
|
||||
setPosthtmlProcessOptions(options) {
|
||||
Object.assign(this.posthtmlProcessOptions, options);
|
||||
}
|
||||
|
||||
isTransformable(extension, context) {
|
||||
return (
|
||||
this.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
getCallbacks(extension, context) {
|
||||
let callbacks = this.callbacks[extension] || [];
|
||||
return callbacks.filter(({ enabled }) => {
|
||||
if (!enabled || typeof enabled !== "function") {
|
||||
return true;
|
||||
}
|
||||
return enabled(context);
|
||||
});
|
||||
}
|
||||
|
||||
getPlugins(extension) {
|
||||
let plugins = this.plugins[extension] || [];
|
||||
return plugins.filter(({ enabled }) => {
|
||||
if (!enabled || typeof enabled !== "function") {
|
||||
return true;
|
||||
}
|
||||
return enabled();
|
||||
});
|
||||
}
|
||||
|
||||
static async transformStandalone(content, callback, posthtmlProcessOptions = {}) {
|
||||
let posthtmlInstance = this._getPosthtmlInstance([
|
||||
{
|
||||
fn: callback,
|
||||
enabled: () => true,
|
||||
},
|
||||
]);
|
||||
let result = await posthtmlInstance.process(content, posthtmlProcessOptions);
|
||||
return result.html;
|
||||
}
|
||||
|
||||
async transformContent(outputPath, content, context) {
|
||||
let extension = FilePathUtil.getFileExtension(outputPath);
|
||||
if (!this.isTransformable(extension, context)) {
|
||||
return content;
|
||||
}
|
||||
|
||||
let bench = this.aggregateBench.get(`Transforming \`${extension}\` with posthtml`);
|
||||
bench.before();
|
||||
let callbacks = this.getCallbacks(extension, context);
|
||||
let plugins = this.getPlugins(extension);
|
||||
let posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context);
|
||||
let result = await posthtmlInstance.process(content, this.posthtmlProcessOptions);
|
||||
bench.after();
|
||||
return result.html;
|
||||
}
|
||||
}
|
||||
|
||||
export { HtmlTransformer };
|
||||
64
node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js
generated
vendored
Normal file
64
node_modules/@11ty/eleventy/src/Util/ImportJsonSync.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import debugUtil from "debug";
|
||||
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import { normalizeFilePathInEleventyPackage } from "./Require.js";
|
||||
|
||||
const debug = debugUtil("Eleventy:ImportJsonSync");
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
function findFilePathInParentDirs(dir, filename) {
|
||||
// `package.json` searches look in parent dirs:
|
||||
// https://docs.npmjs.com/cli/v7/configuring-npm/folders#more-information
|
||||
// Fixes issue #3178, limited to working dir paths only
|
||||
let workingDir = TemplatePath.getWorkingDir();
|
||||
let allDirs = TemplatePath.getAllDirs(dir).filter((entry) => entry.startsWith(workingDir));
|
||||
|
||||
for (let dir of allDirs) {
|
||||
let newPath = TemplatePath.join(dir, filename);
|
||||
if (fs.existsSync(newPath)) {
|
||||
debug("Found %o searching parent directories at: %o", filename, dir);
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function importJsonSync(filePath) {
|
||||
if (!filePath.endsWith(".json")) {
|
||||
throw new Error(`importJsonSync expects a .json file extension (received: ${filePath})`);
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO clear require.cache when these files change
|
||||
return require(filePath);
|
||||
} catch (e) {
|
||||
debug("Attempted to import %o, received this error: %o", filePath, e);
|
||||
// if file does not exist, return nothing
|
||||
}
|
||||
}
|
||||
|
||||
function getEleventyPackageJson() {
|
||||
let filePath = normalizeFilePathInEleventyPackage("package.json");
|
||||
return importJsonSync(filePath);
|
||||
}
|
||||
|
||||
function getModulePackageJson(dir) {
|
||||
let filePath = findFilePathInParentDirs(TemplatePath.absolutePath(dir), "package.json");
|
||||
return importJsonSync(filePath);
|
||||
}
|
||||
|
||||
function getWorkingProjectPackageJson() {
|
||||
let dir = TemplatePath.absolutePath(TemplatePath.getWorkingDir());
|
||||
let filePath = findFilePathInParentDirs(dir, "package.json");
|
||||
return importJsonSync(filePath);
|
||||
}
|
||||
|
||||
export {
|
||||
importJsonSync,
|
||||
findFilePathInParentDirs,
|
||||
getEleventyPackageJson,
|
||||
getModulePackageJson,
|
||||
getWorkingProjectPackageJson,
|
||||
};
|
||||
5
node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy/src/Util/IsAsyncFunction.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
const ComparisonAsyncFunction = (async () => {}).constructor;
|
||||
|
||||
export default function isAsyncFunction(fn) {
|
||||
return fn instanceof ComparisonAsyncFunction;
|
||||
}
|
||||
55
node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js
generated
vendored
Normal file
55
node_modules/@11ty/eleventy/src/Util/JavaScriptDependencies.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import dependencyTree from "@11ty/dependency-tree";
|
||||
import { find } from "@11ty/dependency-tree-esm";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
|
||||
class JavaScriptDependencies {
|
||||
static getErrorMessage(file, type) {
|
||||
return `A problem was encountered looking for JavaScript dependencies in ${type} file: ${file}. This only affects --watch and --serve behavior and does not affect your build.`;
|
||||
}
|
||||
|
||||
static async getDependencies(inputFiles, isProjectUsingEsm) {
|
||||
let depSet = new Set();
|
||||
|
||||
// TODO does this need to work with aliasing? what other JS extensions will have deps?
|
||||
let commonJsFiles = inputFiles.filter(
|
||||
(file) => (!isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".cjs"),
|
||||
);
|
||||
|
||||
for (let file of commonJsFiles) {
|
||||
try {
|
||||
let modules = dependencyTree(file, {
|
||||
nodeModuleNames: "exclude",
|
||||
allowNotFound: true,
|
||||
}).map((dependency) => {
|
||||
return TemplatePath.addLeadingDotSlash(TemplatePath.relativePath(dependency));
|
||||
});
|
||||
|
||||
for (let dep of modules) {
|
||||
depSet.add(dep);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new EleventyBaseError(this.getErrorMessage(file, "CommonJS"), e);
|
||||
}
|
||||
}
|
||||
|
||||
let esmFiles = inputFiles.filter(
|
||||
(file) => (isProjectUsingEsm && file.endsWith(".js")) || file.endsWith(".mjs"),
|
||||
);
|
||||
for (let file of esmFiles) {
|
||||
try {
|
||||
let modules = await find(file);
|
||||
for (let dep of modules) {
|
||||
depSet.add(dep);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new EleventyBaseError(this.getErrorMessage(file, "ESM"), e);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(depSet).sort();
|
||||
}
|
||||
}
|
||||
|
||||
export default JavaScriptDependencies;
|
||||
26
node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js
generated
vendored
Normal file
26
node_modules/@11ty/eleventy/src/Util/MemoizeFunction.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
export default function (callback, options = {}) {
|
||||
let { bench, name } = options;
|
||||
let cache = new Map();
|
||||
|
||||
return (...args) => {
|
||||
// Only supports single-arg functions for now.
|
||||
if (args.filter(Boolean).length > 1) {
|
||||
bench?.get(`(count) ${name} Not valid for memoize`).incrementCount();
|
||||
return callback(...args);
|
||||
}
|
||||
|
||||
let [cacheKey] = args;
|
||||
|
||||
if (!cache.has(cacheKey)) {
|
||||
cache.set(cacheKey, callback(...args));
|
||||
|
||||
bench?.get(`(count) ${name} memoize miss`).incrementCount();
|
||||
|
||||
return cache.get(cacheKey);
|
||||
}
|
||||
|
||||
bench?.get(`(count) ${name} memoize hit`).incrementCount();
|
||||
|
||||
return cache.get(cacheKey);
|
||||
};
|
||||
}
|
||||
20
node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js
generated
vendored
Normal file
20
node_modules/@11ty/eleventy/src/Util/Objects/DeepFreeze.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
|
||||
|
||||
function DeepFreeze(obj, topLevelExceptions) {
|
||||
for (let name of Reflect.ownKeys(obj)) {
|
||||
if ((topLevelExceptions || []).find((key) => key === name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = obj[name];
|
||||
if (isPlainObject(value)) {
|
||||
DeepFreeze(value);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(obj);
|
||||
}
|
||||
|
||||
export { DeepFreeze };
|
||||
9
node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Util/Objects/ObjectFilter.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function objectFilter(obj, callback) {
|
||||
let newObject = {};
|
||||
for (let [key, value] of Object.entries(obj || {})) {
|
||||
if (callback(value, key)) {
|
||||
newObject[key] = value;
|
||||
}
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
111
node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
generated
vendored
Normal file
111
node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
import types from "node:util/types";
|
||||
import debugUtil from "debug";
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
|
||||
const debug = debugUtil("Dev:Eleventy:Proxy");
|
||||
|
||||
function wrapObject(target, fallback) {
|
||||
if (Object.isFrozen(target)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return new Proxy(target, {
|
||||
getOwnPropertyDescriptor(target, prop) {
|
||||
let ret;
|
||||
|
||||
if (Reflect.has(target, prop)) {
|
||||
ret = Reflect.getOwnPropertyDescriptor(target, prop);
|
||||
} else if (Reflect.has(fallback, prop)) {
|
||||
ret = Reflect.getOwnPropertyDescriptor(fallback, prop);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
has(target, prop) {
|
||||
if (Reflect.has(target, prop)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Reflect.has(fallback, prop);
|
||||
},
|
||||
ownKeys(target) {
|
||||
let s = new Set();
|
||||
for (let k of Reflect.ownKeys(target)) {
|
||||
s.add(k);
|
||||
}
|
||||
if (isPlainObject(fallback)) {
|
||||
for (let k of Reflect.ownKeys(fallback)) {
|
||||
s.add(k);
|
||||
}
|
||||
}
|
||||
return Array.from(s);
|
||||
},
|
||||
get(target, prop) {
|
||||
debug("handler:get", prop);
|
||||
|
||||
let value = Reflect.get(target, prop);
|
||||
|
||||
if (Reflect.has(target, prop)) {
|
||||
// Already proxied
|
||||
if (types.isProxy(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isPlainObject(value) && Reflect.has(fallback, prop)) {
|
||||
if (Object.isFrozen(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let ret = wrapObject(value, Reflect.get(fallback, prop));
|
||||
debug("handler:get (primary, object)", prop);
|
||||
return ret;
|
||||
}
|
||||
|
||||
debug("handler:get (primary)", prop);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Does not exist in primary
|
||||
if (Reflect.has(fallback, prop)) {
|
||||
// fallback has prop
|
||||
let fallbackValue = Reflect.get(fallback, prop);
|
||||
|
||||
if (isPlainObject(fallbackValue)) {
|
||||
if (Object.isFrozen(fallbackValue)) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
debug("handler:get (fallback, object)", prop);
|
||||
// set empty object on primary
|
||||
let emptyObject = {};
|
||||
Reflect.set(target, prop, emptyObject);
|
||||
|
||||
return wrapObject(emptyObject, fallbackValue);
|
||||
}
|
||||
|
||||
debug("handler:get (fallback)", prop);
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
// primary *and* fallback do _not_ have prop
|
||||
debug("handler:get (not on primary or fallback)", prop);
|
||||
|
||||
return value;
|
||||
},
|
||||
set(target, prop, value) {
|
||||
debug("handler:set", prop);
|
||||
|
||||
return Reflect.set(target, prop, value);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function ProxyWrap(target, fallback) {
|
||||
if (!isPlainObject(target) || !isPlainObject(fallback)) {
|
||||
throw new Error("ProxyWrap expects objects for both the target and fallback");
|
||||
}
|
||||
|
||||
return wrapObject(target, fallback);
|
||||
}
|
||||
|
||||
export { ProxyWrap };
|
||||
1
node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs
generated
vendored
Normal file
1
node_modules/@11ty/eleventy/src/Util/Objects/SampleModule.mjs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
136
node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js
generated
vendored
Normal file
136
node_modules/@11ty/eleventy/src/Util/Objects/Sortable.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
class Sortable {
|
||||
constructor() {
|
||||
this.isSortAscending = true;
|
||||
this.isSortNumeric = false;
|
||||
this.items = [];
|
||||
this._dirty = true;
|
||||
|
||||
this.sortFunctionStringMap = {
|
||||
"A-Z": "sortFunctionAscending",
|
||||
"Z-A": "sortFunctionDescending",
|
||||
"0-9": "sortFunctionNumericAscending",
|
||||
"9-0": "sortFunctionNumericDescending",
|
||||
};
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
add(item) {
|
||||
this._dirty = true;
|
||||
this.items.push(item);
|
||||
}
|
||||
|
||||
sort(sortFunction) {
|
||||
if (!sortFunction) {
|
||||
sortFunction = this.getSortFunction();
|
||||
} else if (typeof sortFunction === "string") {
|
||||
let key = sortFunction;
|
||||
let name;
|
||||
if (key in this.sortFunctionStringMap) {
|
||||
name = this.sortFunctionStringMap[key];
|
||||
}
|
||||
if (Sortable[name]) {
|
||||
sortFunction = Sortable[name];
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid String argument for sort(). Received \`${key}\`. Valid values: ${Object.keys(
|
||||
this.sortFunctionStringMap,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.items.slice().sort(sortFunction);
|
||||
}
|
||||
|
||||
sortAscending() {
|
||||
return this.sort(this.getSortFunctionAscending());
|
||||
}
|
||||
|
||||
sortDescending() {
|
||||
return this.sort(this.getSortFunctionDescending());
|
||||
}
|
||||
|
||||
setSortDescending(isDescending = true) {
|
||||
this.isSortAscending = !isDescending;
|
||||
}
|
||||
|
||||
setSortAscending(isAscending = true) {
|
||||
this.isSortAscending = isAscending;
|
||||
}
|
||||
|
||||
setSortNumeric(isNumeric) {
|
||||
this.isSortNumeric = isNumeric;
|
||||
}
|
||||
|
||||
/* Sort functions */
|
||||
static sortFunctionNumericAscending(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
static sortFunctionNumericDescending(a, b) {
|
||||
return b - a;
|
||||
}
|
||||
|
||||
static sortFunctionAscending(a, b) {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
} else if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static sortFunctionDescending(a, b) {
|
||||
return Sortable.sortFunctionAscending(b, a);
|
||||
}
|
||||
|
||||
static sortFunctionAlphabeticAscending(a, b) {
|
||||
return Sortable.sortFunctionAscending(a, b);
|
||||
}
|
||||
|
||||
static sortFunctionAlphabeticDescending(a, b) {
|
||||
return Sortable.sortFunctionAscending(b, a);
|
||||
}
|
||||
|
||||
static sortFunctionDate(mapA, mapB) {
|
||||
return Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());
|
||||
}
|
||||
|
||||
static sortFunctionDateInputPath(mapA, mapB) {
|
||||
let sortDate = Sortable.sortFunctionNumericAscending(mapA.date.getTime(), mapB.date.getTime());
|
||||
if (sortDate === 0) {
|
||||
return Sortable.sortFunctionAlphabeticAscending(mapA.inputPath, mapB.inputPath);
|
||||
}
|
||||
return sortDate;
|
||||
}
|
||||
/* End sort functions */
|
||||
|
||||
getSortFunction() {
|
||||
if (this.isSortAscending) {
|
||||
return this.getSortFunctionAscending();
|
||||
} else {
|
||||
return this.getSortFunctionDescending();
|
||||
}
|
||||
}
|
||||
|
||||
getSortFunctionAscending() {
|
||||
if (this.isSortNumeric) {
|
||||
return Sortable.sortFunctionNumericAscending;
|
||||
} else {
|
||||
return Sortable.sortFunctionAlphabeticAscending;
|
||||
}
|
||||
}
|
||||
|
||||
getSortFunctionDescending() {
|
||||
if (this.isSortNumeric) {
|
||||
return Sortable.sortFunctionNumericDescending;
|
||||
} else {
|
||||
return Sortable.sortFunctionAlphabeticDescending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Sortable;
|
||||
3
node_modules/@11ty/eleventy/src/Util/Objects/Unique.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy/src/Util/Objects/Unique.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Unique(arr) {
|
||||
return Array.from(new Set(arr));
|
||||
}
|
||||
16
node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js
generated
vendored
Normal file
16
node_modules/@11ty/eleventy/src/Util/PassthroughCopyBehaviorCheck.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
function isUsingEleventyDevServer(config) {
|
||||
return (
|
||||
!config.serverOptions.module || config.serverOptions.module === "@11ty/eleventy-dev-server"
|
||||
);
|
||||
}
|
||||
|
||||
// Config opt-out via serverPassthroughCopyBehavior
|
||||
// False when other server is used
|
||||
// False when runMode is "build" or "watch"
|
||||
export default function (config, runMode) {
|
||||
return (
|
||||
config.serverPassthroughCopyBehavior === "passthrough" &&
|
||||
isUsingEleventyDevServer(config) &&
|
||||
runMode === "serve"
|
||||
);
|
||||
}
|
||||
60
node_modules/@11ty/eleventy/src/Util/PathNormalizer.js
generated
vendored
Normal file
60
node_modules/@11ty/eleventy/src/Util/PathNormalizer.js
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
|
||||
class PathNormalizer {
|
||||
static getParts(inputPath) {
|
||||
if (!inputPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let separator = "/";
|
||||
if (inputPath.includes(path.sep)) {
|
||||
separator = path.sep;
|
||||
}
|
||||
|
||||
return inputPath.split(separator).filter((entry) => entry !== ".");
|
||||
}
|
||||
|
||||
// order is important here: the top-most directory returns first
|
||||
// array of file and all parent directories
|
||||
static getAllPaths(inputPath) {
|
||||
let parts = this.getParts(inputPath);
|
||||
let allPaths = [];
|
||||
|
||||
let fullpath = "";
|
||||
for (let part of parts) {
|
||||
fullpath += (fullpath.length > 0 ? "/" : "") + part;
|
||||
allPaths.push(fullpath);
|
||||
}
|
||||
|
||||
return allPaths;
|
||||
}
|
||||
|
||||
static normalizeSeperator(inputPath) {
|
||||
if (!inputPath) {
|
||||
return inputPath;
|
||||
}
|
||||
return inputPath.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
static fullNormalization(inputPath) {
|
||||
if (typeof inputPath !== "string") {
|
||||
return inputPath;
|
||||
}
|
||||
|
||||
// Fix file:///Users/ or file:///C:/ paths passed in
|
||||
if (inputPath.startsWith("file://")) {
|
||||
inputPath = fileURLToPath(inputPath);
|
||||
}
|
||||
|
||||
// Paths should not be absolute (we convert absolute paths to relative)
|
||||
// Paths should not have a leading dot slash
|
||||
// Paths should always be `/` independent of OS path separator
|
||||
return TemplatePath.stripLeadingDotSlash(
|
||||
this.normalizeSeperator(TemplatePath.relativePath(inputPath)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PathNormalizer;
|
||||
21
node_modules/@11ty/eleventy/src/Util/PathPrefixer.js
generated
vendored
Normal file
21
node_modules/@11ty/eleventy/src/Util/PathPrefixer.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import path from "node:path";
|
||||
|
||||
import PathNormalizer from "./PathNormalizer.js";
|
||||
|
||||
class PathPrefixer {
|
||||
static normalizePathPrefix(pathPrefix) {
|
||||
if (pathPrefix) {
|
||||
// add leading / (for browsersync), see #1454
|
||||
// path.join uses \\ for Windows so we split and rejoin
|
||||
return PathPrefixer.joinUrlParts("/", pathPrefix);
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
static joinUrlParts(...parts) {
|
||||
return PathNormalizer.normalizeSeperator(path.join(...parts));
|
||||
}
|
||||
}
|
||||
|
||||
export default PathPrefixer;
|
||||
3
node_modules/@11ty/eleventy/src/Util/Pluralize.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy/src/Util/Pluralize.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function (count, singleWord, pluralWord) {
|
||||
return count === 1 ? singleWord : pluralWord;
|
||||
}
|
||||
344
node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js
generated
vendored
Normal file
344
node_modules/@11ty/eleventy/src/Util/ProjectDirectories.js
generated
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { TemplatePath } from "@11ty/eleventy-utils";
|
||||
import isGlob from "is-glob";
|
||||
|
||||
/* Directories internally should always use *nix forward slashes */
|
||||
class ProjectDirectories {
|
||||
static defaults = {
|
||||
input: "./",
|
||||
data: "./_data/", // Relative to input directory
|
||||
includes: "./_includes/", // Relative to input directory
|
||||
layouts: "./_layouts/", // Relative to input directory
|
||||
output: "./_site/",
|
||||
};
|
||||
|
||||
// no updates allowed, input/output set via CLI
|
||||
#frozen = false;
|
||||
|
||||
#raw = {};
|
||||
|
||||
#dirs = {};
|
||||
|
||||
inputFile = undefined;
|
||||
inputGlob = undefined;
|
||||
|
||||
// Add leading dot slash
|
||||
// Use forward slashes
|
||||
static normalizePath(fileOrDir) {
|
||||
return TemplatePath.standardizeFilePath(fileOrDir);
|
||||
}
|
||||
|
||||
// Must be a directory
|
||||
// Always include a trailing slash
|
||||
static normalizeDirectory(dir) {
|
||||
return this.addTrailingSlash(this.normalizePath(dir));
|
||||
}
|
||||
|
||||
normalizeDirectoryPathRelativeToInputDirectory(filePath) {
|
||||
return ProjectDirectories.normalizeDirectory(path.join(this.input, filePath));
|
||||
}
|
||||
|
||||
static addTrailingSlash(path) {
|
||||
if (path.slice(-1) === "/") {
|
||||
return path;
|
||||
}
|
||||
return path + "/";
|
||||
}
|
||||
|
||||
// If input/output are set via CLI, they take precedence over all other configuration values.
|
||||
freeze() {
|
||||
this.#frozen = true;
|
||||
}
|
||||
|
||||
setViaConfigObject(configDirs = {}) {
|
||||
// input must come last
|
||||
let inputChanged = false;
|
||||
if (
|
||||
configDirs.input &&
|
||||
ProjectDirectories.normalizeDirectory(configDirs.input) !== this.input
|
||||
) {
|
||||
this.#setInputRaw(configDirs.input);
|
||||
inputChanged = true;
|
||||
}
|
||||
|
||||
// If falsy or an empty string, the current directory is used.
|
||||
if (configDirs.output !== undefined) {
|
||||
if (ProjectDirectories.normalizeDirectory(configDirs.output) !== this.output) {
|
||||
this.setOutput(configDirs.output);
|
||||
}
|
||||
}
|
||||
|
||||
// Input relative directory, if falsy or an empty string, inputDir is used!
|
||||
// Always set if input changed, e.g. input is `src` and data is `../_data` (resulting in `./_data`) we still want to set data to this new value
|
||||
if (configDirs.data !== undefined) {
|
||||
if (
|
||||
inputChanged ||
|
||||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.data || "") !== this.data
|
||||
) {
|
||||
this.setData(configDirs.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Input relative directory, if falsy or an empty string, inputDir is used!
|
||||
if (configDirs.includes !== undefined) {
|
||||
if (
|
||||
inputChanged ||
|
||||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.includes || "") !==
|
||||
this.includes
|
||||
) {
|
||||
this.setIncludes(configDirs.includes);
|
||||
}
|
||||
}
|
||||
|
||||
// Input relative directory, if falsy or an empty string, inputDir is used!
|
||||
if (configDirs.layouts !== undefined) {
|
||||
if (
|
||||
inputChanged ||
|
||||
this.normalizeDirectoryPathRelativeToInputDirectory(configDirs.layouts || "") !==
|
||||
this.layouts
|
||||
) {
|
||||
this.setLayouts(configDirs.layouts);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputChanged) {
|
||||
this.updateInputDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
updateInputDependencies() {
|
||||
// raw first, fall back to Eleventy defaults if not yet set
|
||||
this.setData(this.#raw.data ?? ProjectDirectories.defaults.data);
|
||||
this.setIncludes(this.#raw.includes ?? ProjectDirectories.defaults.includes);
|
||||
|
||||
// Should not include this if not explicitly opted-in
|
||||
if (this.#raw.layouts !== undefined) {
|
||||
this.setLayouts(this.#raw.layouts ?? ProjectDirectories.defaults.layouts);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to project root, must exist */
|
||||
#setInputRaw(dirOrFile, inputDir = undefined) {
|
||||
// is frozen and was defined previously
|
||||
if (this.#frozen && this.#raw.input !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#raw.input = dirOrFile;
|
||||
|
||||
if (!dirOrFile) {
|
||||
// input must exist if inputDir is not set.
|
||||
return;
|
||||
}
|
||||
|
||||
// Input has to exist (assumed glob if it does not exist)
|
||||
let inputExists = fs.existsSync(dirOrFile);
|
||||
let inputExistsAndIsDirectory = inputExists && fs.statSync(dirOrFile).isDirectory();
|
||||
|
||||
if (inputExistsAndIsDirectory) {
|
||||
// is not a file or glob
|
||||
this.#dirs.input = ProjectDirectories.normalizeDirectory(dirOrFile);
|
||||
} else {
|
||||
if (inputExists) {
|
||||
this.inputFile = ProjectDirectories.normalizePath(dirOrFile);
|
||||
} else {
|
||||
if (!isGlob(dirOrFile)) {
|
||||
throw new Error(
|
||||
`The "${dirOrFile}" \`input\` parameter (directory or file path) must exist on the file system (unless detected as a glob by the \`is-glob\` package)`,
|
||||
);
|
||||
}
|
||||
|
||||
this.inputGlob = dirOrFile;
|
||||
}
|
||||
|
||||
// Explicit Eleventy option for inputDir
|
||||
if (inputDir) {
|
||||
// Changed in 3.0: must exist
|
||||
if (!fs.existsSync(inputDir)) {
|
||||
throw new Error("Directory must exist (via inputDir option to Eleventy constructor).");
|
||||
}
|
||||
|
||||
this.#dirs.input = ProjectDirectories.normalizeDirectory(inputDir);
|
||||
} else {
|
||||
// the input directory is implied to be the parent directory of the
|
||||
// file, unless inputDir is explicitly specified (via Eleventy constructor `options`)
|
||||
this.#dirs.input = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.getDirFromFilePath(dirOrFile), // works with globs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setInput(dirOrFile, inputDir = undefined) {
|
||||
this.#setInputRaw(dirOrFile, inputDir); // does not update
|
||||
this.updateInputDependencies();
|
||||
}
|
||||
|
||||
/* Relative to input dir */
|
||||
setIncludes(dir) {
|
||||
if (dir !== undefined) {
|
||||
// falsy or an empty string is valid (falls back to input dir)
|
||||
this.#raw.includes = dir;
|
||||
this.#dirs.includes = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(this.input, dir || ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to input dir */
|
||||
/* Optional */
|
||||
setLayouts(dir) {
|
||||
if (dir !== undefined) {
|
||||
// falsy or an empty string is valid (falls back to input dir)
|
||||
this.#raw.layouts = dir;
|
||||
this.#dirs.layouts = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(this.input, dir || ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to input dir */
|
||||
setData(dir) {
|
||||
if (dir !== undefined) {
|
||||
// falsy or an empty string is valid (falls back to input dir)
|
||||
// TODO must exist if specified
|
||||
this.#raw.data = dir;
|
||||
this.#dirs.data = ProjectDirectories.normalizeDirectory(
|
||||
TemplatePath.join(this.input, dir || ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Relative to project root */
|
||||
setOutput(dir) {
|
||||
// is frozen and was defined previously
|
||||
if (this.#frozen && this.#raw.output !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir !== undefined) {
|
||||
this.#raw.output = dir;
|
||||
this.#dirs.output = ProjectDirectories.normalizeDirectory(dir || "");
|
||||
}
|
||||
}
|
||||
|
||||
get input() {
|
||||
return this.#dirs.input || ProjectDirectories.defaults.input;
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this.#dirs.data || ProjectDirectories.defaults.data;
|
||||
}
|
||||
|
||||
get includes() {
|
||||
return this.#dirs.includes || ProjectDirectories.defaults.includes;
|
||||
}
|
||||
|
||||
get layouts() {
|
||||
// explicit opt-in, no fallback.
|
||||
return this.#dirs.layouts;
|
||||
}
|
||||
|
||||
get output() {
|
||||
return this.#dirs.output || ProjectDirectories.defaults.output;
|
||||
}
|
||||
|
||||
isTemplateFile(filePath) {
|
||||
let inputPath = this.getInputPath(filePath);
|
||||
if (this.layouts && inputPath.startsWith(this.layouts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputPath.startsWith(this.includes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return inputPath.startsWith(this.input);
|
||||
}
|
||||
|
||||
// for a hypothetical template file
|
||||
getInputPath(filePathRelativeToInputDir) {
|
||||
// TODO change ~/ to project root dir
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(this.input, TemplatePath.standardizeFilePath(filePathRelativeToInputDir)),
|
||||
);
|
||||
}
|
||||
|
||||
// Inverse of getInputPath
|
||||
// Removes input dir from path
|
||||
getInputPathRelativeToInputDirectory(filePathRelativeToInputDir) {
|
||||
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
|
||||
|
||||
// No leading dot slash
|
||||
return TemplatePath.stripLeadingSubPath(filePathRelativeToInputDir, inputDir);
|
||||
}
|
||||
|
||||
// for a hypothetical Eleventy layout file
|
||||
getLayoutPath(filePathRelativeToLayoutDir) {
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(
|
||||
this.layouts || this.includes,
|
||||
TemplatePath.standardizeFilePath(filePathRelativeToLayoutDir),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Removes layout dir from path
|
||||
getLayoutPathRelativeToInputDirectory(filePathRelativeToLayoutDir) {
|
||||
let layoutPath = this.getLayoutPath(filePathRelativeToLayoutDir);
|
||||
let inputDir = TemplatePath.addLeadingDotSlash(TemplatePath.join(this.input));
|
||||
|
||||
// No leading dot slash
|
||||
return TemplatePath.stripLeadingSubPath(layoutPath, inputDir);
|
||||
}
|
||||
|
||||
getProjectPath(filePath) {
|
||||
return TemplatePath.addLeadingDotSlash(
|
||||
TemplatePath.join(".", TemplatePath.standardizeFilePath(filePath)),
|
||||
);
|
||||
}
|
||||
|
||||
// Access the data without being able to set the data.
|
||||
getUserspaceInstance() {
|
||||
let d = this;
|
||||
|
||||
return {
|
||||
get input() {
|
||||
return d.input;
|
||||
},
|
||||
get inputFile() {
|
||||
return d.inputFile;
|
||||
},
|
||||
get inputGlob() {
|
||||
return d.inputGlob;
|
||||
},
|
||||
get data() {
|
||||
return d.data;
|
||||
},
|
||||
get includes() {
|
||||
return d.includes;
|
||||
},
|
||||
get layouts() {
|
||||
return d.layouts;
|
||||
},
|
||||
get output() {
|
||||
return d.output;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
return {
|
||||
input: this.input,
|
||||
inputFile: this.inputFile,
|
||||
inputGlob: this.inputGlob,
|
||||
data: this.data,
|
||||
includes: this.includes,
|
||||
layouts: this.layouts,
|
||||
output: this.output,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectDirectories;
|
||||
134
node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js
generated
vendored
Normal file
134
node_modules/@11ty/eleventy/src/Util/ProjectTemplateFormats.js
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
import debugUtil from "debug";
|
||||
const debug = debugUtil("Eleventy:Util:ProjectTemplateFormats");
|
||||
|
||||
class ProjectTemplateFormats {
|
||||
#useAll = {};
|
||||
#raw = {};
|
||||
|
||||
#values = {}; // Set objects
|
||||
|
||||
static union(...sets) {
|
||||
let s = new Set();
|
||||
|
||||
for (let set of sets) {
|
||||
if (!set || typeof set[Symbol.iterator] !== "function") {
|
||||
continue;
|
||||
}
|
||||
for (let v of set) {
|
||||
s.add(v);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#normalize(formats) {
|
||||
if (Array.isArray(formats)) {
|
||||
formats = "" + formats.join(",");
|
||||
}
|
||||
|
||||
if (typeof formats !== "string") {
|
||||
throw new Error(
|
||||
`Invalid formats (expect String, Array) passed to ProjectTemplateFormats->normalize: ${formats}`,
|
||||
);
|
||||
}
|
||||
|
||||
let final = new Set();
|
||||
for (let format of formats.split(",")) {
|
||||
format = format.trim();
|
||||
if (format && format !== "*") {
|
||||
final.add(format);
|
||||
}
|
||||
}
|
||||
|
||||
return final;
|
||||
}
|
||||
|
||||
isWildcard() {
|
||||
return this.#useAll.cli || this.#useAll.config || false;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
#isUseAll(rawFormats) {
|
||||
if (rawFormats === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof rawFormats === "string") {
|
||||
rawFormats = rawFormats.split(",");
|
||||
}
|
||||
|
||||
if (Array.isArray(rawFormats)) {
|
||||
return rawFormats.find((entry) => entry === "*") !== undefined;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.x Breaking: "" now means no formats. In 2.x and prior it meant "*"
|
||||
setViaCommandLine(formats) {
|
||||
if (formats === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#useAll.cli = this.#isUseAll(formats);
|
||||
this.#raw.cli = formats;
|
||||
this.#values.cli = this.#normalize(formats);
|
||||
}
|
||||
|
||||
// 3.x Breaking: "" now means no formats—in 2.x and prior it meant "*"
|
||||
// 3.x Adds support for comma separated string—in 2.x this required an Array
|
||||
setViaConfig(formats) {
|
||||
if (formats === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// "*" is supported
|
||||
this.#useAll.config = this.#isUseAll(formats);
|
||||
this.#raw.config = formats;
|
||||
this.#values.config = this.#normalize(formats);
|
||||
}
|
||||
|
||||
addViaConfig(formats) {
|
||||
if (!formats) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#isUseAll(formats)) {
|
||||
throw new Error(
|
||||
`\`addTemplateFormats("*")\` is not supported for project template syntaxes.`,
|
||||
);
|
||||
}
|
||||
|
||||
// "*" not supported here
|
||||
this.#raw.configAdd = formats;
|
||||
this.#values.configAdd = this.#normalize(formats);
|
||||
}
|
||||
|
||||
getAllTemplateFormats() {
|
||||
return Array.from(ProjectTemplateFormats.union(this.#values.config, this.#values.configAdd));
|
||||
}
|
||||
|
||||
getTemplateFormats() {
|
||||
if (this.#useAll.cli) {
|
||||
let v = this.getAllTemplateFormats();
|
||||
debug("Using CLI --formats='*': %o", v);
|
||||
return v;
|
||||
}
|
||||
|
||||
if (this.#raw.cli !== undefined) {
|
||||
let v = Array.from(this.#values.cli);
|
||||
debug("Using CLI --formats: %o", v);
|
||||
return v;
|
||||
}
|
||||
|
||||
let v = this.getAllTemplateFormats();
|
||||
debug(
|
||||
"Using configuration `templateFormats`, `setTemplateFormats()`, `addTemplateFormats()`: %o",
|
||||
v,
|
||||
);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
export default ProjectTemplateFormats;
|
||||
206
node_modules/@11ty/eleventy/src/Util/Require.js
generated
vendored
Normal file
206
node_modules/@11ty/eleventy/src/Util/Require.js
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
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 eventBus from "../EventBus.js";
|
||||
|
||||
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();
|
||||
|
||||
// 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 (e) {
|
||||
// if file does not exist, return nothing
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function dynamicImportAbsolutePath(absolutePath, type, returnRaw = false) {
|
||||
if (absolutePath.endsWith(".json") || type === "json") {
|
||||
// https://v8.dev/features/import-assertions#dynamic-import() is still experimental in Node 20
|
||||
let rawInput = loadContents(absolutePath);
|
||||
if (!rawInput) {
|
||||
return;
|
||||
}
|
||||
return JSON.parse(rawInput);
|
||||
}
|
||||
|
||||
let urlPath;
|
||||
try {
|
||||
let u = new URL(`file:${absolutePath}`);
|
||||
|
||||
// Bust the import cache if this is the last modified file
|
||||
if (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);
|
||||
}
|
||||
|
||||
if (returnRaw) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
return promise.then((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 (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);
|
||||
});
|
||||
}
|
||||
|
||||
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, "esm");
|
||||
}
|
||||
|
||||
async function dynamicImport(localPath, type) {
|
||||
let absolutePath = TemplatePath.absolutePath(localPath);
|
||||
|
||||
// Returns promise
|
||||
return dynamicImportAbsolutePath(absolutePath, type);
|
||||
}
|
||||
|
||||
/* 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, "esm", true);
|
||||
}
|
||||
|
||||
/* Used to import project 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, true);
|
||||
}
|
||||
|
||||
export {
|
||||
loadContents as EleventyLoadContent,
|
||||
dynamicImport as EleventyImport,
|
||||
dynamicImportRaw as EleventyImportRaw,
|
||||
dynamicImportFromEleventyPackage as EleventyImportFromEleventy,
|
||||
dynamicImportRawFromEleventyPackage as EleventyImportRawFromEleventy,
|
||||
normalizeFilePathInEleventyPackage,
|
||||
};
|
||||
69
node_modules/@11ty/eleventy/src/Util/ReservedData.js
generated
vendored
Normal file
69
node_modules/@11ty/eleventy/src/Util/ReservedData.js
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
class EleventyReservedDataError extends TypeError {}
|
||||
|
||||
class ReservedData {
|
||||
static properties = [
|
||||
// "pkg", // Object.freeze’d upstream
|
||||
// "eleventy", // Object.freeze’d upstream
|
||||
// "page" is only frozen for specific subproperties below
|
||||
"content",
|
||||
"collections",
|
||||
];
|
||||
|
||||
static pageProperties = [
|
||||
"date",
|
||||
"inputPath",
|
||||
"fileSlug",
|
||||
"filePathStem",
|
||||
"outputFileExtension",
|
||||
"templateSyntax",
|
||||
"url",
|
||||
"outputPath",
|
||||
// not yet `excerpt` or `lang` set via front matter and computed data
|
||||
];
|
||||
|
||||
// Check in the data cascade for reserved data properties.
|
||||
static getReservedKeys(data) {
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let keys = this.properties.filter((key) => {
|
||||
return key in data;
|
||||
});
|
||||
|
||||
if ("page" in data) {
|
||||
if (typeof data.page === "object") {
|
||||
for (let key of this.pageProperties) {
|
||||
if (key in data.page) {
|
||||
keys.push(`page.${key}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// fail `page` when set to non-object values.
|
||||
keys.push("page");
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
static check(data) {
|
||||
let reserved = ReservedData.getReservedKeys(data);
|
||||
if (reserved.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let error = new EleventyReservedDataError(
|
||||
`Cannot override reserved Eleventy properties: ${reserved.join(", ")}`,
|
||||
);
|
||||
|
||||
error.reservedNames = reserved;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
static isReservedDataError(e) {
|
||||
return e instanceof EleventyReservedDataError;
|
||||
}
|
||||
}
|
||||
|
||||
export default ReservedData;
|
||||
11
node_modules/@11ty/eleventy/src/Util/SetUnion.js
generated
vendored
Normal file
11
node_modules/@11ty/eleventy/src/Util/SetUnion.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
function setUnion(...sets) {
|
||||
let root = new Set();
|
||||
for (let set of sets) {
|
||||
for (let entry of set) {
|
||||
root.add(entry);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
export { setUnion };
|
||||
70
node_modules/@11ty/eleventy/src/Util/TransformsUtil.js
generated
vendored
Normal file
70
node_modules/@11ty/eleventy/src/Util/TransformsUtil.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import EleventyBaseError from "../Errors/EleventyBaseError.js";
|
||||
import { isPlainObject } from "@11ty/eleventy-utils";
|
||||
import debugUtil from "debug";
|
||||
|
||||
const debug = debugUtil("Eleventy:Transforms");
|
||||
|
||||
class EleventyTransformError extends EleventyBaseError {}
|
||||
|
||||
class TransformsUtil {
|
||||
static changeTransformsToArray(transformsObj) {
|
||||
let transforms = [];
|
||||
for (let name in transformsObj) {
|
||||
transforms.push({
|
||||
name: name,
|
||||
callback: transformsObj[name],
|
||||
});
|
||||
}
|
||||
return transforms;
|
||||
}
|
||||
|
||||
static async runAll(content, pageData, transforms = {}, options = {}) {
|
||||
let { baseHrefOverride, logger } = options;
|
||||
let { inputPath, outputPath, url } = pageData;
|
||||
|
||||
if (!isPlainObject(transforms)) {
|
||||
throw new Error("Object of transforms expected.");
|
||||
}
|
||||
|
||||
let transformsArray = this.changeTransformsToArray(transforms);
|
||||
|
||||
for (let { callback, name } of transformsArray) {
|
||||
debug("Running %o transform on %o: %o", name, inputPath, outputPath);
|
||||
|
||||
try {
|
||||
let hadContentBefore = !!content;
|
||||
|
||||
content = await callback.call(
|
||||
{
|
||||
inputPath,
|
||||
outputPath,
|
||||
url,
|
||||
page: pageData,
|
||||
baseHref: baseHrefOverride,
|
||||
},
|
||||
content,
|
||||
outputPath,
|
||||
);
|
||||
|
||||
if (hadContentBefore && !content) {
|
||||
if (!logger || !logger.warn) {
|
||||
throw new Error("Internal error: missing `logger` instance.");
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Warning: Transform \`${name}\` returned empty when writing ${outputPath} from ${inputPath}.`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new EleventyTransformError(
|
||||
`Transform \`${name}\` encountered an error when transforming ${inputPath}.`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
export default TransformsUtil;
|
||||
9
node_modules/@11ty/eleventy/src/Util/ValidUrl.js
generated
vendored
Normal file
9
node_modules/@11ty/eleventy/src/Util/ValidUrl.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export default function isValidUrl(url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
// invalid url OR local path
|
||||
return false;
|
||||
}
|
||||
}
|
||||
168
node_modules/@11ty/eleventy/src/defaultConfig.js
generated
vendored
Normal file
168
node_modules/@11ty/eleventy/src/defaultConfig.js
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
import bundlePlugin from "@11ty/eleventy-plugin-bundle";
|
||||
|
||||
import urlFilter from "./Filters/Url.js";
|
||||
import slugFilter from "./Filters/Slug.js";
|
||||
import slugifyFilter from "./Filters/Slugify.js";
|
||||
import getLocaleCollectionItem from "./Filters/GetLocaleCollectionItem.js";
|
||||
import getCollectionItemIndex from "./Filters/GetCollectionItemIndex.js";
|
||||
import { FilterPlugin as InputPathToUrlFilterPlugin } from "./Plugins/InputPathToUrl.js";
|
||||
import { HtmlTransformer } from "./Util/HtmlTransformer.js";
|
||||
import TransformsUtil from "./Util/TransformsUtil.js";
|
||||
import MemoizeUtil from "./Util/MemoizeFunction.js";
|
||||
|
||||
/**
|
||||
* @module 11ty/eleventy/defaultConfig
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback addFilter - Register a global filter.
|
||||
* @param {string} name - Register a template filter by this name.
|
||||
* @param {function} callback - The filter logic.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} config
|
||||
* @property {addFilter} addFilter - Register a new global filter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} defaultConfig
|
||||
* @property {Array<string>} templateFormats - An array of accepted template formats.
|
||||
* @property {string} [pathPrefix='/'] - The directory under which all output files should be written to.
|
||||
* @property {string} [markdownTemplateEngine='liquid'] - Template engine to process markdown files with.
|
||||
* @property {string} [htmlTemplateEngine='liquid'] - Template engine to process html files with.
|
||||
* @property {boolean} [dataTemplateEngine=false] - Changed in v1.0
|
||||
* @property {string} [jsDataFileSuffix='.11tydata'] - File suffix for jsData files.
|
||||
* @property {object} keys
|
||||
* @property {string} [keys.package='pkg'] - Global data property for package.json data
|
||||
* @property {string} [keys.layout='layout']
|
||||
* @property {string} [keys.permalink='permalink']
|
||||
* @property {string} [keys.permalinkRoot='permalinkBypassOutputDir']
|
||||
* @property {string} [keys.engineOverride='templateEngineOverride']
|
||||
* @property {string} [keys.computed='eleventyComputed']
|
||||
* @property {object} dir
|
||||
* @property {string} [dir.input='.']
|
||||
* @property {string} [dir.includes='_includes']
|
||||
* @property {string} [dir.data='_data']
|
||||
* @property {string} [dir.output='_site']
|
||||
* @deprecated handlebarsHelpers
|
||||
* @deprecated nunjucksFilters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default configuration object factory.
|
||||
*
|
||||
* @param {config} config - Eleventy configuration object.
|
||||
* @returns {defaultConfig}
|
||||
*/
|
||||
export default function (config) {
|
||||
let templateConfig = this;
|
||||
|
||||
// Used for the HTML <base>, InputPathToUrl, Image transform plugins
|
||||
let ut = new HtmlTransformer();
|
||||
ut.setUserConfig(config);
|
||||
|
||||
// This needs to be assigned before bundlePlugin is added below.
|
||||
config.htmlTransformer = ut;
|
||||
|
||||
config.addPlugin(bundlePlugin, {
|
||||
bundles: false, // no default bundles included—must be opt-in.
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
// Filter: Maps an input path to output URL
|
||||
config.addPlugin(InputPathToUrlFilterPlugin, {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
let memoizeBench = config.benchmarkManager.get("Configuration");
|
||||
config.addFilter("slug", MemoizeUtil(slugFilter, { name: "slug", bench: memoizeBench }));
|
||||
config.addFilter("slugify", MemoizeUtil(slugifyFilter, { name: "slugify", bench: memoizeBench }));
|
||||
|
||||
// Deprecated, use HtmlBasePlugin instead.
|
||||
// Adds a pathPrefix manually to a URL string
|
||||
config.addFilter("url", function addPathPrefixFilter(url, pathPrefixOverride) {
|
||||
let pathPrefix;
|
||||
if (pathPrefixOverride && typeof pathPrefixOverride === "string") {
|
||||
pathPrefix = pathPrefixOverride;
|
||||
} else {
|
||||
pathPrefix = templateConfig.getPathPrefix();
|
||||
}
|
||||
|
||||
return urlFilter.call(this, url, pathPrefix);
|
||||
});
|
||||
|
||||
config.addFilter("log", (input, ...messages) => {
|
||||
console.log(input, ...messages);
|
||||
return input;
|
||||
});
|
||||
|
||||
config.addFilter("getCollectionItemIndex", function (collection, pageOverride) {
|
||||
return getCollectionItemIndex.call(this, collection, pageOverride);
|
||||
});
|
||||
config.addFilter("getCollectionItem", function (collection, pageOverride, langCode) {
|
||||
return getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, 0);
|
||||
});
|
||||
config.addFilter("getPreviousCollectionItem", function (collection, pageOverride, langCode) {
|
||||
return getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, -1);
|
||||
});
|
||||
config.addFilter("getNextCollectionItem", function (collection, pageOverride, langCode) {
|
||||
return getLocaleCollectionItem.call(this, config, collection, pageOverride, langCode, 1);
|
||||
});
|
||||
|
||||
// Process arbitrary content with transforms
|
||||
config.addFilter(
|
||||
"renderTransforms",
|
||||
async function transformsFilter(content, pageEntryOverride, baseHrefOverride) {
|
||||
return TransformsUtil.runAll(content, pageEntryOverride || this.page, config.transforms, {
|
||||
baseHrefOverride,
|
||||
logger: config.logger,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Run the `htmlTransformer` transform
|
||||
config.addTransform("@11ty/eleventy/html-transformer", async function (content) {
|
||||
return ut.transformContent(this.outputPath, content, this);
|
||||
});
|
||||
|
||||
return {
|
||||
templateFormats: ["liquid", "md", "njk", "html", "11ty.js"],
|
||||
// if your site deploys to a subdirectory, change this
|
||||
pathPrefix: "/",
|
||||
markdownTemplateEngine: "liquid",
|
||||
htmlTemplateEngine: "liquid",
|
||||
|
||||
// Renamed from `jsDataFileSuffix` in 2.0 (and swapped to an Array)
|
||||
// If you remove "" we won’t look for dir/dir.json or file.json
|
||||
dataFileSuffixes: [".11tydata", ""],
|
||||
|
||||
// "index" will look for `directory/index.*` directory data files instead of `directory/directory.*`
|
||||
dataFileDirBaseNameOverride: false,
|
||||
|
||||
keys: {
|
||||
// TODO breaking: use `false` by default
|
||||
package: "pkg", // supports `false`
|
||||
layout: "layout",
|
||||
permalink: "permalink",
|
||||
permalinkRoot: "permalinkBypassOutputDir",
|
||||
engineOverride: "templateEngineOverride",
|
||||
computed: "eleventyComputed",
|
||||
dataSchema: "eleventyDataSchema",
|
||||
},
|
||||
|
||||
// Deprecated, define using `export const directories = {}` instead.
|
||||
// Reference values using `eleventyConfig.directories` instead.
|
||||
dir: {
|
||||
// These values here aren’t used internally either (except by a few tests), instead we’re using `ProjectDirectories.defaults`.
|
||||
// These are kept in place for backwards compat with `eleventyConfig.dir` references in project config code and plugins.
|
||||
input: ".",
|
||||
includes: "_includes",
|
||||
data: "_data",
|
||||
output: "_site",
|
||||
},
|
||||
|
||||
// deprecated, use config.addNunjucksFilter
|
||||
nunjucksFilters: {},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user