coralite 0.4.2 → 0.5.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/lib/coralite.js CHANGED
@@ -17,7 +17,8 @@ export const tokens = {}
17
17
  *
18
18
  * @param {Object} options
19
19
  * @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 - A map where keys are token names and values are either strings or functions representing the corresponding tokens' content or behavior.
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
21
22
  * @returns {Promise<Object.<string, string>>}
22
23
  */
23
24
  export async function defineComponent (options) {
package/lib/parse.js CHANGED
@@ -501,7 +501,7 @@ export async function createComponent ({
501
501
 
502
502
  // merge values from component script
503
503
  if (component.script) {
504
- const computedValues = await parseScript(component, values, components, document)
504
+ const computedValues = await parseScript(component, values, element, components, document)
505
505
 
506
506
  values = Object.assign(values, computedValues)
507
507
 
@@ -618,38 +618,19 @@ export async function createComponent ({
618
618
  *
619
619
  * @param {CoraliteModule} component - The Coralite module to parse
620
620
  * @param {CoraliteModuleValues} values - Replacement tokens for the component
621
+ * @param {CoraliteElement} element - Element
621
622
  * @param {Object.<string, CoraliteModule>} components - Mapping of other components that might be referenced
622
623
  * @param {CoraliteDocument} document - The current document being processed
623
624
  * @returns {Promise<Object.<string,(string|(CoraliteElement|CoraliteTextNode)[])>>}
624
- *
625
- * @example
626
- * ```
627
- * // Example usage:
628
- * const parsedResult = await parseScript({
629
- * id: 'my-component',
630
- * template: `<div>Hello World</div>`,
631
- * script: `export default function () {
632
- * return '<div>Hello World</div>';
633
- * }`,
634
- * tokens: { 'hello': 'Hello' }
635
- * }, {
636
- * 'my-component': {
637
- * id: 'my-component',
638
- * template: `<div>${tokens.hello}</div>`,
639
- * script: `export default function () {
640
- * return '<div>Hello</div>';
641
- * }`
642
- * }
643
- * }, document);
644
- * ```
645
625
  */
646
- export async function parseScript (component, values, components, document) {
626
+ export async function parseScript (component, values, element, components, document) {
647
627
  const contextifiedObject = vm.createContext({
648
628
  coralite: {
649
629
  tokens: values,
650
630
  /**
651
631
  * @param {Object} options
652
632
  * @param {Object.<string, (string | function)>} options.tokens
633
+ * @param {Object.<string, Function>} options.slots
653
634
  * @returns {Promise<Object.<string, string>>}
654
635
  */
655
636
  async defineComponent (options) {
@@ -668,6 +649,46 @@ export async function parseScript (component, values, components, document) {
668
649
  }
669
650
  }
670
651
 
652
+ // process computed slots
653
+ if (options.slots) {
654
+ for (const name in options.slots) {
655
+ if (Object.prototype.hasOwnProperty.call(options.slots, name)) {
656
+ const computedSlot = options.slots[name]
657
+ // slot content to compute
658
+ const slotContent = []
659
+ // new slot elements
660
+ const elementSlots = []
661
+
662
+ for (let i = 0; i < element.slots.length; i++) {
663
+ const slot = element.slots[i]
664
+
665
+ if (slot.name === name) {
666
+ // slot content to compute
667
+ slotContent.push(slot.node)
668
+ } else {
669
+ elementSlots.push(slot)
670
+ }
671
+ }
672
+
673
+ // compute slot nodes
674
+ const result = computedSlot(slotContent)
675
+
676
+ // append new slot nodes
677
+ for (let index = 0; index < result.length; index++) {
678
+ const node = result[index]
679
+
680
+ elementSlots.push({
681
+ name,
682
+ node
683
+ })
684
+ }
685
+
686
+ // update element slots
687
+ element.slots = elementSlots
688
+ }
689
+ }
690
+ }
691
+
671
692
  return values
672
693
  },
673
694
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "HTML modules static site generator",
5
5
  "main": "./lib/coralite.js",
6
6
  "type": "module",
@@ -1,22 +0,0 @@
1
- changelog:
2
- categories:
3
- - title: Breaking Changes 🛠
4
- labels:
5
- - Semver-Major
6
- - breaking-change
7
- - title: Exciting New Features 🎉
8
- labels:
9
- - Semver-Minor
10
- - enhancement
11
- - title: 🏕 Features
12
- labels:
13
- - '*'
14
- exclude:
15
- labels:
16
- - dependencies
17
- - title: 👒 Dependencies
18
- labels:
19
- - dependencies
20
- - title: Other Changes
21
- labels:
22
- - "*"
@@ -1,45 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- release:
5
- types: [created]
6
-
7
- jobs:
8
- build:
9
- runs-on: ubuntu-latest
10
- steps:
11
- - name: Checkout
12
- uses: actions/checkout@v4
13
-
14
- - uses: pnpm/action-setup@v4
15
- name: Install pnpm
16
- with:
17
- version: 9
18
- run_install: false
19
-
20
- - name: Install Node.js
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: 22
24
- cache: "pnpm"
25
-
26
- - name: Install dependencies
27
- run: pnpm install
28
-
29
- - name: Lint
30
- run: pnpm run lint
31
-
32
-
33
- publish-npm:
34
- needs: build
35
- runs-on: ubuntu-latest
36
- steps:
37
- - uses: actions/checkout@v4
38
- - uses: actions/setup-node@v4
39
- with:
40
- node-version: 22
41
- registry-url: https://registry.npmjs.org/
42
- - run: npm publish
43
- env:
44
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
45
-
@@ -1,45 +0,0 @@
1
- name: Test e2e
2
-
3
- on:
4
- - push
5
- - pull_request
6
-
7
- jobs:
8
- test:
9
- timeout-minutes: 60
10
- runs-on: ubuntu-latest
11
-
12
- steps:
13
- - name: Checkout
14
- uses: actions/checkout@v4
15
-
16
- - uses: pnpm/action-setup@v4
17
- name: Install pnpm
18
- with:
19
- version: 9
20
- run_install: false
21
-
22
- - name: Install Node.js
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: 22
26
- cache: "pnpm"
27
-
28
- - name: Install dependencies
29
- run: pnpm install
30
-
31
- - name: Build HTML from templates
32
- run: pnpm run html
33
-
34
- - name: Install Playwright Browser
35
- run: pnpm exec playwright install firefox --with-deps
36
-
37
- - name: Run Playwright tests
38
- run: pnpm exec playwright test
39
-
40
- - uses: actions/upload-artifact@v4
41
- if: ${{ !cancelled() }}
42
- with:
43
- name: playwright-report
44
- path: playwright-report/
45
- retention-days: 30
@@ -1,128 +0,0 @@
1
- # Getting Started: Basic templating
2
-
3
- This guide will walk you through creating your first page using Coralite, a static site generator built around HTML modules.
4
-
5
- ## Project Structure
6
-
7
- Create a new project directory with the following structure:
8
-
9
- ```
10
- my-coralite-site/
11
- ├── src/
12
- │ ├── templates/
13
- │ │ ├── header.html
14
- │ │ └── footer.html
15
- │ └── pages/
16
- │ └── index.html
17
- └── dist/
18
- ```
19
-
20
- ## Creating Templates
21
-
22
- Coralite uses HTML templates with mustache-style tokens for dynamic content. Let's create two basic templates:
23
-
24
- ### 1. Header Template (src/templates/header.html)
25
-
26
- ```html
27
- <template id="coralite-header">
28
- <h1>{{ title }}</h1>
29
- <slot name="subtitle"></slot>
30
- <slot></slot>
31
- </template>
32
- ```
33
-
34
- ### 2. Footer Template (src/templates/footer.html)
35
-
36
- ```html
37
- <template id="coralite-footer">
38
- <footer>
39
- Just keep swimming.
40
- </footer>
41
- </template>
42
- ```
43
-
44
- ## Building Your First Page
45
-
46
- Create your main page in `src/pages/index.html`:
47
-
48
- ```html
49
- <!DOCTYPE html>
50
- <html lang="en">
51
- <head>
52
- <meta charset="UTF-8">
53
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
- <title>My First Coralite Page</title>
55
- </head>
56
- <body>
57
- <coralite-header title="Welcome to My Site">
58
- <span slot="subtitle">A Coralite Creation</span>
59
- <p>This is my first page built with Coralite!</p>
60
- </coralite-header>
61
-
62
- <coralite-footer></coralite-footer>
63
- </body>
64
- </html>
65
- ```
66
-
67
- ## Template Features
68
-
69
- 1. **Dynamic Content**: Use `{{ variableName }}` syntax to insert dynamic content passed through component attributes.
70
- 2. **Slots**:
71
- - Named slots: Use `slot="name"` to target specific areas in your template
72
- - Default slots: Content without a slot attribute goes into the default `<slot>`
73
-
74
- ## Building Your Site
75
-
76
- Generate your site using the Coralite CLI:
77
-
78
- ```bash
79
- coralite --templates ./src/templates --pages ./src/pages --output ./dist
80
- ```
81
-
82
- To preview changes without generating files, use the dry-run mode:
83
-
84
- ```bash
85
- coralite --templates ./src/templates --pages ./src/pages --output ./dist --dry
86
- ```
87
-
88
- ## Template Tips
89
-
90
- 1. Every template must have an `id` attribute that matches its usage in pages:
91
- ```html
92
- <template id="coralite-header">
93
- ```
94
-
95
- 2. Component names in pages must match template IDs:
96
- ```html
97
- <coralite-header title="My Title">
98
- ```
99
-
100
- 3. Data can be passed via attributes and accessed using mustache syntax:
101
- ```html
102
- <!-- In your page -->
103
- <coralite-header title="Hello World">
104
-
105
- <!-- In your template -->
106
- <h1>{{ title }}</h1>
107
- ```
108
-
109
- ## Next Steps
110
-
111
- - Try creating more complex templates with multiple slots
112
- - Explore nested components by using one component inside another
113
- - Use the dry-run mode to debug template issues
114
- - Check the [full documentation](https://github.com/tjdav/coralite) for advanced features
115
-
116
- ## Common Issues
117
-
118
- 1. If your templates aren't rendering, ensure:
119
- - Template IDs match the component names in your pages
120
- - All required attributes are provided
121
- - The Node.js `--experimental-vm-modules` flag is enabled
122
-
123
- 2. Missing content might indicate:
124
- - Incorrect slot names
125
- - Missing slot definitions in templates
126
- - Malformed template syntax
127
-
128
- Remember to keep your templates modular and reusable for the best development experience with Coralite!
@@ -1,66 +0,0 @@
1
- import { test, expect } from '@playwright/test'
2
-
3
- test('has title', async ({ page }) => {
4
- await page.goto('/')
5
-
6
- // Expect a title "to contain" a substring.
7
- await expect(page).toHaveTitle(/Home page/)
8
- })
9
-
10
- test('has compiled coralite-header custom element', async ({ page }) => {
11
- await page.goto('/')
12
-
13
- // Check if the custom coralite-header element exists and contains the expected text
14
- await expect(page.locator('body')).toMatchAriaSnapshot(`
15
- - text: Hello
16
- - banner: This is the mighty header
17
- - text: world
18
- `)
19
- })
20
-
21
- test('has custom element with default slot', async ({ page }) => {
22
- await page.goto('/about.html')
23
-
24
- await expect(page.locator('body')).toMatchAriaSnapshot(`
25
- - text: test
26
- `)
27
- })
28
-
29
- test('has aggregate content', async ({ page }) => {
30
- await page.goto('/blog/index.html')
31
-
32
- await expect(page.locator('body')).toMatchAriaSnapshot(`
33
- - text: Hello
34
- - banner: This is the mighty header
35
- - heading "Post 1" [level=2]
36
- - text: Nemo
37
- - time: Wed, 8 Jan 25
38
- - img "Photo of a cat"
39
- - text: short description
40
- - banner: This is the mighty header
41
- - heading "Post 2" [level=2]
42
- - text: Nemo
43
- - time: Thu, 9 Jan 25
44
- - img "Photo of a dog"
45
- - text: short description
46
- - banner: This is the mighty header
47
- - text: world
48
- `)
49
- })
50
-
51
- test('has named slots', async ({ page }) => {
52
- await page.goto('/blog/index.html')
53
-
54
- const metaName = page.locator('meta[name="name"]')
55
- const metaDescription = page.locator('meta[name="description"]')
56
- const metaDefaultHello = page.locator('meta[name="default-hello"]')
57
- const metaDefaultWorld = page.locator('meta[name="default-world"]')
58
-
59
- // named slots
60
- await expect(metaName).toHaveAttribute('content', 'coralite')
61
- await expect(metaDescription).toHaveAttribute('content', 'look mum, no database!')
62
- // default slots
63
- await expect(metaDefaultHello).toHaveAttribute('content', 'hello')
64
- await expect(metaDefaultWorld).toHaveAttribute('content', 'world')
65
- })
66
-
@@ -1,12 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta name="title" content="About">
5
- <title>About</title>
6
- </head>
7
- <body>
8
- <coralite-layout>
9
- test
10
- </coralite-layout>
11
- </body>
12
- </html>
@@ -1,21 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Blog posts</title>
7
- <coralite-head>
8
- <meta slot="meta" name="name" content="coralite">
9
- <meta slot="meta" name="description" content="look mum, no database!">
10
- <meta name="default-hello" content="hello">
11
- <meta name="default-world" content="world">
12
- </coralite-head>
13
- </head>
14
- <body>
15
- Hello
16
-
17
- <coralite-header></coralite-header>
18
- <coralite-posts path="blog"></coralite-posts>
19
- world
20
- </body>
21
- </html>
@@ -1,22 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Document</title>
7
- <meta name="title" content="Post 1">
8
- <meta name="author" content="Nemo">
9
- <meta name="image" content="image.png">
10
- <meta name="image_alt" content="Photo of a cat">
11
- <meta name="description" content="short description">
12
- <meta name="published_time" content="2025-01-08T20:23:07.645Z">
13
- </head>
14
- <body>
15
- Hello
16
-
17
- <coralite-header></coralite-header>
18
- <coralite-author name="Nemo" datetime="2025-01-08T20:23:07.645Z"></coralite-author>
19
-
20
- world
21
- </body>
22
- </html>
@@ -1,18 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Document</title>
7
- <meta name="title" content="Post 2">
8
- <meta name="author" content="Nemo">
9
- <meta name="image" content="image.png">
10
- <meta name="image_alt" content="Photo of a dog">
11
- <meta name="description" content="short description">
12
- <meta name="published_time" content="2025-01-09T20:23:07.645Z">
13
- </head>
14
- <body>
15
- <coralite-header></coralite-header>
16
- <coralite-author name="Nemo" datetime="2025-01-08T20:23:07.645Z"></coralite-author>
17
- </body>
18
- </html>
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Home page</title>
7
- </head>
8
- <body>
9
- Hello
10
-
11
- <coralite-header></coralite-header>
12
-
13
- world
14
- </body>
15
- </html>
@@ -1,24 +0,0 @@
1
- <template id="coralite-author">
2
- <span>{{ name }}</span>
3
- <time datetime="{{ datetime }}">
4
- {{ localeDate }}
5
- </time>
6
- </template>
7
-
8
- <script type="module">
9
- import { defineComponent, tokens } from 'coralite'
10
-
11
- export default defineComponent({
12
- id: 'coralite-author',
13
- tokens: {
14
- localeDate () {
15
- return new Date(tokens.datetime).toLocaleDateString('en-AU', {
16
- weekday: 'short',
17
- year: '2-digit',
18
- month: 'short',
19
- day: 'numeric'
20
- })
21
- }
22
- }
23
- })
24
- </script>
@@ -1,15 +0,0 @@
1
- <template id="coralite-head">
2
- <slot name="meta">
3
- <meta name="meta" content="default">
4
- </slot>
5
- <slot name="css">
6
- <link href="styles.css" rel="stylesheet">
7
- </slot>
8
- <slot name="script">
9
- <script>
10
- console.log('hello')
11
- </script>
12
- </slot>
13
- <slot name="empty"></slot>
14
- <slot></slot>
15
- </template>
@@ -1,5 +0,0 @@
1
- <template id="coralite-header">
2
- <header>
3
- This is the mighty header
4
- </header>
5
- </template>
@@ -1,7 +0,0 @@
1
- <template id="coralite-post">
2
- <h2>{{ title }}</h2>
3
- <coralite-author name="{{ author }}" datetime="{{ published_time }}"></coralite-author>
4
- <img src="{{ image }}" alt="{{ image_alt }}">
5
- {{ description }}
6
- <coralite-header></coralite-header>
7
- </template>
@@ -1,29 +0,0 @@
1
- <template id="coralite-posts">
2
- {{ posts }}
3
- </template>
4
-
5
- <script type="module">
6
- import { defineComponent, tokens, aggregate } from 'coralite'
7
-
8
- export default defineComponent({
9
- id: 'coralite-posts',
10
- tokens: {
11
- posts () {
12
- return aggregate({
13
- path: tokens.path,
14
- componentId: 'coralite-post',
15
- pages: 10,
16
- sortBy: 'published_time',
17
- tokens: {
18
- aliases: {
19
- datetime: ['published_time']
20
- },
21
- values: {
22
- image: 'default.png'
23
- }
24
- }
25
- })
26
- }
27
- }
28
- })
29
- </script>
@@ -1,3 +0,0 @@
1
- <template id="coralite-title">
2
- <h1>{{ title }}</h1>
3
- </template>
@@ -1,5 +0,0 @@
1
- <template id="coralite-layout">
2
- <div>
3
- <slot></slot>
4
- </div>
5
- </template>