1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
import { DepGraph as DependencyGraph } from "dependency-graph";
import debugUtil from "debug";
const debug = debugUtil("Eleventy:TemplateDepGraph");
const COLLECTION_PREFIX = "__collection:";
export class TemplateDepGraph extends DependencyGraph {
static STAGES = ["[basic]", "[userconfig]", "[keys]", "all"];
#configCollectionNames = new Set();
constructor() {
// BREAKING TODO move this back to non-circular with errors
super({ circular: true });
let previous;
// establish stage relationships, all uses keys, keys uses userconfig, userconfig uses tags
for (let stageName of TemplateDepGraph.STAGES.filter(Boolean).reverse()) {
let stageKey = `${COLLECTION_PREFIX}${stageName}`;
if (previous) {
this.uses(previous, stageKey);
}
previous = stageKey;
}
}
uses(from, to) {
this.addDependency(from, to);
}
addTag(tagName, type) {
if (
tagName === "all" ||
(tagName.startsWith("[") && tagName.endsWith("]")) ||
this.#configCollectionNames.has(tagName)
) {
return;
}
if (!type) {
throw new Error(
`Missing tag type for addTag. Expecting one of ${TemplateDepGraph.STAGES.map((entry) => entry.slice(1, -1)).join(" or ")}. Received: ${type}`,
);
}
debug("collection type %o uses tag %o", tagName, type);
this.uses(`${COLLECTION_PREFIX}[${type}]`, `${COLLECTION_PREFIX}${tagName}`);
}
addConfigCollectionName(collectionName) {
if (collectionName === "all") {
return;
}
this.#configCollectionNames.add(collectionName);
// Collection relationships to `[userconfig]` are added last, in unfilteredOrder()
}
cleanupCollectionNames(collectionNames = []) {
let s = new Set(collectionNames);
if (s.has("[userconfig]")) {
return collectionNames;
}
let hasAnyConfigCollections = collectionNames.find((name) => {
if (this.#configCollectionNames.has(name)) {
return true;
}
return false;
});
if (hasAnyConfigCollections) {
s.add("[userconfig]");
}
return Array.from(s);
}
addTemplate(filePath, consumes = [], publishesTo = []) {
// Move to the beginning if it doesn’t consume anything
if (consumes.length === 0) {
this.uses(`${COLLECTION_PREFIX}[basic]`, filePath);
}
consumes = this.cleanupCollectionNames(consumes);
publishesTo = this.cleanupCollectionNames(publishesTo);
// Can’t consume AND publish to `all` simultaneously
let consumesAll = consumes.includes("all");
if (consumesAll) {
publishesTo = publishesTo.filter((entry) => entry !== "all");
}
debug("%o consumes %o and publishes to %o", filePath, consumes, publishesTo);
for (let collectionName of publishesTo) {
if (!consumesAll) {
let tagType = "basic";
let consumesUserConfigCollection = consumes.includes("[userconfig]");
if (consumesUserConfigCollection) {
// must finish before [keys]
tagType = "keys";
}
this.addTag(collectionName, tagType);
}
this.uses(`${COLLECTION_PREFIX}${collectionName}`, filePath);
}
for (let collectionName of consumes) {
this.uses(filePath, `${COLLECTION_PREFIX}${collectionName}`);
let stageIndex = TemplateDepGraph.STAGES.indexOf(collectionName);
let nextStage = stageIndex > 0 ? TemplateDepGraph.STAGES[stageIndex + 1] : undefined;
if (nextStage) {
this.uses(`${COLLECTION_PREFIX}${nextStage}`, filePath);
}
}
}
addDependency(from, to) {
if (!this.hasNode(from)) {
this.addNode(from);
}
if (!this.hasNode(to)) {
this.addNode(to);
}
super.addDependency(from, to);
}
unfilteredOrder() {
// these need to be added last, after the template map has been added (see addConfigCollectionName)
for (let collectionName of this.#configCollectionNames) {
this.uses(`${COLLECTION_PREFIX}[keys]`, `${COLLECTION_PREFIX}${collectionName}`);
}
return super.overallOrder();
}
overallOrder() {
let unfiltered = this.unfilteredOrder();
let filtered = unfiltered.filter((entry) => {
if (entry === `${COLLECTION_PREFIX}[keys]`) {
return true;
}
return !entry.startsWith(`${COLLECTION_PREFIX}[`) && !entry.endsWith("]");
});
let allKey = `${COLLECTION_PREFIX}all`;
// Add another collections.all entry to the end (if not already the last one)
if (filtered[filtered.length - 1] !== allKey) {
filtered.push(allKey);
}
return filtered;
}
}
|