coralite 0.5.1 → 0.6.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.
package/bin/coralite.js CHANGED
@@ -1,17 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { render } from 'dom-serializer'
4
- import { getHTML, parseHTMLDocument, parseModule, createComponent, getSubDirectory } from '#lib'
3
+ import { getSubDirectory, getPkg, coralite } from '#lib'
5
4
  import { Command } from 'commander'
6
5
  import { join } from 'node:path'
7
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs'
6
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
7
+ import kleur from 'kleur'
8
8
 
9
- /**
10
- * @import { CoraliteModule } from '#types'
11
- */
12
-
13
- const pkgPath = new URL('../package.json', import.meta.url)
14
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
9
+ const pkg = await getPkg()
15
10
  const program = new Command()
16
11
 
17
12
  program
@@ -21,7 +16,8 @@ program
21
16
  .requiredOption('-t, --templates <path>', 'Path to templates directory')
22
17
  .requiredOption('-p, --pages <path>', 'Path to pages directory')
23
18
  .requiredOption('-o, --output <path>', 'Output directory for the generated site')
24
- .option('-d, --dry', 'Run in dry-run mode')
19
+ .option('-i, --ignore-attribute <key=value...>', 'Ignore elements by attribute name value pair', [])
20
+ .option('-d, --dry-run', 'Run in dry-run mode')
25
21
 
26
22
  program.parse(process.argv)
