@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 +10 -0
- package/.eleventy.js +15 -0
- package/.eslintignore +2 -0
- package/.eslintrc.js +220 -0
- package/.gitattributes +16 -0
- package/.github/workflows/ci-win.yml +31 -0
- package/.github/workflows/ci.yml +31 -0
- package/.nvmrc +1 -0
- package/README.md +103 -0
- package/TODO.md +7 -0
- package/demo/components/greeting.js +18 -0
- package/demo/index.md +3 -0
- package/netlify.toml +9 -0
- package/package.json +37 -0
- package/src/index.js +37 -0
- package/src/utils.js +61 -0
- package/test/plugin.test.js +80 -0
- package/test/utils.test.js +98 -0
- package/test-fixtures/components/greeting.js +18 -0
- package/test-fixtures/index.md +3 -0
package/.editorconfig
ADDED
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
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,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,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
package/netlify.toml
ADDED
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);
|