@projectevergreen/eleventy-plugin-wcc 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.
package/.editorconfig ADDED
@@ -0,0 +1,10 @@
1
+ # http://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ charset = utf-8
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = false
package/.eleventy.js ADDED
@@ -0,0 +1,15 @@
1
+ import { wccPlugin } from './src/index.js';
2
+
3
+ export default function(eleventyConfig) {
4
+ eleventyConfig.addPlugin(wccPlugin, {
5
+ definitions: [
6
+ new URL('./demo/components/greeting.js', import.meta.url)
7
+ ]
8
+ });
9
+
10
+ return {
11
+ dir: {
12
+ input: './demo'
13
+ }
14
+ }
15
+ };
package/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ !.eslintrc.cjs
2
+ src/lib/wcc.dist.js
package/.eslintrc.js ADDED
@@ -0,0 +1,220 @@
1
+ module.exports = {
2
+ parserOptions: {
3
+ ecmaVersion: 2022,
4
+ sourceType: 'module'
5
+ },
6
+ env: {
7
+ browser: false,
8
+ node: true
9
+ },
10
+ rules: {
11
+ 'comma-dangle': [2, 'never'],
12
+ 'no-cond-assign': 2,
13
+ 'no-console': 0,
14
+ 'no-constant-condition': 2,
15
+ 'no-control-regex': 0,
16
+ 'no-debugger': 0,
17
+ 'no-dupe-args': 0,
18
+ 'no-dupe-keys': 0,
19
+ 'no-duplicate-case': 2,
20
+ 'no-empty-character-class': 0,
21
+ 'no-empty': 2,
22
+ 'no-ex-assign': 2,
23
+ 'no-extra-boolean-cast': 2,
24
+ 'no-extra-parens': [2, 'all', {
25
+ 'conditionalAssign': false,
26
+ 'returnAssign': false,
27
+ 'nestedBinaryExpressions': false
28
+ }],
29
+ 'no-extra-semi': 2,
30
+ 'no-func-assign': 2,
31
+ 'no-inner-declarations': 2,
32
+ 'no-invalid-regexp': 0,
33
+ 'no-irregular-whitespace': 2,
34
+ 'no-negated-in-lhs': 2,
35
+ 'no-obj-calls': 0,
36
+ 'no-regex-spaces': 0,
37
+ 'no-sparse-arrays': 2,
38
+ 'no-unreachable': 2,
39
+ 'use-isnan': 2,
40
+ 'valid-jsdoc': 0,
41
+ 'valid-typeof': 0,
42
+ 'no-unexpected-multiline': 0,
43
+ 'accessor-pairs': 2,
44
+ 'block-scoped-var': 2,
45
+ 'complexity': 2,
46
+ 'consistent-return': 0,
47
+ 'curly': 2,
48
+ 'default-case': 2,
49
+ 'dot-notation': 2,
50
+ 'dot-location': 0,
51
+ 'eqeqeq': [2, 'allow-null'],
52
+ 'guard-for-in': 0,
53
+ 'no-alert': 0,
54
+ 'no-caller': 0,
55
+ 'no-div-regex': 0,
56
+ 'no-else-return': 0,
57
+ 'no-empty-label': 0,
58
+ 'no-eq-null': 0,
59
+ 'no-eval': 2,
60
+ 'no-extend-native': 0,
61
+ 'no-extra-bind': 2,
62
+ 'no-fallthrough': 2,
63
+ 'no-floating-decimal': 2,
64
+ 'no-implicit-coercion': [2, {
65
+ 'number': true,
66
+ 'string': true,
67
+ 'boolean': false
68
+ }],
69
+ 'no-implied-eval': 2,
70
+ 'no-invalid-this': 0,
71
+ 'no-iterator': 2,
72
+ 'no-labels': 0,
73
+ 'no-lone-blocks': 0,
74
+ 'no-loop-func': 2,
75
+ 'no-multi-spaces': 2,
76
+ 'no-multi-str': 0,
77
+ 'no-native-reassign': 0,
78
+ 'no-new-func': 0,
79
+ 'no-new-wrappers': 2,
80
+ 'no-new': 2,
81
+ 'no-octal-escape': 0,
82
+ 'no-octal': 0,
83
+ 'no-param-reassign': 0,
84
+ 'no-process-env': 0,
85
+ 'no-proto': 0,
86
+ 'no-redeclare': 2,
87
+ 'no-return-assign': 0,
88
+ 'no-script-url': 0,
89
+ 'no-self-compare': 0,
90
+ 'no-sequences': 0,
91
+ 'no-throw-literal': 2,
92
+ 'no-unused-expressions': 0,
93
+ 'no-useless-call': 0,
94
+ 'no-void': 0,
95
+ 'no-warning-comments': [1, {
96
+ 'terms': [
97
+ 'todo',
98
+ ' fixme',
99
+ ' TODO',
100
+ ' FIXME'
101
+ ],
102
+ 'location': 'anywhere'
103
+ }],
104
+ 'no-with': 0,
105
+ 'radix': 2,
106
+ 'vars-on-top': 2,
107
+ 'wrap-iife': [2, 'inside'],
108
+ 'yoda': 0,
109
+ 'strict': [2, 'global'],
110
+ 'init-declarations': 0,
111
+ 'no-catch-shadow': 2,
112
+ 'no-delete-var': 2,
113
+ 'no-label-var': 0,
114
+ 'no-shadow-restricted-names': 0,
115
+ 'no-shadow': 0,
116
+ 'no-undef-init': 0,
117
+ 'no-undef': 0,
118
+ 'no-undefined': 0,
119
+ 'no-unused-vars': 2,
120
+ 'no-use-before-define': 0,
121
+ 'callback-return': 0,
122
+ 'handle-callback-err': 2,
123
+ 'no-mixed-requires': 0,
124
+ 'no-new-require': 0,
125
+ 'no-path-concat': 2,
126
+ 'no-process-exit': 2,
127
+ 'no-restricted-modules': 0,
128
+ 'no-sync': 0,
129
+ 'array-bracket-spacing': 2,
130
+ 'brace-style': [2, '1tbs', {
131
+ 'allowSingleLine': true
132
+ }],
133
+ 'camelcase': 2,
134
+ 'comma-spacing': 2,
135
+ 'comma-style': [2, 'last'],
136
+ 'computed-property-spacing': 0,
137
+ 'consistent-this': [0, 'self', 'that'],
138
+ 'eol-last': 0,
139
+ 'func-names': 0,
140
+ 'func-style': 0,
141
+ 'id-length': 0,
142
+ 'indent': [2, 2, {
143
+ 'VariableDeclarator': 1,
144
+ 'SwitchCase': 1,
145
+ 'ignoredNodes': [
146
+ 'TemplateLiteral'
147
+ ]
148
+ }],
149
+ 'key-spacing': [2, {
150
+ 'beforeColon': false,
151
+ 'afterColon': true
152
+ }],
153
+ 'lines-around-comment': 0,
154
+ 'linebreak-style': 0,
155
+ 'max-nested-callbacks': [2, { 'maximum': 8 }],
156
+ 'new-cap': 2,
157
+ 'new-parens': 2,
158
+ 'no-array-constructor': 2,
159
+ 'no-continue': 0,
160
+ 'no-inline-comments': 0,
161
+ 'no-lonely-if': 0,
162
+ 'no-mixed-spaces-and-tabs': [2, 'smart-tabs'],
163
+ 'no-multiple-empty-lines': [2, { 'max': 1 }],
164
+ 'no-nested-ternary': 0,
165
+ 'no-new-object': 2,
166
+ 'no-spaced-func': 2,
167
+ 'no-ternary': 0,
168
+ 'no-trailing-spaces': 0,
169
+ 'no-underscore-dangle': [2, { 'allowAfterThis': true }],
170
+ 'no-unneeded-ternary': 2,
171
+ 'object-curly-spacing': [2, 'always', {}],
172
+ 'one-var': 0,
173
+ 'operator-assignment': 0,
174
+ 'operator-linebreak': 0,
175
+ 'padded-blocks': [2, { 'switches': 'always' }],
176
+ 'quote-props': [2, 'consistent'],
177
+ 'quotes': [2, 'single', 'avoid-escape'],
178
+ 'id-match': 0,
179
+ 'semi-spacing': [2, { 'after': true }],
180
+ 'semi': [2, 'always'],
181
+ 'sort-vars': 0,
182
+ 'keyword-spacing': 2,
183
+ 'space-before-blocks': 2,
184
+ 'space-before-function-paren': 0,
185
+ 'space-in-parens': 2,
186
+ 'space-infix-ops': 2,
187
+ 'space-return-throw-case': 0,
188
+ 'space-unary-ops': 0,
189
+ 'spaced-comment': [2, 'always', {
190
+ 'line': {
191
+ 'markers': ['/'],
192
+ 'exceptions': ['-', '+']
193
+ },
194
+ 'block': {
195
+ 'markers': ['!'],
196
+ 'exceptions': ['*']
197
+ }
198
+ }],
199
+ 'wrap-regex': 2,
200
+ 'arrow-parens': 0,
201
+ 'arrow-spacing': 0,
202
+ 'constructor-super': 0,
203
+ 'generator-star-spacing': 0,
204
+ 'no-class-assign': 0,
205
+ 'no-const-assign': 0,
206
+ 'no-this-before-super': 0,
207
+ 'no-var': 0,
208
+ 'object-shorthand': 0,
209
+ 'prefer-const': 0,
210
+ 'prefer-spread': 0,
211
+ 'prefer-reflect': 0,
212
+ 'require-yield': 0,
213
+ 'max-depth': [2, 4],
214
+ 'max-len': [2, 200, 1, { 'ignorePattern': 'true' }],
215
+ 'max-params': 0,
216
+ 'max-statements': 0,
217
+ 'no-bitwise': [2, { 'allow': ['~'] }],
218
+ 'no-plusplus': 2
219
+ }
220
+ };
package/.gitattributes ADDED
@@ -0,0 +1,16 @@
1
+ * text eol=lf
2
+
3
+ *.bat text eol=crlf
4
+ *.sh text eol=lf
5
+
6
+ *.jpg binary
7
+ *.jpeg binary
8
+ *.png binary
9
+ *.avif binary
10
+ *.webp binary
11
+ *.gif binary
12
+ *.ico binary
13
+ *.woff binary
14
+ *.woff2 binary
15
+ *.ttf binary
16
+ *.eot binary
@@ -0,0 +1,31 @@
1
+ name: Continuous Integration (Windows)
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+
7
+ build:
8
+ runs-on: ubuntu-latest
9
+
10
+ strategy:
11
+ matrix:
12
+ node: [22, 24]
13
+
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - name: Use Node.js ${{ matrix.node }}
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: ${{ matrix.node }}
20
+ - name: Installing project dependencies
21
+ run: |
22
+ npm ci
23
+ - name: Lint
24
+ run: |
25
+ npm run lint
26
+ - name: Test
27
+ run: |
28
+ npm run test
29
+ - name: Build
30
+ run: |
31
+ npm run build
@@ -0,0 +1,31 @@
1
+ name: Continuous Integration
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+
7
+ build:
8
+ runs-on: ubuntu-latest
9
+
10
+ strategy:
11
+ matrix:
12
+ node: [22, 24]
13
+
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - name: Use Node.js ${{ matrix.node }}
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: ${{ matrix.node }}
20
+ - name: Installing project dependencies
21
+ run: |
22
+ npm ci
23
+ - name: Lint
24
+ run: |
25
+ npm run lint
26
+ - name: Test
27
+ run: |
28
+ npm run test
29
+ - name: Build
30
+ run: |
31
+ npm run build
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 22.18.0
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # eleventy-plugin-wcc
2
+
3
+ ## Overview
4
+
5
+ [**Eleventy**](https://www.11ty.dev/) plugin for rendering native Web Components using [**Web Components Compiler (WCC)**](https://github.com/ProjectEvergreen/wcc).
6
+
7
+ > _A [starter kit](https://github.com/thescientist13/eleventy-starter-wcc/) for 11ty + WCC is also available._
8
+
9
+ ## Installation
10
+
11
+ Install from NPM.
12
+
13
+ ```sh
14
+ $ npm i -D @projectevergreen/eleventy-plugin-wcc
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Add the plugin to your _eleventy.js_ config and provide a [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) for all _top level_ custom element definitions you use.
20
+ ```js
21
+ import { wccPlugin } from '@projectevergreen/eleventy-plugin-wcc';
22
+
23
+ module.exports = function(eleventyConfig) {
24
+ eleventyConfig.addPlugin(wccPlugin, {
25
+ definitions: [
26
+ new URL('./src/js/my-element.js', import.meta.url)
27
+ ]
28
+ });
29
+ };
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### 1. Create a Custom Element
35
+
36
+ Write a custom element like below. In this case, we are using [Declarative Shadow DOM](https://web.dev/declarative-shadow-dom/).
37
+
38
+ ```js
39
+ // src/components/greeting.js
40
+ const template = document.createElement('template');
41
+
42
+ template.innerHTML = `
43
+ <p>Hello from the greeting component!</p>
44
+ `;
45
+
46
+ export default class GreetingComponent extends HTMLElement {
47
+ constructor() {
48
+ super();
49
+ this.attachShadow({ mode: 'open' });
50
+ }
51
+
52
+ async connectedCallback() {
53
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
54
+ }
55
+ }
56
+
57
+ customElements.define('x-greeting', GreetingComponent);
58
+ ```
59
+
60
+ ### 2. Update Configuration
61
+
62
+ Add your custom element paths to your _.eleventy.js_ config
63
+
64
+ ```js
65
+ import { wccPlugin } from '@projectevergreen/eleventy-plugin-wcc';
66
+
67
+ module.exports = function(eleventyConfig) {
68
+ eleventyConfig.addPlugin(wccPlugin, {
69
+ definitions: [
70
+ new URL('./src/components/my-element.js', import.meta.url)
71
+ ]
72
+ });
73
+ };
74
+ ```
75
+
76
+ ### 3. Use It!
77
+
78
+ Now in your content or layouts, use the custom element.
79
+ ```md
80
+ <!-- src/index.md -->
81
+ # Hello From 11ty + WCC! 👋
82
+
83
+ <x-greeting></x-greeting>
84
+ ```
85
+
86
+ ----
87
+
88
+ Now if you run `eleventy`, you should get an _index.html_ in your _site/_ directory with the custom element content pre-rendered! 🎈
89
+ ```html
90
+ <h2>Hello From 11ty + WCC!</h2>
91
+
92
+ <x-greeting>
93
+ <template shadowroot="open">
94
+ <p>Hello from the greeting component!</p>
95
+ </template>
96
+ </x-greeting>
97
+ ```
98
+
99
+ ## Options
100
+
101
+ `trimParagraphTagsInMd` (bool, default true) - Trims unexpected `<p>` tags that markdown puts around custom elements [more details](https://github.com/ProjectEvergreen/eleventy-plugin-wcc/issues/8).
102
+
103
+ > _Please follow along in our [issue tracker](https://github.com/ProjectEvergreen/eleventy-plugin-wcc/issues) or make a suggestion!_
package/TODO.md ADDED
@@ -0,0 +1,7 @@
1
+ 1. [ ] deps upgrade
2
+ 1. [ ] min node version
3
+ 1. [ ] ESM
4
+ 1. [ ] github actions
5
+ 1. [ ] README
6
+ 1. [ ] reload breaks on `require.cache`
7
+ 1. [ ] downstream test
@@ -0,0 +1,18 @@
1
+ const template = document.createElement('template');
2
+
3
+ template.innerHTML = `
4
+ <p>Hello from the greeting component!</p>
5
+ `;
6
+
7
+ export default class GreetingComponent extends HTMLElement {
8
+ constructor() {
9
+ super();
10
+ this.attachShadow({ mode: 'open' });
11
+ }
12
+
13
+ async connectedCallback() {
14
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
15
+ }
16
+ }
17
+
18
+ customElements.define('x-greeting', GreetingComponent);
package/demo/index.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 11ty + WCC Demo
2
+
3
+ <x-greeting></x-greeting>
package/netlify.toml ADDED
@@ -0,0 +1,9 @@
1
+ [build]
2
+ publish = "_site/"
3
+ command = "npm run build"
4
+
5
+ [build.processing]
6
+ skip_processing = true
7
+
8
+ [build.environment]
9
+ NODE_VERSION = "22.18.0"
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@projectevergreen/eleventy-plugin-wcc",
3
+ "version": "0.3.0",
4
+ "description": "An Eleventy plugin for rendering Web Components with WCC.",
5
+ "repository": "https://github.com/ProjectEvergreen/eleventy-plugin-wcc",
6
+ "main": "src/index.js",
7
+ "author": "Owen Buckley <owen@thegreenhouse.io>",
8
+ "type": "module",
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "Eleventy",
12
+ "WCC",
13
+ "Web Components"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "clean": "rimraf ./_site",
20
+ "lint": "eslint \"src/**/*.js\" \"demo/**/*.js\"",
21
+ "test": "node --test",
22
+ "build": "npm run clean && eleventy",
23
+ "demo": "npm run build -- --serve"
24
+ },
25
+ "peerDependencies": {
26
+ "@11ty/eleventy": "^3.1.0"
27
+ },
28
+ "dependencies": {
29
+ "parse5": "^8.0.0",
30
+ "wc-compiler": "~0.18.0"
31
+ },
32
+ "devDependencies": {
33
+ "@11ty/eleventy": "^3.1.0",
34
+ "eslint": "^8.18.0",
35
+ "rimraf": "^3.0.2"
36
+ }
37
+ }
package/src/index.js ADDED
@@ -0,0 +1,37 @@
1
+ import { renderFromHTML } from 'wc-compiler';
2
+ import { stripWrappingParagraphs } from './utils.js';
3
+
4
+ export const wccPlugin = {
5
+ configFunction: function (eleventyConfig, options = {}) {
6
+ const { definitions = [], trimParagraphTagsInMd = true } = options;
7
+ const definitionPathnames = definitions.map(definition => definition.pathname);
8
+
9
+ for (const definition of definitions) {
10
+ eleventyConfig.addWatchTarget(definition.pathname);
11
+ }
12
+
13
+ eleventyConfig.addTransform('wcc', async function (content, outputPath) {
14
+ if (!outputPath.endsWith('.html')) {
15
+ return;
16
+ }
17
+
18
+ const processedContent =
19
+ trimParagraphTagsInMd && this.inputPath.endsWith('.md')
20
+ ? stripWrappingParagraphs(content)
21
+ : content;
22
+
23
+ const { html } = await renderFromHTML(processedContent, definitions);
24
+ return html;
25
+ });
26
+
27
+ eleventyConfig.on('eleventy.beforeWatch', async (changedFiles) => {
28
+ for (const file of changedFiles) {
29
+ if (definitionPathnames.includes(file)) {
30
+ // naive cache busting solution for ESM
31
+ // https://github.com/nodejs/help/issues/2806
32
+ await import(`${file}?t=${Date.now()}`);
33
+ }
34
+ }
35
+ });
36
+ }
37
+ };
package/src/utils.js ADDED
@@ -0,0 +1,61 @@
1
+ import * as parse5 from 'parse5';
2
+
3
+ function isWhitespaceTextNode(node) {
4
+ return node.nodeName === '#text' && (/^[\t\n\r ]*$/).test(node.value);
5
+ }
6
+
7
+ export const stripWrappingParagraphs = (html) => {
8
+ const isFullHtmlDoc = (/^<(!DOCTYPE )?html>/i).test(html);
9
+ const parsedHtml = isFullHtmlDoc ? parse5.parse(html) : parse5.parseFragment(html);
10
+
11
+ const rootNode = chooseRootNode(parsedHtml);
12
+ rootNode.childNodes = rootNode.childNodes.map(traverseNodes);
13
+
14
+ return parse5.serialize(parsedHtml);
15
+ };
16
+
17
+ function chooseRootNode(parsedHtml, isFullHtmlDoc) {
18
+ if (isFullHtmlDoc) {
19
+ const rootNode = parsedHtml.childNodes
20
+ .find((x) => x.nodeName === 'html')
21
+ ?.childNodes?.find((x) => x.nodeName === 'body');
22
+
23
+ if (!rootNode) {
24
+ throw new Error('html output is missing the body tag');
25
+ }
26
+
27
+ return rootNode;
28
+ } else {
29
+ return parsedHtml;
30
+ }
31
+ }
32
+
33
+ function traverseNodes(node) {
34
+ node = stripWrappingParagraph(node);
35
+
36
+ // Don't traverse children of custom elements
37
+ if (node.childNodes && !node.nodeName.includes('-')) {
38
+ node.childNodes = node.childNodes.map(traverseNodes);
39
+ }
40
+
41
+ return node;
42
+ }
43
+
44
+ function stripWrappingParagraph(node) {
45
+ if (node.nodeName !== 'p') {
46
+ return node;
47
+ }
48
+
49
+ // Ignore whitespace-only text nodes
50
+ const meaningfulChildren = node.childNodes.filter(
51
+ (child) => !isWhitespaceTextNode(child)
52
+ );
53
+
54
+ if (meaningfulChildren.length === 1 &&
55
+ meaningfulChildren[0].nodeName.includes('-')) {
56
+ return meaningfulChildren[0];
57
+ }
58
+
59
+ return node;
60
+ }
61
+
@@ -0,0 +1,80 @@
1
+ import Eleventy from '@11ty/eleventy';
2
+ import { wccPlugin } from '../src/index.js';
3
+ import assert from 'node:assert/strict';
4
+ import { describe, it, before } from 'node:test';
5
+
6
+ describe('WCC plugin', () => {
7
+ describe('Default options', () => {
8
+ let indexMdFile;
9
+
10
+ before(async () => {
11
+ const templates = await setUpTemplates();
12
+ ({ indexMdFile } = templates);
13
+ });
14
+
15
+ it('prints the header', () => {
16
+ const expected = '<h2>11ty + WCC Demo</h2>';
17
+
18
+ assert.ok(indexMdFile.content.includes(expected), 'Header not found');
19
+ });
20
+
21
+ it('prints the greeting component', () => {
22
+ const contentNormalized = indexMdFile.content
23
+ .replaceAll(/\s+/g, ' ')
24
+ .trim();
25
+ const expected =
26
+ '<x-greeting><template shadowrootmode="open"> <p>Hello from the greeting component!</p> </template></x-greeting>';
27
+
28
+ assert.ok(
29
+ contentNormalized.includes(expected),
30
+ 'x-greeting component not found'
31
+ );
32
+ });
33
+
34
+ it('removes wrapping p tags', () => {
35
+ const regexp = new RegExp('<p><x-greeting>(.|\\s)*</x-greeting></p>');
36
+
37
+ assert.doesNotMatch(indexMdFile.content, regexp);
38
+ });
39
+ });
40
+
41
+ describe('trimParagraphTagsInMd: false', () => {
42
+ it('leaves wrapping p tags', async () => {
43
+ const eleventy = setUpEleventy({ trimParagraphTagsInMd: false });
44
+ const { indexMdFile } = await setUpTemplates(eleventy);
45
+ const regexp = new RegExp('<p><x-greeting>(.|\\s)*</x-greeting></p>');
46
+
47
+ assert.match(indexMdFile.content, regexp);
48
+ });
49
+ });
50
+ });
51
+
52
+ function setUpEleventy(pluginOptions = {}) {
53
+ return new Eleventy('test-fixtures', 'test/output', {
54
+ configPath: null,
55
+ config: function (eleventyConfig) {
56
+ eleventyConfig.addPlugin(wccPlugin, {
57
+ definitions: [
58
+ new URL('../test-fixtures/components/greeting.js', import.meta.url)
59
+ ],
60
+ ...pluginOptions
61
+ });
62
+ }
63
+ });
64
+ }
65
+
66
+ async function setUpTemplates(eleventy = null) {
67
+ eleventy ??= setUpEleventy();
68
+
69
+ await eleventy.init();
70
+ const elevOutput = await eleventy.toJSON();
71
+
72
+ const indexMdFile = elevOutput.find(
73
+ (template) => template.inputPath === './test-fixtures/index.md'
74
+ );
75
+
76
+ assert.ok(indexMdFile, 'Precondition failed: demo/index.md not found');
77
+
78
+ return { indexMdFile };
79
+ }
80
+
@@ -0,0 +1,98 @@
1
+ import { stripWrappingParagraphs } from '../src/utils.js';
2
+ import assert from 'node:assert/strict';
3
+ import { describe, it } from 'node:test';
4
+
5
+ describe('stripWrappingParagraphs', () => {
6
+ describe('Html fragment', () => {
7
+ it('removes wrapping p tags', async () => {
8
+ const input = '<p><x-greeting></x-greeting></p>';
9
+ const expected = '<x-greeting></x-greeting>';
10
+
11
+ const result = stripWrappingParagraphs(input);
12
+
13
+ assert.equal(result, expected);
14
+ });
15
+
16
+ it('removes wrapping p tags (inner content)', async () => {
17
+ const input = '<p><x-greeting>inner content</x-greeting></p>';
18
+ const expected = '<x-greeting>inner content</x-greeting>';
19
+
20
+ const result = stripWrappingParagraphs(input);
21
+
22
+ assert.equal(result, expected);
23
+ });
24
+
25
+ it('removes wrapping p tags (inner content, newlines)', async () => {
26
+ const input = '<p><x-greeting>inner\ncontent</x-greeting></p>';
27
+ const expected = '<x-greeting>inner\ncontent</x-greeting>';
28
+
29
+ const result = stripWrappingParagraphs(input);
30
+
31
+ assert.equal(result, expected);
32
+ });
33
+
34
+ it('removes wrapping p tags (whitespace)', async () => {
35
+ const input = '<p> \n\t<x-greeting></x-greeting> \n</p>';
36
+ const expected = '<x-greeting></x-greeting>';
37
+
38
+ const result = stripWrappingParagraphs(input);
39
+
40
+ assert.equal(result, expected);
41
+ });
42
+
43
+ it('does not remove wrapping p tags if it includes other content', async () => {
44
+ const input = '<p>Hello <x-greeting></x-greeting></p>';
45
+
46
+ const result = stripWrappingParagraphs(input);
47
+
48
+ assert.equal(result, input);
49
+ });
50
+
51
+ it('removes wrapping p tags (multiple)', async () => {
52
+ const input =
53
+ '<p><x-greeting></x-greeting></p>\n<p><x-test></x-test></p>';
54
+ const expected = '<x-greeting></x-greeting>\n<x-test></x-test>';
55
+
56
+ const result = stripWrappingParagraphs(input);
57
+
58
+ assert.equal(result, expected);
59
+ });
60
+
61
+ it('removes nested wrapping p tags', async () => {
62
+ const input = '<main><p><x-greeting></x-greeting></p></main>';
63
+ const expected = '<main><x-greeting></x-greeting></main>';
64
+
65
+ const result = stripWrappingParagraphs(input);
66
+
67
+ assert.equal(result, expected);
68
+ });
69
+
70
+ it('removes double nested wrapping p tags', async () => {
71
+ const input = '<main><div><p><x-greeting></x-greeting></p></div></main>';
72
+ const expected = '<main><div><x-greeting></x-greeting></div></main>';
73
+
74
+ const result = stripWrappingParagraphs(input);
75
+
76
+ assert.equal(result, expected);
77
+ });
78
+ });
79
+
80
+ describe('Html document', () => {
81
+ it('preserves html, head, and body tags', () => {
82
+ const input = '<!DOCTYPE html><html lang="en"><head></head><body><p>Hello</p></body></html>';
83
+
84
+ const result = stripWrappingParagraphs(input);
85
+
86
+ assert.equal(result, input);
87
+ });
88
+
89
+ it('strips p tags in body', () => {
90
+ const input = '<!DOCTYPE html><html lang="en"><head></head><body><p><x-greeting></x-greeting></p></body></html>';
91
+ const expected = '<!DOCTYPE html><html lang="en"><head></head><body><x-greeting></x-greeting></body></html>';
92
+
93
+ const result = stripWrappingParagraphs(input);
94
+
95
+ assert.equal(result, expected);
96
+ });
97
+ });
98
+ });
@@ -0,0 +1,18 @@
1
+ const template = document.createElement('template');
2
+
3
+ template.innerHTML = `
4
+ <p>Hello from the greeting component!</p>
5
+ `;
6
+
7
+ export default class GreetingComponent extends HTMLElement {
8
+ constructor() {
9
+ super();
10
+ this.attachShadow({ mode: 'open' });
11
+ }
12
+
13
+ async connectedCallback() {
14
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
15
+ }
16
+ }
17
+
18
+ customElements.define('x-greeting', GreetingComponent);
@@ -0,0 +1,3 @@
1
+ ## 11ty + WCC Demo
2
+
3
+ <x-greeting></x-greeting>