27
23
  program.on('error', (err) => {
@@ -29,81 +25,52 @@ program.on('error', (err) => {
29
25
  })
30
26
 
31
27
  const options = program.opts()
32
- const templatesPath = options.templates
33
- const pagesPath = options.pages
34
- const outputDir = options.output
35
- const dryRun = options.dry
36
-
37
- const htmlTemplates = await getHTML({
38
- path: templatesPath,
39
- recursive: true
40
- })
41
- const htmlPages = await getHTML({
42
- path: pagesPath,
43
- recursive: true
44
- })
28
+ const pages = options.pages
29
+ const output = options.output
30
+ const ignoreByAttribute = []
45
31
 
46
- /** @type {Object.<string, CoraliteModule>} */
47
- const coraliteModules = {}
32
+ for (let i = 0; i < options.ignoreAttribute.length; i++) {
33
+ const pair = options.ignoreAttribute[i].split('=')
48
34
 
49
- // create templates
50
- for (let i = 0; i < htmlTemplates.length; i++) {
51
- const html = htmlTemplates[i]
52
- const coraliteModule = parseModule(html.content)
35
+ if (pair.length !== 2) {
36
+ throw new Error('Ignore attribute "' + pair[0] + '" expected a value but found none')
37
+ }
53
38
 
54
- coraliteModules[coraliteModule.id] = coraliteModule
39
+ ignoreByAttribute.push(pair)
55
40
  }
56
41
 
57
- for (let i = 0; i < htmlPages.length; i++) {
58
- const html = htmlPages[i]
59
- const document = parseHTMLDocument(html, {
60
- pages: pagesPath,
61
- templates: templatesPath
62
- })
63
-
64
- for (let i = 0; i < document.customElements.length; i++) {
65
- const customElement = document.customElements[i]
66
- const component = await createComponent({
67
- id: customElement.name,
68
- values: customElement.attribs,
69
- element: customElement,
70
- components: coraliteModules,
71
- document
72
- })
42
+ const documents = await coralite({
43
+ templates: options.templates,
44
+ pages,
45
+ ignoreByAttribute
46
+ })
73
47
 
74
- for (let i = 0; i < component.children.length; i++) {
75
- // update component parent
76
- component.children[i].parent = customElement.parent
77
- }
78
- const index = customElement.parent.children.indexOf(customElement, customElement.parentChildIndex)
79
- // replace custom element with template
80
- customElement.parent.children.splice(index, 1, ...component.children)
81
- }
48
+ if (options.dryRun) {
49
+ const PAD = ' '
50
+ const border = '─'.repeat(Math.min(process.stdout.columns, 36) / 2)
82
51
 
83
- // render document
84
- // @ts-ignore
85
- const content = render(document.root)
52
+ for (let i = 0; i < documents.length; i++) {
53
+ const document = documents[i]
86
54
 
87
- if (!dryRun) {
55
+ process.stdout.write('\n' + PAD + kleur.green('Document is ready!\n\n'))
56
+ process.stdout.write(PAD + `${kleur.bold('- Path:')} ${join(document.item.parentPath, document.item.name)}\n`)
57
+ process.stdout.write(PAD + `${kleur.bold('- Built in:')} ${Math.floor(document.duration)}ms\n\n`)
58
+ process.stdout.write(border + kleur.inverse(' Content start ') + border + '\n\n')
59
+ process.stdout.write(document.html)
60
+ process.stdout.write('\n\n' + border + kleur.inverse(' Content end ') + border + '\n')
61
+ }
62
+ } else {
63
+ for (let i = 0; i < documents.length; i++) {
64
+ const document = documents[i]
88
65
  // get pages sub directory
89
- const subDir = getSubDirectory(pagesPath, html.parentPath)
90
- const dir = join(outputDir, subDir)
91
-
92
- try {
93
- if (!existsSync(dir)) {
94
- // create directory
95
- mkdirSync(dir)
96
- }
66
+ const subDir = getSubDirectory(pages, document.item.parentPath)
67
+ const dir = join(output, subDir)
97
68
 
98
- writeFileSync(join(dir, html.name), content)
99
- // file written successfully
100
- } catch (err) {
101
- console.error(err)
69
+ if (!existsSync(dir)) {
70
+ // create directory
71
+ mkdirSync(dir)
102
72
  }
103
- } else {
104
- console.log('Document')
105
- console.log('Path: ' + join(html.parentPath, html.name))
106
- console.log('Content')
107
- console.log(content)
73
+
74
+ writeFileSync(join(dir, document.item.name), document.html)
108
75
  }
109
76
  }
package/eslint.config.js CHANGED
@@ -110,7 +110,8 @@ export default [
110
110
  {
111
111
  ignores: [
112
112
  '**/dist/',
113
- '**/.history/'
113
+ '**/.history/',
114
+ '**/playwright-report/'
114
115
  ]
115
116
  }
116
117
  ]
package/lib/coralite.js CHANGED
@@ -1,5 +1,13 @@
1
+ import { createComponent, parseHTMLDocument, parseModule, getHTML } from '#lib'
2
+ import render from 'dom-serializer'
3
+
1
4
  /**
2
- * @import { CoraliteElement, CoraliteTextNode, CoraliteAggregateTemplate } from '#types'
5
+ * @import {
6
+ * CoraliteElement,
7
+ * CoraliteTextNode,
8
+ * CoraliteAggregateTemplate,
9
+ * CoraliteAnyNode,
10
+ * CoraliteModule } from '#types'
3
11
  */
4
12
 
5
13
  /**
@@ -7,6 +15,11 @@
7
15
  * The HTML module code is run in the `parseScript` function in parse.js
8
16
  */
9
17
 
18
+ /**
19
+ * @callback DefineComponentSlot
20
+ * @param {CoraliteAnyNode[]} nodes
21
+ */
22
+
10
23
  /**
11
24
  * @type {Object.<string, string>}
12
25
  */
@@ -17,8 +30,8 @@ export const tokens = {}
17
30
  *
18
31
  * @param {Object} options
19
32
  * @param {string} [options.id] - Optional component id, if not defined, the id will be extracted from the first top level element with the id attribute
20
- * @param {Object.<string, (string | Function)>} options.tokens - Token names and values are either strings or functions representing the corresponding tokens' content or behavior.
21
- * @param {Object.<string, Function>} options.slots - Middleware for slot content
33
+ * @param {Object.<string, (string | Function)>} [options.tokens] - Token names and values are either strings or functions representing the corresponding tokens' content or behavior.
34
+ * @param {Object.<string, DefineComponentSlot>} [options.slots] - Middleware for slot content
22
35
  * @returns {Promise<Object.<string, string>>}
23
36
  */
24
37
  export async function defineComponent (options) {
@@ -37,3 +50,83 @@ export async function defineComponent (options) {
37
50
  export async function aggregate (options) {
38
51
  return []
39
52
  }
53
+
54
+ /**
55
+ * @param {Object} options
56
+ * @param {string} options.templates
57
+ * @param {string} options.pages
58
+ * @param {Array<string[]>} [options.ignoreByAttribute]
59
+ */
60
+ export async function coralite ({
61
+ templates,
62
+ pages,
63
+ ignoreByAttribute
64
+ }) {
65
+ const startTime = performance.now()
66
+ const htmlTemplates = await getHTML({
67
+ path: templates,
68
+ recursive: true
69
+ })
70
+ const htmlPages = await getHTML({
71
+ path: pages,
72
+ recursive: true
73
+ })
74
+
75
+ /** @type {Object.<string, CoraliteModule>} */
76
+ const coraliteModules = {}
77
+ const documents = []
78
+
79
+ // create templates
80
+ for (let i = 0; i < htmlTemplates.length; i++) {
81
+ const html = htmlTemplates[i]
82
+ const coraliteModule = parseModule(html.content)
83
+
84
+ coraliteModules[coraliteModule.id] = coraliteModule
85
+ }
86
+
87
+ for (let i = 0; i < htmlPages.length; i++) {
88
+ const html = htmlPages[i]
89
+ const document = parseHTMLDocument(html, {
90
+ pages,
91
+ templates
92
+ }, ignoreByAttribute)
93
+
94
+ for (let i = 0; i < document.customElements.length; i++) {
95
+ const customElement = document.customElements[i]
96
+ const component = await createComponent({
97
+ id: customElement.name,
98
+ values: customElement.attribs,
99
+ element: customElement,
100
+ components: coraliteModules,
101
+ document
102
+ })
103
+
104
+ if (!component) {
105
+ // skip if no component
106
+ continue
107
+ }
108
+
109
+ for (let i = 0; i < component.children.length; i++) {
110
+ // update component parent
111
+ component.children[i].parent = customElement.parent
112
+ }
113
+ const index = customElement.parent.children.indexOf(customElement, customElement.parentChildIndex)
114
+ // replace custom element with template
115
+ customElement.parent.children.splice(index, 1, ...component.children)
116
+ }
117
+
118
+ // render document
119
+ // @ts-ignore
120
+ const result = render(document.root)
121
+
122
+ documents.push({
123
+ item: document,
124
+ html: result,
125
+ duration: performance.now() - startTime
126
+ })
127
+ }
128
+
129
+ return documents
130
+ }
131
+
132
+ export default coralite
package/lib/get-pkg.js ADDED
@@ -0,0 +1,22 @@
1
+ import { readFile } from 'node:fs/promises'
2
+
3
+ /**
4
+ * @typedef {Object} PackageJson
5
+ * @property {string} name
6
+ * @property {string} description
7
+ * @property {string} version
8
+ */
9
+
10
+ /**
11
+ * Get package.json
12
+ * @return {Promise<PackageJson>}
13
+ */
14
+ export async function getPkg () {
15
+ try {
16
+ const pkgPath = await readFile(new URL('../package.json', import.meta.url), { encoding: 'utf8' })
17
+
18
+ return JSON.parse(pkgPath)
19
+ } catch (err) {
20
+ console.error(err.message)
21
+ }
22
+ }
package/lib/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import getHTML from './get-html.js'
2
+ import coralite from './coralite.js'
2
3
 
3
4
  export * from './parse.js'
4
5
  export * from './path-utils.js'
6
+ export * from './get-pkg.js'
5
7
 
6
8
  export {
7
- getHTML
9
+ getHTML,
10
+ coralite
8
11
  }
package/lib/parse.js CHANGED
@@ -31,6 +31,7 @@ const customElementTagTokenRegExp = /^[^-].*[-._a-z0-9\u00B7\u00C0-\u00D6\u00D8-
31
31
  *
32
32
  * @param {HTMLData} html - The HTML data containing the content to parse
33
33
  * @param {CoralitePath} path - The path object containing the file path information
34
+ * @param {Array<string[]>} [ignoreByAttribute] - Ignore element with attribute name value pair
34
35
  * @returns {CoraliteDocument} An object representing the parsed document structure
35
36
  *
36
37
  * @example
@@ -51,13 +52,14 @@ const customElementTagTokenRegExp = /^[^-].*[-._a-z0-9\u00B7\u00C0-\u00D6\u00D8-
51
52
  * // document.root will contain parsed elements and text nodes
52
53
  * ```
53
54
  */
54
- export function parseHTMLDocument (html, path) {
55
+ export function parseHTMLDocument (html, path, ignoreByAttribute) {
55
56
  // root element reference
56
57
  /** @type {CoraliteDocumentRoot} */
57
58
  const root = {
58
59
  type: 'root',
59
60
  children: []
60
61
  }
62
+
61
63
  // stack to keep track of current element hierarchy
62
64
  /** @type {CoraliteContentNode[]} */
63
65
  const stack = [root]
@@ -75,6 +77,16 @@ export function parseHTMLDocument (html, path) {
75
77
  const parent = stack[stack.length - 1]
76
78
  const element = createElement(originalName, attributes, customElements, parent)
77
79
 
80
+ if (ignoreByAttribute) {
81
+ for (let i = 0; i < ignoreByAttribute.length; i++) {
82
+ const [key, value] = ignoreByAttribute[i]
83
+
84
+ if (attributes[key] && attributes[key].includes(value)) {
85
+ element.remove = true
86
+ }
87
+ }
88
+ }
89
+
78
90
  // push element to stack as it may have children
79
91
  stack.push(element)
80
92
  },
@@ -84,6 +96,15 @@ export function parseHTMLDocument (html, path) {
84
96
  createTextNode(text, parent)
85
97
  },
86
98
  onclosetag () {
99
+ const element = stack[stack.length - 1]
100
+
101
+ // @ts-ignore
102
+ if (element.remove) {
103
+ // remove element from tree
104
+ // @ts-ignore
105
+ element.parent.children.pop()
106
+ }
107
+
87
108
  // remove current element from stack as we're done with its children
88
109
  stack.pop()
89
110
  },
@@ -400,7 +421,7 @@ export function parseModule (string) {
400
421
  * @param {Object.<string, CoraliteModule>} options.components - Mapping of component IDs to their module definitions
401
422
  * @param {CoraliteElement} [options.element] - Mapping of component IDs to their module definitions
402
423
  * @param {CoraliteDocument} options.document - Current document being processed
403
- * @returns {Promise<CoraliteElement>}
424
+ * @returns {Promise<CoraliteElement | void>}
404
425
  *
405
426
  * @example
406
427
  * ```
@@ -432,7 +453,7 @@ export async function createComponent ({
432
453
  let component = components[id]
433
454
 
434
455
  if (!component) {
435
- throw new Error('Could not find component: "' + id +'"')
456
+ return console.warn('Could not find component "' + id +'" used in document "' + document.parentPath + '/' + document.name + '"')
436
457
  }
437
458
 
438
459
  component = structuredClone(component)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "HTML modules static site generator",
5
5
  "main": "./lib/coralite.js",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  "test-e2e": "playwright test",
32
32
  "test-e2e-report": "playwright show-report",
33
33
  "test-e2e-ui": "playwright test --ui",
34
- "html": "node --experimental-vm-modules bin/coralite.js -t tests/fixtures/templates -p tests/fixtures/pages -o dist",
34
+ "html": "node --experimental-vm-modules bin/coralite.js -t tests/fixtures/templates -p tests/fixtures/pages -o dist -i 'data-dev=true'",
35
35
  "server": "sirv dist --dev --port 3000"
36
36
  },
37
37
  "bin": "bin/coralite.js",
@@ -40,8 +40,14 @@
40
40
  "#types": "./types/index.js"
41
41
  },
42
42
  "exports": {
43
- "default": "./lib/coralite.js",
44
- "types": "./types/index.js"
43
+ ".": {
44
+ "default": "./lib/coralite.js",
45
+ "types": "./types/index.js"
46
+ },
47
+ "./utils": {
48
+ "default": "./lib/index.js",
49
+ "types": "./types/index.js"
50
+ }
45
51
  },
46
52
  "devDependencies": {
47
53
  "@commitlint/cli": "^19.6.1",
@@ -50,6 +56,7 @@
50
56
  "@stylistic/eslint-plugin-js": "^2.12.1",
51
57
  "@stylistic/eslint-plugin-plus": "^2.12.1",
52
58
  "@types/node": "^22.10.5",
59
+ "kleur": "^4.1.5",
53
60
  "sirv-cli": "^3.0.0"
54
61
  },
55
62
  "engines": {
package/types/index.js CHANGED
@@ -72,6 +72,7 @@
72
72
  * @property {CoraliteContentNode} parent - Parent element
73
73
  * @property {number} [parentChildIndex] - Position in parent's child list
74
74
  * @property {Object[]} [slots]
75
+ * @property {boolean} [remove] - Mark element to be removed from stack
75
76
  */
76
77
 
77
78