coralite 0.2.1 → 0.3.0

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.
@@ -37,9 +37,8 @@ jobs:
37
37
  - uses: actions/checkout@v4
38
38
  - uses: actions/setup-node@v4
39
39
  with:
40
- node-version: 20
40
+ node-version: 22
41
41
  registry-url: https://registry.npmjs.org/
42
- - run: npm ci
43
42
  - run: npm publish
44
43
  env:
45
44
  NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/bin/coralite.js CHANGED
@@ -43,14 +43,14 @@ const htmlPages = await getHTML({
43
43
  })
44
44
 
45
45
  /** @type {Object.<string, CoraliteModule>} */
46
- const components = {}
46
+ const coraliteModules = {}
47
47
 
48
48
  // create components
49
49
  for (let i = 0; i < htmlComponents.length; i++) {
50
50
  const html = htmlComponents[i]
51
- const component = parseModule(html.content)
51
+ const coraliteModule = parseModule(html.content)
52
52
 
53
- components[component.id] = component
53
+ coraliteModules[coraliteModule.id] = coraliteModule
54
54
  }
55
55
 
56
56
  for (let i = 0; i < htmlPages.length; i++) {
@@ -65,8 +65,8 @@ for (let i = 0; i < htmlPages.length; i++) {
65
65
  const component = await createComponent({
66
66
  id: customElement.name,
67
67
  values: customElement.attribs,
68
- customElementSlots: document.customElementSlots,
69
- components,
68
+ element: customElement,
69
+ components: coraliteModules,
70
70
  document
71
71
  })
72
72
 
package/lib/parse.js CHANGED
@@ -13,10 +13,10 @@ import { invalidCustomTags, validTags } from './tags.js'
13
13
  * CoraliteModule,
14
14
  * CoraliteTextNode,
15
15
  * CoraliteElement,
16
- * CoraliteSlotElement,
17
16
  * CoraliteModuleSlotElement,
18
17
  * CoraliteDocumentTokens,
19
18
  * CoraliteDocumentRoot,
19
+ * CoraliteAnyNode,
20
20
  * } from '#types'
21
21
  */
22
22
 
@@ -32,7 +32,7 @@ const customElementTagTokenRegExp = /^[^-].*[-._a-z0-9\u00B7\u00C0-\u00D6\u00D8-
32
32
  * @returns {CoraliteDocument} An object representing the parsed document structure
33
33
  *
34
34
  * @example
35
- * ```javascript
35
+ * ```
36
36
  * // Example usage:
37
37
  * const html = {
38
38
  * name: 'index.html',
@@ -57,10 +57,8 @@ export function parseHTMLDocument (html, path) {
57
57
  children: []
58
58
  }
59
59
  // stack to keep track of current element hierarchy
60
- const stack = []
60
+ const stack = [root]
61
61
  const customElements = []
62
- /** @type {Object.<string, CoraliteSlotElement[]>} */
63
- const customElementSlots = {}
64
62
 
65
63
  const parser = new Parser({
66
64
  onprocessinginstruction (name, data) {
@@ -71,25 +69,7 @@ export function parseHTMLDocument (html, path) {
71
69
  })
72
70
  },
73
71
  onopentag (originalName, attributes) {
74
- const element = createElement(originalName, attributes, customElements, stack, root)
75
-
76
- if (attributes.slot) {
77
- const customElement = customElements[customElements.length - 1]
78
- const slot = {
79
- name: attributes.slot,
80
- customElement,
81
- element
82
- }
83
-
84
- if (!customElementSlots[customElement.name]) {
85
- customElementSlots[customElement.name] = [slot]
86
- } else {
87
- customElementSlots[customElement.name].unshift(slot)
88
- }
89
-
90
- // remove slot attribute
91
- delete attributes.slot
92
- }
72
+ createElement(originalName, attributes, customElements, stack)
93
73
  },
94
74
  ontext (text) {
95
75
  if (text.trim()) {
@@ -100,10 +80,10 @@ export function parseHTMLDocument (html, path) {
100
80
  // remove current element from stack as we're done with its children
101
81
  stack.pop()
102
82
  },
103
- oncomment (comment) {
83
+ oncomment (data) {
104
84
  stack[stack.length - 1].children.push({
105
85
  type: 'comment',
106
- data: comment,
86
+ data,
107
87
  parent: stack[stack.length - 1]
108
88
  })
109
89
  }
@@ -112,12 +92,32 @@ export function parseHTMLDocument (html, path) {
112
92
  parser.write(html.content)
113
93
  parser.end()
114
94
 
95
+ for (let i = 0; i < customElements.length; i++) {
96
+ const customElement = customElements[i]
97
+
98
+ for (let i = 0; i < customElement.children.length; i++) {
99
+ const childNode = customElement.children[i]
100
+ let slotName = 'default'
101
+
102
+ if (childNode.attribs && childNode.attribs.slot) {
103
+ slotName = childNode.attribs.slot
104
+
105
+ // clean up slot attribute
106
+ delete childNode.attribs.slot
107
+ }
108
+
109
+ customElement.slots.push({
110
+ name: slotName,
111
+ node: childNode
112
+ })
113
+ }
114
+ }
115
+
115
116
  return {
116
117
  name: html.name,
117
118
  parentPath: html.parentPath,
118
119
  root,
119
120
  customElements,
120
- customElementSlots,
121
121
  path
122
122
  }
123
123
  }
@@ -145,6 +145,7 @@ export function parseHTMLMeta (string) {
145
145
  const stack = []
146
146
  /** @type {Object.<string, CoraliteToken[]>} */
147
147
  const meta = {}
148
+ let hasHead = false
148
149
 
149
150
  const parser = new Parser({
150
151
  onopentag (name, attributes) {
@@ -162,6 +163,7 @@ export function parseHTMLMeta (string) {
162
163
  },
163
164
  onclosetag (name) {
164
165
  if (name === 'head') {
166
+ hasHead = true
165
167
  // end on closing head tag
166
168
  return parser.end()
167
169
  }
@@ -173,6 +175,10 @@ export function parseHTMLMeta (string) {
173
175
  parser.write(string)
174
176
  parser.end()
175
177
 
178
+ if (!hasHead) {
179
+ throw new Error('Document requires a head element')
180
+ }
181
+
176
182
  return meta
177
183
  }
178
184
 
@@ -184,7 +190,7 @@ export function parseHTMLMeta (string) {
184
190
  * @returns {CoraliteModule} - Parsed module information, including template, script, tokens, and slot configurations
185
191
  *
186
192
  * @example
187
- * ```javascript
193
+ * ```
188
194
  * // Example usage:
189
195
  * const html = `<template id="home">
190
196
  * <slot name="default">Hello</slot>
@@ -216,7 +222,7 @@ export function parseModule (string) {
216
222
  children: []
217
223
  }
218
224
  // stack to keep track of current element hierarchy
219
- const stack = []
225
+ const stack = [root]
220
226
  const customElements = []
221
227
  /** @type {Object.<string, Object.<string,CoraliteModuleSlotElement>>} */
222
228
  const slotElements = {}
@@ -230,7 +236,7 @@ export function parseModule (string) {
230
236
 
231
237
  const parser = new Parser({
232
238
  onopentag (originalName, attributes) {
233
- const element = createElement(originalName, attributes, customElements, stack, root)
239
+ const element = createElement(originalName, attributes, customElements, stack)
234
240
  const attributeNames = Object.keys(attributes)
235
241
 
236
242
  // collect tokens
@@ -315,10 +321,10 @@ export function parseModule (string) {
315
321
  console.log(csb)
316
322
  },
317
323
 
318
- oncomment (comment) {
324
+ oncomment (data) {
319
325
  stack[stack.length - 1].children.push({
320
326
  type: 'comment',
321
- data: comment,
327
+ data,
322
328
  parent: stack[stack.length - 1]
323
329
  })
324
330
  }
@@ -374,11 +380,12 @@ export function parseModule (string) {
374
380
  * @param {Object.<string, (string | (CoraliteTextNode | CoraliteElement)[])>} options.values - Token values available for replacement
375
381
  * @param {Object.<string, CoraliteModuleSlotElement[]>} [options.customElementSlots = {}] - Custom slots and their configurations for the component
376
382
  * @param {Object.<string, CoraliteModule>} options.components - Mapping of component IDs to their module definitions
383
+ * @param {CoraliteElement} options.element - Mapping of component IDs to their module definitions
377
384
  * @param {CoraliteDocument} options.document - Current document being processed
378
385
  * @returns {Promise<CoraliteElement>}
379
386
  *
380
387
  * @example
381
- * ```javascript
388
+ * ```
382
389
  * // Example usage:
383
390
  * const component = await createComponent({
384
391
  * id: 'my-component',
@@ -405,7 +412,7 @@ export function parseModule (string) {
405
412
  export async function createComponent ({
406
413
  id,
407
414
  values,
408
- customElementSlots = {},
415
+ element,
409
416
  components,
410
417
  document
411
418
  }) {
@@ -429,8 +436,9 @@ export async function createComponent ({
429
436
  if (value == null) {
430
437
  if (component.script) {
431
438
  computedTokens.push({
432
- type: 'element',
439
+ type: 'attribute',
433
440
  node: item.element,
441
+ attribute: item.name,
434
442
  name: token.name,
435
443
  content: token.content
436
444
  })
@@ -487,8 +495,7 @@ export async function createComponent ({
487
495
  const value = values[computedToken.name]
488
496
 
489
497
  if (computedToken.type === 'attribute') {
490
- // replace token string
491
- node.attribs[computedToken.name] = node.attribs[computedToken.name].replace(computedToken.content, value)
498
+ node.attribs[computedToken.attribute] = node.attribs[computedToken.attribute].replace(computedToken.content, value)
492
499
  } else {
493
500
  if (Array.isArray(value)) {
494
501
  // inject nodes
@@ -508,7 +515,7 @@ export async function createComponent ({
508
515
  parent: node.parent
509
516
  }
510
517
  )
511
- } else if (node.data) {
518
+ } else {
512
519
  // replace token string
513
520
  node.data = node.data.replace(computedToken.content, value)
514
521
  }
@@ -525,7 +532,7 @@ export async function createComponent ({
525
532
  const component = await createComponent({
526
533
  id: customElement.name,
527
534
  values: Object.assign(values, customElement.attribs),
528
- customElementSlots,
535
+ element: customElement,
529
536
  components,
530
537
  document
531
538
  })
@@ -541,57 +548,46 @@ export async function createComponent ({
541
548
  children.splice(childIndex, 1, ...component.children)
542
549
  }
543
550
 
544
- const slots = customElementSlots[id]
551
+ const slots = component.slotElements[id]
545
552
 
553
+ // replace slot content
546
554
  if (slots) {
547
- const componentSlots = component.slotElements[id]
548
- const usedSlots = {}
549
- const slotIndexes = {}
550
-
551
- for (let i = 0; i < slots.length; i++) {
552
- const slot = slots[i]
553
- const name = slot.name
554
-
555
- if (componentSlots[name]) {
556
- const componentSlot = componentSlots[name]
557
- const parentChildren = componentSlot.element.parent.children
558
- let slotIndex = slotIndexes[name]
559
- let deleteCount = 0
560
-
561
- if (slotIndex == null) {
562
- slotIndex = parentChildren.indexOf(componentSlot.element, componentSlot.element.parentChildIndex)
563
- deleteCount = 1
564
- slotIndexes[name] = slotIndex
565
- }
555
+ const slotChildren = {}
556
+ const slotNames = Object.keys(slots)
566
557
 
567
- parentChildren.splice(slotIndex, deleteCount, slot.element)
568
- usedSlots[slot.name] = true
569
- }
558
+ // sort slot content by name
559
+ for (let i = 0; i < slotNames.length; i++) {
560
+ const slotName = slotNames[i]
561
+ slotChildren[slotName] = []
570
562
  }
571
563
 
572
- for (const name in componentSlots) {
573
- if (Object.prototype.hasOwnProperty.call(componentSlots, name)) {
574
- if (!usedSlots[name]) {
575
- const componentSlot = componentSlots[name]
576
- const element = componentSlot.element
577
- const parent = element.parent
578
- const children = element.children || [{
579
- type: 'text',
580
- data: ''
581
- }]
582
- const slotIndex = parent.children.indexOf(componentSlot.element, componentSlot.element.parentChildIndex)
583
-
584
-
585
- for (let i = 0; i < children.length; i++) {
586
- const childNode = children[i]
587
- childNode.parent = parent
588
- }
564
+ // insert slot content by name
565
+ if (element) {
566
+ for (let i = 0; i < element.slots.length; i++) {
567
+ const elementSlotContent = element.slots[i]
568
+ const slotName = elementSlotContent.name
569
+ const slot = slots[slotName]
589
570
 
590
- // replace unused slots with default values
591
- parent.children.splice(slotIndex, 1, ...children)
571
+ if (slot) {
572
+ slotChildren[slotName].push(elementSlotContent.node)
592
573
  }
593
574
  }
594
575
  }
576
+
577
+ for (let i = 0; i < slotNames.length; i++) {
578
+ const slotName = slotNames[i]
579
+ let slotNodes = slotChildren[slotName]
580
+ const slot = slots[slotName]
581
+ const slotIndex = slot.element.parent.children.indexOf(slot.element)
582
+
583
+ if (!slotNodes.length) {
584
+ // set default content
585
+ slotNodes = slot.element.children || []
586
+ }
587
+
588
+ // replace slot element with content
589
+ slot.element.parent.children.splice(slotIndex, 1, ...slotNodes)
590
+ }
595
591
  }
596
592
 
597
593
  return template
@@ -607,7 +603,7 @@ export async function createComponent ({
607
603
  * @returns {Promise<Object.<string,(string|(CoraliteElement|CoraliteTextNode)[])>>}
608
604
  *
609
605
  * @example
610
- * ```javascript
606
+ * ```
611
607
  * // Example usage:
612
608
  * const parsedResult = await parseScript({
613
609
  * id: 'my-component',
@@ -750,18 +746,20 @@ function addMetadata (meta, name, content) {
750
746
  * @param {string} name
751
747
  * @param {Object.<string, string>} attributes
752
748
  * @param {CoraliteElement[]} customElements
753
- * @param {CoraliteElement[]} stack
754
- * @param {CoraliteDocumentRoot} root
749
+ * @param {CoraliteAnyNode[]} stack
755
750
  */
756
- function createElement (name, attributes, customElements, stack, root) {
751
+ function createElement (name, attributes, customElements, stack) {
757
752
  const sanitisedName = name.toLowerCase()
758
- const parentIndex = stack.length - 1
753
+ const parent = stack[stack.length - 1]
754
+
759
755
  /** @type {CoraliteElement} */
760
756
  const element = {
761
757
  type: 'tag',
762
758
  name: sanitisedName,
763
759
  attribs: attributes,
764
- children: []
760
+ children: [],
761
+ parent,
762
+ parentChildIndex: parent.children.length
765
763
  }
766
764
 
767
765
  if (!validTags[sanitisedName]) {
@@ -772,23 +770,14 @@ function createElement (name, attributes, customElements, stack, root) {
772
770
  if (customElementTagRegExp.test(sanitisedName)) {
773
771
  // store custom elements
774
772
  customElements.push(element)
773
+ element.slots = []
775
774
  } else {
776
775
  throw new Error('Invalid custom element tag name: "' + sanitisedName + '" https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name')
777
776
  }
778
777
  }
779
778
 
780
- if (parentIndex === -1) {
781
- root.children.push(element)
782
- element.parent = root
783
- } else {
784
- const parent = stack[parentIndex]
785
-
786
- element.parent = parent
787
- element.parentChildIndex = parent.children.length
788
-
789
- // add element to its parent's children
790
- parent.children.push(element)
791
- }
779
+ // add element to its parent's children
780
+ parent.children.push(element)
792
781
 
793
782
  // push element to stack as it may have children
794
783
  stack.push(element)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "HTML modules static site generator",
5
5
  "main": "./lib/coralite.js",
6
6
  "type": "module",
package/types/index.js CHANGED
@@ -66,7 +66,7 @@
66
66
  * @property {'tag'} type - Element type
67
67
  * @property {string} name - Tag name
68
68
  * @property {Object.<string, string>} attribs - Element attributes
69
- * @property {(CoraliteElement | CoraliteTextNode)[]} children - Child nodes of the element
69
+ * @property {CoraliteAnyNode[]} children - Child nodes of the element
70
70
  * @property {CoraliteElement | CoraliteDocumentRoot} parent - Parent element
71
71
  * @property {number} [parentChildIndex] - Position in parent's child list
72
72
  */
@@ -79,6 +79,10 @@
79
79
  * @property {CoraliteElement} parent - Parent element of the text node
80
80
  */
81
81
 
82
+ /**
83
+ * @typedef {CoraliteDirective | CoraliteElement | CoraliteTextNode} CoraliteAnyNode
84
+ */
85
+
82
86
  /**
83
87
  * @typedef {Object} CoraliteSlotElement
84
88
  * @property {string} name - Slot's unique identifier
@@ -96,7 +100,7 @@
96
100
  /**
97
101
  * @typedef {Object} CoraliteDocumentRoot
98
102
  * @property {'root'} type - Node type
99
- * @property {(CoraliteDirective | CoraliteElement | CoraliteTextNode)[]} children - Document list
103
+ * @property {CoraliteAnyNode[]} children - Document list
100
104
  */
101
105
 
102
106
  /**
@@ -1,11 +0,0 @@
1
- Based on the provided Git diff above, create a git commit message by strictly following these rules:
2
-
3
- 1. Use one of the predefined prefixes (feat, fix, perf, build, chore, ci, docs, style, refactor, test, types, wip) or a custom prefix for non-changelog related tasks.
4
- 2. The commit message must have a header, which includes a type and subject, separated by a colon.
5
- 3. Follow the imperative present tense for both the subject and body of the commit message:
6
- - Use concise and descriptive subjects.
7
- - Avoid including implementation details in the body unless necessary for understanding context.
8
- 4. Include an optional footer to provide information about breaking changes using the format "BREAKING CHANGE:" followed by additional details.
9
- 5. Limit line length to 72 characters and maintain consistent use of whitespace for better readability and uniformity.
10
- 6. If changes are about JSDoc, the prefix should be 'types'.
11
- 7. Use any suggestion below this to help with the context.
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
- <excludeFolder url="file://$MODULE_DIR$/temp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
@@ -1,6 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
- </profile>
6
- </component>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="JavaScriptLibraryMappings">
4
- <includedPredefinedLibrary name="Node.js Core" />
5
- </component>
6
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/coralite.iml" filepath="$PROJECT_DIR$/.idea/coralite.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>
@@ -1,5 +0,0 @@
1
- {
2
- "cSpell.words": [
3
- "coralite"
4
- ]
5
- }