@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.4",
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.0.0",
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.1",
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.26.0",
33
- "eslint": "^9.26.0",
34
- "eslint-config-prettier": "^10.1.2",
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.5.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
- /@([a-zA-Z0-9-_]+):([^,\s\n\]'"<>)}#]+)(?=[,\s\n\]'"<>)}#]|$)/g
8
+ /@([\w-]+):([^,\s\]'"<>\\)}#]+)(?=[,\s\]'"<>\\)}#]|$)/g
10
9
 
11
10
  const AT_FILE_ATTRIBUTE_REGEX =
12
- /@file:([^,\s\n\]'"<>)}#]+)(?=[,\s\n\]'"<>)}#]|$)/g
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
- // Process object values in parallel via Promise.all for nested objects
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
- // Quick check to avoid regex processing if there's no @ symbol
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
- for (const match of matches) {
128
- const [fullMatch, attribute, value] = match
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
- // Sort attributes by length to replace longest first
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
- // Find the resolver
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
- // Clear the cache if this is a full build (not incremental)
12
- if (!options.incremental) {
13
- templateContextCache.clear()
14
- }
15
-
16
- let templateContext
17
- const cacheKey = getContextCacheKey(
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
- // Create template context based on whether this is a collection or not
27
- if (page._meta.isCollection) {
28
- templateContext = {
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
  }