@slybridges/kiss 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slybridges/kiss",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Keep It Simple and Static site generator",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"browser-sync": "^3.0.4",
|
|
16
16
|
"chalk": "^4.1.2",
|
|
17
|
-
"cheerio": "^1.
|
|
17
|
+
"cheerio": "^1.1.0",
|
|
18
18
|
"chokidar": "^3.6.0",
|
|
19
19
|
"date-fns": "^4.1.0",
|
|
20
20
|
"fast-glob": "^3.3.3",
|
|
@@ -23,17 +23,17 @@
|
|
|
23
23
|
"lodash": "^4.17.21",
|
|
24
24
|
"marked": "^12.0.2",
|
|
25
25
|
"nunjucks": "^3.2.4",
|
|
26
|
-
"sharp": "^0.34.
|
|
26
|
+
"sharp": "^0.34.2",
|
|
27
27
|
"slugify": "^1.6.6",
|
|
28
28
|
"xml": "^1.0.1",
|
|
29
29
|
"yargs": "^17.7.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@eslint/js": "^9.
|
|
33
|
-
"eslint": "^9.
|
|
34
|
-
"eslint-config-prettier": "^10.1.
|
|
32
|
+
"@eslint/js": "^9.30.1",
|
|
33
|
+
"eslint": "^9.30.1",
|
|
34
|
+
"eslint-config-prettier": "^10.1.5",
|
|
35
35
|
"globals": "^15.15.0",
|
|
36
|
-
"prettier": "^3.
|
|
36
|
+
"prettier": "^3.6.2"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
package/src/helpers.js
CHANGED
|
@@ -3,16 +3,12 @@ const fs = require("fs")
|
|
|
3
3
|
const path = require("path")
|
|
4
4
|
|
|
5
5
|
// @ attribute format: @<attribute>:<value><terminator>
|
|
6
|
-
// terminators: space, comma, newline, end of string, ', ",<, >, ), ], },
|
|
7
|
-
// Regex must have global flag for matchAll to work correctly
|
|
6
|
+
// terminators: space, comma, newline, end of string, ', ",<, >, ), ], }, #, \
|
|
8
7
|
const AT_GENERIC_ATTRIBUTE_REGEX =
|
|
9
|
-
/@([
|
|
8
|
+
/@([\w-]+):([^,\s\]'"<>\\)}#]+)(?=[,\s\]'"<>\\)}#]|$)/g
|
|
10
9
|
|
|
11
10
|
const AT_FILE_ATTRIBUTE_REGEX =
|
|
12
|
-
/@file:([^,\s\
|
|
13
|
-
|
|
14
|
-
// Indexed cached version of collections for faster lookups
|
|
15
|
-
const collectionsCache = new Map()
|
|
11
|
+
/@file:([^,\s\]'"<>\\)}#]+)(?=[,\s\]'"<>\\)}#]|$)/g
|
|
16
12
|
|
|
17
13
|
const computePageId = (inputPath, config) => {
|
|
18
14
|
const imputPathObject = path.parse(inputPath)
|
|
@@ -37,36 +33,15 @@ const computeParentId = (inputPath, config) => {
|
|
|
37
33
|
return computePageId(parentPath, config)
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
// Optimized findCollectionById with caching
|
|
41
36
|
const findCollectionById = (collections, id) => {
|
|
42
37
|
if (id === ".") {
|
|
43
38
|
return collections
|
|
44
39
|
}
|
|
45
|
-
|
|
46
|
-
// Quick cache lookup by ID
|
|
47
|
-
const cacheKey = typeof id === "string" ? id : JSON.stringify(id)
|
|
48
|
-
if (collectionsCache.has(cacheKey)) {
|
|
49
|
-
return collectionsCache.get(cacheKey)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
40
|
const flatCollections = flattenObjects(
|
|
53
41
|
collections,
|
|
54
42
|
(v) => v._type === "collection",
|
|
55
43
|
)
|
|
56
|
-
|
|
57
|
-
const result = flatCollections.find((collection) => collection._id === id)
|
|
58
|
-
|
|
59
|
-
// Cache the result for future lookups
|
|
60
|
-
if (result) {
|
|
61
|
-
collectionsCache.set(cacheKey, result)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return result
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Clear the collections cache - should be called when collections are modified
|
|
68
|
-
const clearCollectionsCache = () => {
|
|
69
|
-
collectionsCache.clear()
|
|
44
|
+
return flatCollections.find((collection) => collection._id === id)
|
|
70
45
|
}
|
|
71
46
|
|
|
72
47
|
const flattenObjects = (obj, predicate) => {
|
|
@@ -402,7 +377,6 @@ module.exports = {
|
|
|
402
377
|
relativeToAbsoluteAttributes,
|
|
403
378
|
sortPageIds,
|
|
404
379
|
sortPages,
|
|
405
|
-
clearCollectionsCache,
|
|
406
380
|
}
|
|
407
381
|
|
|
408
382
|
/** private **/
|
|
@@ -3,19 +3,7 @@ const path = require("path")
|
|
|
3
3
|
|
|
4
4
|
const { AT_GENERIC_ATTRIBUTE_REGEX } = require("../helpers")
|
|
5
5
|
|
|
6
|
-
// Memoization cache for attribute resolution
|
|
7
|
-
const memoCache = new Map()
|
|
8
|
-
|
|
9
|
-
// Cache key generator for memoization
|
|
10
|
-
const getCacheKey = (attribute, value, pageId) =>
|
|
11
|
-
`${pageId}:${attribute}:${value}`
|
|
12
|
-
|
|
13
6
|
const atAttributesContentTransform = (page, options, config, context) => {
|
|
14
|
-
// Clear the cache if this is a full build (not incremental)
|
|
15
|
-
if (!options.incremental) {
|
|
16
|
-
memoCache.clear()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
7
|
// we need to go though all the keys in the page
|
|
20
8
|
// that will be expensive, but YOLO
|
|
21
9
|
for (const objKey in page) {
|
|
@@ -45,6 +33,7 @@ const transformAtAttributesInObjValue = (
|
|
|
45
33
|
config,
|
|
46
34
|
context,
|
|
47
35
|
) => {
|
|
36
|
+
let match
|
|
48
37
|
const resolvers = [
|
|
49
38
|
{ key: "data", handler: dataAttributeResolver },
|
|
50
39
|
{ key: "file", handler: fileAttributeResolver, pageAttribute: "permalink" },
|
|
@@ -55,35 +44,8 @@ const transformAtAttributesInObjValue = (
|
|
|
55
44
|
pageAttribute: "permalink",
|
|
56
45
|
},
|
|
57
46
|
]
|
|
58
|
-
|
|
59
47
|
if (typeof objValue === "object") {
|
|
60
|
-
//
|
|
61
|
-
if (Array.isArray(objValue) && objValue.length > 10) {
|
|
62
|
-
// For large arrays, process in parallel
|
|
63
|
-
const promises = objValue.map(async (item, index) => {
|
|
64
|
-
if (typeof item === "string") {
|
|
65
|
-
return transformAtAttributesInObjValue(
|
|
66
|
-
`${objKey}[${index}]`,
|
|
67
|
-
item,
|
|
68
|
-
page,
|
|
69
|
-
config,
|
|
70
|
-
context,
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
return item
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// This is non-blocking but will still return the result immediately
|
|
77
|
-
// since we're not awaiting the Promise.all
|
|
78
|
-
Promise.all(promises).then((results) => {
|
|
79
|
-
results.forEach((result, index) => {
|
|
80
|
-
objValue[index] = result
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
return objValue
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// For regular objects or small arrays, process sequentially
|
|
48
|
+
// we need to go though all the keys in the object
|
|
87
49
|
for (const key in objValue) {
|
|
88
50
|
if (typeof objValue[key] === "string") {
|
|
89
51
|
let newValue = transformAtAttributesInObjValue(
|
|
@@ -94,49 +56,29 @@ const transformAtAttributesInObjValue = (
|
|
|
94
56
|
context,
|
|
95
57
|
)
|
|
96
58
|
objValue[key] = newValue
|
|
97
|
-
} else if (typeof objValue[key] === "object") {
|
|
98
|
-
objValue[key] = transformAtAttributesInObjValue(
|
|
99
|
-
key,
|
|
100
|
-
objValue[key],
|
|
101
|
-
page,
|
|
102
|
-
config,
|
|
103
|
-
context,
|
|
104
|
-
)
|
|
105
59
|
}
|
|
106
60
|
}
|
|
107
61
|
return objValue
|
|
108
62
|
}
|
|
109
|
-
|
|
110
63
|
if (typeof objValue !== "string") {
|
|
111
64
|
return objValue
|
|
112
65
|
}
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
if (!objValue.includes("@")) {
|
|
116
|
-
return objValue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Use matchAll with the global regex (now fixed in helpers.js)
|
|
120
|
-
const matches = Array.from(objValue.matchAll(AT_GENERIC_ATTRIBUTE_REGEX))
|
|
121
|
-
if (matches.length === 0) {
|
|
122
|
-
return objValue
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Create a map of attributes to avoid duplicates
|
|
66
|
+
// we cannot search and replace things as we go, as some attributes may overlap others (e.g. @id:home and @id:home:fr)
|
|
67
|
+
// so first, we are going to find all the attributes
|
|
126
68
|
let allAttributes = {}
|
|
127
|
-
|
|
128
|
-
|
|
69
|
+
while ((match = AT_GENERIC_ATTRIBUTE_REGEX.exec(objValue))) {
|
|
70
|
+
let [fullMatch, attribute, value] = match
|
|
71
|
+
// using an object to deduplicate attributes
|
|
129
72
|
allAttributes[fullMatch] = { attribute, fullMatch, value }
|
|
130
73
|
}
|
|
131
|
-
|
|
132
|
-
//
|
|
74
|
+
// we sort attributes by their fullMatch length to make sure we replace the longest first
|
|
75
|
+
// this is important to make sure we don't replace a shorter attribute that is part of a longer one
|
|
133
76
|
const sortedAttributes = Object.values(allAttributes).sort(
|
|
134
77
|
(a, b) => b.fullMatch.length - a.fullMatch.length,
|
|
135
78
|
)
|
|
136
|
-
|
|
137
|
-
// Process the attributes
|
|
79
|
+
// now we can go through all the attributes and resolve them
|
|
138
80
|
for (const { attribute, fullMatch, value } of sortedAttributes) {
|
|
139
|
-
//
|
|
81
|
+
// find the resolver
|
|
140
82
|
const resolver = resolvers.find((r) => r.key === attribute)
|
|
141
83
|
if (!resolver) {
|
|
142
84
|
global.logger.error(
|
|
@@ -144,25 +86,12 @@ const transformAtAttributesInObjValue = (
|
|
|
144
86
|
)
|
|
145
87
|
continue
|
|
146
88
|
}
|
|
147
|
-
|
|
148
|
-
// Check memoization cache
|
|
149
|
-
const cacheKey = getCacheKey(attribute, value, page._meta.id)
|
|
150
|
-
let result, error
|
|
151
|
-
|
|
152
|
-
if (memoCache.has(cacheKey)) {
|
|
153
|
-
;[result, error] = memoCache.get(cacheKey)
|
|
154
|
-
} else {
|
|
155
|
-
;[result, error] = resolver.handler(value, page, config, context)
|
|
156
|
-
// Cache the result
|
|
157
|
-
memoCache.set(cacheKey, [result, error])
|
|
158
|
-
}
|
|
159
|
-
|
|
89
|
+
const [result, error] = resolver.handler(value, page, config, context)
|
|
160
90
|
if (error) {
|
|
161
91
|
global.logger.error(`Page '${page._meta.id}' in '${objKey}': ${error}`)
|
|
162
92
|
objValue = objValue.replaceAll(fullMatch, value)
|
|
163
93
|
continue
|
|
164
94
|
}
|
|
165
|
-
|
|
166
95
|
if (resolver.pageAttribute) {
|
|
167
96
|
// result is a page
|
|
168
97
|
// first we check if the page is excluded from write
|
|
@@ -1,47 +1,19 @@
|
|
|
1
1
|
const { findCollectionById } = require("../helpers.js")
|
|
2
2
|
|
|
3
|
-
// Template context cache for memoization
|
|
4
|
-
const templateContextCache = new Map()
|
|
5
|
-
|
|
6
|
-
// Cache key generator for memoization
|
|
7
|
-
const getContextCacheKey = (pageId, collectionId) =>
|
|
8
|
-
`${pageId}:${collectionId || "no-collection"}`
|
|
9
|
-
|
|
10
3
|
const nunjucksContentTransform = (page, options, config, context) => {
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
page._meta.id,
|
|
19
|
-
page._meta.isCollection ? page._meta.id : null,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
// Check if we have a cached context
|
|
23
|
-
if (templateContextCache.has(cacheKey)) {
|
|
24
|
-
templateContext = templateContextCache.get(cacheKey)
|
|
4
|
+
let templateContext = {}
|
|
5
|
+
if (page._meta.isCollection) {
|
|
6
|
+
templateContext = {
|
|
7
|
+
...context,
|
|
8
|
+
collection: findCollectionById(context.collections, page._meta.id),
|
|
9
|
+
...page,
|
|
10
|
+
}
|
|
25
11
|
} else {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
...context,
|
|
30
|
-
collection: findCollectionById(context.collections, page._meta.id),
|
|
31
|
-
...page,
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
templateContext = {
|
|
35
|
-
...context,
|
|
36
|
-
...page,
|
|
37
|
-
}
|
|
12
|
+
templateContext = {
|
|
13
|
+
...context,
|
|
14
|
+
...page,
|
|
38
15
|
}
|
|
39
|
-
|
|
40
|
-
// Cache the template context for future use
|
|
41
|
-
templateContextCache.set(cacheKey, templateContext)
|
|
42
16
|
}
|
|
43
|
-
|
|
44
|
-
// Render the page using Nunjucks
|
|
45
17
|
page._html = config.libs.nunjucks.render(page.layout, templateContext)
|
|
46
18
|
return page
|
|
47
19
|
}
|