├── .nvmrc ├── test ├── stubs │ ├── empty.webc │ ├── style.webc │ ├── comment.webc │ ├── img.webc │ ├── issue-152 │ │ ├── parent-nohtml.webc │ │ ├── child.webc │ │ └── parent.webc │ ├── components │ │ ├── nested-child-empty.webc │ │ ├── nested-child.webc │ │ ├── img-plain.webc │ │ ├── component-data.webc │ │ ├── child-circular.webc │ │ ├── child-circular2.webc │ │ ├── img-as-root.webc │ │ ├── nested-child-slot-before-after.webc │ │ ├── nested-child-style-only.webc │ │ ├── script-html.webc │ │ ├── nested-child-script.webc │ │ ├── nested-child-slot.webc │ │ ├── nested-child-style.webc │ │ ├── html-evaluating-props-nothis.webc │ │ ├── html-evaluating-props.webc │ │ ├── img-props.webc │ │ ├── nested-child-style-script-both-empty.webc │ │ ├── with-uid.webc │ │ ├── child-root-empty-class.webc │ │ ├── nested-child-style-keep.webc │ │ ├── nested-child-namedslot.webc │ │ ├── assets.webc │ │ ├── with-uid-root.webc │ │ ├── child-root.webc │ │ ├── nested-child-slot-style.webc │ │ ├── scoped-style.webc │ │ ├── text-link-slot.webc │ │ ├── child-css-js-a.webc │ │ ├── child-css-js-b.webc │ │ ├── child-css-js-c.webc │ │ ├── child-css-js-d.webc │ │ ├── child-css-js-e.webc │ │ ├── child-css-js-f.webc │ │ ├── root-style.webc │ │ ├── scoped-override.webc │ │ ├── override-parent.webc │ │ ├── nested-child-namedslot-style.webc │ │ ├── scoped-override-collision-b.webc │ │ ├── img.webc │ │ ├── scoped-override-collision-a.webc │ │ ├── shadowroot.webc │ │ ├── override-parent-scoped.webc │ │ ├── render-slots.webc │ │ ├── shadowrootmode.webc │ │ ├── render-css-keep.webc │ │ ├── render-css-root.webc │ │ ├── render-css.webc │ │ ├── render-slots-raw.webc │ │ ├── render-css-root-override.webc │ │ └── clientside.webc │ ├── issue-80-b │ │ ├── c.webc │ │ ├── page.webc │ │ └── b.webc │ ├── issue-80 │ │ ├── c.webc │ │ ├── page.webc │ │ └── b.webc │ ├── slot.webc │ ├── component-in-page-mode.webc │ ├── issue-135 │ │ ├── page.webc │ │ └── component.webc │ ├── no-template.webc │ ├── externals │ │ ├── my-style.css │ │ ├── my-style-scoped.css │ │ ├── my-script.js │ │ ├── externals-scoped.webc │ │ ├── externals.webc │ │ ├── externals-dynamic.webc │ │ ├── externals-keep.webc │ │ ├── externals-urls.webc │ │ └── externals-urls-keep.webc │ ├── issue-118 │ │ ├── oh-no.webc │ │ └── page.webc │ ├── sample-require.cjs │ ├── global-components │ │ ├── subfolder │ │ │ └── my-custom-element.webc │ │ ├── my-custom-element.webc │ │ └── other-custom-element.webc │ ├── html-number.webc │ ├── attrs-dashes │ │ ├── component-attrs.webc │ │ └── my-component.webc │ ├── component-script-html.webc │ ├── issue-105 │ │ ├── test-p.webc │ │ └── test.js │ ├── issue-98 │ │ ├── component.webc │ │ └── page.webc │ ├── slot-unused.webc │ ├── issue-104 │ │ ├── root-attrs-component.webc │ │ ├── attrs-component.webc │ │ ├── attrs-object.webc │ │ ├── root-component.webc │ │ ├── root-override-attrs-component.webc │ │ ├── component.webc │ │ ├── component-context.webc │ │ └── setup-component.webc │ ├── issue-78 │ │ ├── a-component.webc │ │ ├── add-banner-to-css-root.webc │ │ ├── add-banner-to-css.webc │ │ ├── add-banner-to-css-render.webc │ │ ├── img.webc │ │ └── page.webc │ ├── issue-91 │ │ ├── img.webc │ │ └── page.webc │ ├── nested-link.webc │ ├── component-html-resolve.webc │ ├── dynamic-bucket │ │ ├── index.webc │ │ └── component.webc │ ├── empty-class.webc │ ├── issue-79 │ │ ├── components │ │ │ ├── other-component.webc │ │ │ └── my-component.webc │ │ └── pages │ │ │ └── articles │ │ │ └── my-page.webc │ ├── looping │ │ ├── components │ │ │ ├── card-header.webc │ │ │ ├── component.webc │ │ │ ├── card-actions.webc │ │ │ ├── card-thing.webc │ │ │ └── card-content.webc │ │ ├── issue-139.webc │ │ ├── array-value.webc │ │ ├── array.webc │ │ ├── object-key.webc │ │ ├── scoped-data.webc │ │ ├── array-object-keys.webc │ │ ├── array-object-values.webc │ │ ├── object.webc │ │ ├── script-setup-data.webc │ │ └── complex │ │ │ ├── entry-point.webc │ │ │ └── data.js │ ├── nested.webc │ ├── slot-fallback-content.webc │ ├── slot-named.webc │ ├── bucket-inherit │ │ ├── index.webc │ │ ├── child.webc │ │ └── component.webc │ ├── component-html-assets.webc │ ├── template.webc │ ├── slot-keep.webc │ ├── slot-raw.webc │ ├── template-custom.webc │ ├── if-component-style.webc │ ├── img-to-img.webc │ ├── nested-webc-raw.webc │ ├── slot-unused-default.webc │ ├── nested-alias.webc │ ├── nested-content.webc │ ├── nested-webc-keep.webc │ ├── plaintext-transform.webc │ ├── script-type.webc │ ├── slot-named-fallback.webc │ ├── template-custom-keep.webc │ ├── template-custom-notype.webc │ ├── using-css-keep.webc │ ├── using-css-root.webc │ ├── using-css.webc │ ├── class-mixins.webc │ ├── global-data.webc │ ├── props-missing.webc │ ├── setup-script │ │ ├── import-target.js │ │ ├── component.webc │ │ └── test.js │ ├── style-merge.webc │ ├── slot-nested-2.webc │ ├── slot-nested-3.webc │ ├── slot-nested.webc │ ├── slot-unused-2.webc │ ├── scoped.webc │ ├── using-css-root-override.webc │ ├── using-img.webc │ ├── import-alias-nested.webc │ ├── props-versus-text │ │ └── page.webc │ ├── using-uid.webc │ ├── webc-raw-prop.webc │ ├── deep-root │ │ └── component.webc │ ├── issue-138 │ │ ├── page.webc │ │ └── img.webc │ ├── nested-content-with-attr.webc │ ├── props-missing-nothis.webc │ ├── scoped-override.webc │ ├── using-img-plain.webc │ ├── bucket-noinherit │ │ ├── component.webc │ │ └── index.webc │ ├── dynamic-attributes-host-component-data │ │ ├── lol.webc │ │ └── page.webc │ ├── issue-156 │ │ └── link.webc │ ├── nested-reference.webc │ ├── alias-paragraph.webc │ ├── bucket-inherit-attrs │ │ ├── component.webc │ │ └── index.webc │ ├── import-keep.webc │ ├── webc-raw-html-prop.webc │ ├── nested-twice.webc │ ├── template-custom-nested.webc │ ├── nested-alias-reference.webc │ ├── head │ │ ├── head.webc │ │ ├── custom-head.webc │ │ ├── meta.webc │ │ └── head-is-component.webc │ ├── issue-3 │ │ ├── page.webc │ │ └── my-article.webc │ ├── render-slots-parent.webc │ ├── props.webc │ ├── import-alias-suffix.webc │ ├── import-alias.webc │ ├── issue-154 │ │ ├── c-red.webc │ │ ├── c-blue.webc │ │ └── page.webc │ ├── issue-67 │ │ ├── meta-social.webc │ │ └── page.webc │ ├── import-alias-nested-quickstart.webc │ ├── issue-175 │ │ └── c-data-table.webc │ ├── nested-no-shadowdom.webc │ ├── render-child-component.webc │ ├── two-style.webc │ ├── render.webc │ ├── render2.webc │ ├── import-twice.webc │ ├── defined-style-noroot.webc │ ├── render-async.webc │ ├── render-async2.webc │ ├── render-raw.webc │ ├── render3.webc │ ├── defined-style.webc │ ├── issue-94 │ │ └── component.webc │ ├── components-order.webc │ ├── asset-buckets.webc │ ├── issue-208 │ │ └── page.webc │ ├── html.webc │ ├── scoped-override-collisions.webc │ ├── scoped-top.webc │ ├── style-override.webc │ ├── render-raw-nokeep.webc │ ├── issue-85 │ │ └── page.webc │ ├── page.webc │ ├── components-list.webc │ ├── nested-multiple-slots.webc │ ├── page-capital-doctype-issue-24.webc │ ├── attribute-quotes.webc │ ├── fake_node_modules │ │ └── @11ty │ │ │ └── test │ │ │ └── syntax-highlighter.webc │ ├── nested-multiple-slots-raw.webc │ ├── issue-115 │ │ └── page.webc │ ├── props-no-this.webc │ ├── render-import.webc │ ├── render-require.webc │ ├── render-js-import.webc │ └── render-setup-import.webc ├── comments-test.js ├── issue-85-test.js ├── issue-94-test.js ├── props-versus-text-test.js ├── issue-135-test.js ├── issue-84-test.js ├── issue-138-test.js ├── issue-79-test.js ├── issue-98-test.js ├── issue-118-test.js ├── issue-91-test.js ├── issue-115-test.js ├── issue-83-test.js ├── issue-145-test.js ├── issue-156-test.js ├── issue-154-test.js ├── issue-80-test.js ├── issue-attrs-dashes-test.js ├── issue-88-test.js ├── shadowdom-test.js ├── classAttributeTest.js ├── attributeSerializerTest.js ├── issue-208-test.js ├── globalComponentsTest.js ├── issue-152-test.js ├── scoped-type-js-test.js ├── moduleResolutionTest.js ├── issue-104-test.js ├── issue-78-test.js ├── renderFunctionJsTest.js ├── cssTest.js ├── looping-test.js └── contentPropsTest.js ├── .gitignore ├── .npmignore ├── examples ├── aggregation │ ├── page.webc │ ├── components │ │ └── my-component.webc │ └── page.js ├── attributes │ ├── page.webc │ ├── components │ │ └── my-component.webc │ └── page.js ├── imports │ ├── components │ │ ├── my-component.webc │ │ ├── my-dynamic-component.webc │ │ └── my-remapped-component.webc │ ├── page.webc │ └── page.js ├── properties │ ├── components │ │ └── my-component.webc │ ├── page.webc │ └── page.js ├── lookup-attributes │ ├── components │ │ └── my-component.webc │ ├── page.webc │ └── page.js ├── scoped-css │ ├── page.webc │ ├── components │ │ ├── my-component.webc │ │ └── my-component-prefixed.webc │ └── page.js ├── custom-transforms │ ├── package.json │ ├── page.js │ └── page.webc ├── helpers │ ├── page.webc │ └── page.js ├── raw │ ├── page.webc │ └── page.js ├── render │ ├── components │ │ ├── add-banner-to-css.webc │ │ └── img.webc │ ├── page.webc │ └── page.js ├── html │ ├── page.webc │ └── page.js ├── page │ ├── page.js │ └── page.webc ├── slots │ ├── page.js │ ├── components │ │ └── my-component.webc │ └── page.webc └── stream │ ├── page.webc │ └── page.js ├── .gitattributes ├── .editorconfig ├── src ├── astCache.js ├── path.js ├── astModify.js ├── dynamicScript.js ├── streams.js ├── fsCache.js ├── looping.js ├── dataCascade.js ├── moduleResolution.js ├── assetManager.js ├── css.js ├── astQuery.js ├── componentManager.js └── attributeSerializer.js ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── LICENSE ├── package.json └── webc.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /test/stubs/empty.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/style.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/comment.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-152/parent-nohtml.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-empty.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-80-b/c.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-80/c.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-80/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/slot.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | playground 3 | .idea 4 | -------------------------------------------------------------------------------- /test/stubs/component-in-page-mode.webc: -------------------------------------------------------------------------------- 1 |
Test
-------------------------------------------------------------------------------- /test/stubs/components/nested-child.webc: -------------------------------------------------------------------------------- 1 | SSR content -------------------------------------------------------------------------------- /test/stubs/issue-135/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-152/child.webc: -------------------------------------------------------------------------------- 1 |

-------------------------------------------------------------------------------- /test/stubs/issue-80-b/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/no-template.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | playground 3 | examples 4 | .* 5 | -------------------------------------------------------------------------------- /test/stubs/components/img-plain.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/externals/my-style.css: -------------------------------------------------------------------------------- 1 | /* This is some CSS */ -------------------------------------------------------------------------------- /test/stubs/issue-118/oh-no.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/issue-152/parent.webc: -------------------------------------------------------------------------------- 1 |

-------------------------------------------------------------------------------- /test/stubs/sample-require.cjs: -------------------------------------------------------------------------------- 1 | module.exports = "Imported"; -------------------------------------------------------------------------------- /test/stubs/externals/my-style-scoped.css: -------------------------------------------------------------------------------- 1 | :host { color: red; } -------------------------------------------------------------------------------- /test/stubs/global-components/subfolder/my-custom-element.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/html-number.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/attrs-dashes/component-attrs.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/component-script-html.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/externals/my-script.js: -------------------------------------------------------------------------------- 1 | /* This is the external script */ -------------------------------------------------------------------------------- /test/stubs/issue-105/test-p.webc: -------------------------------------------------------------------------------- 1 |

I am an HTML-only component

-------------------------------------------------------------------------------- /test/stubs/issue-98/component.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/slot-unused.webc: -------------------------------------------------------------------------------- 1 |
Text
-------------------------------------------------------------------------------- /examples/aggregation/page.webc: -------------------------------------------------------------------------------- 1 | Default slot -------------------------------------------------------------------------------- /test/stubs/components/component-data.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/issue-104/root-attrs-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-78/a-component.webc: -------------------------------------------------------------------------------- 1 | woah i am component. i do things. -------------------------------------------------------------------------------- /test/stubs/issue-91/img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-link.webc: -------------------------------------------------------------------------------- 1 | ParentChild -------------------------------------------------------------------------------- /test/stubs/component-html-resolve.webc: -------------------------------------------------------------------------------- 1 |

-------------------------------------------------------------------------------- /test/stubs/components/child-circular.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/child-circular2.webc: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /test/stubs/dynamic-bucket/index.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/empty-class.webc: -------------------------------------------------------------------------------- 1 | Light dom -------------------------------------------------------------------------------- /test/stubs/global-components/my-custom-element.webc: -------------------------------------------------------------------------------- 1 | This is a global component. -------------------------------------------------------------------------------- /test/stubs/issue-104/attrs-component.webc: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /test/stubs/issue-104/attrs-object.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/issue-79/components/other-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-79/pages/articles/my-page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-80/b.webc: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /test/stubs/looping/components/card-header.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/nested.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/slot-fallback-content.webc: -------------------------------------------------------------------------------- 1 |
Fallback content
-------------------------------------------------------------------------------- /test/stubs/slot-named.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/bucket-inherit/index.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/component-html-assets.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/img-as-root.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-slot-before-after.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/nested-child-style-only.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/script-html.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-104/root-component.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/issue-80-b/b.webc: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /test/stubs/issue-98/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/template.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-script.webc: -------------------------------------------------------------------------------- 1 | SSR content -------------------------------------------------------------------------------- /test/stubs/components/nested-child-slot.webc: -------------------------------------------------------------------------------- 1 | SSR contentAfter slot content -------------------------------------------------------------------------------- /test/stubs/components/nested-child-style.webc: -------------------------------------------------------------------------------- 1 | SSR content -------------------------------------------------------------------------------- /test/stubs/slot-keep.webc: -------------------------------------------------------------------------------- 1 |
Fallback content
-------------------------------------------------------------------------------- /test/stubs/slot-raw.webc: -------------------------------------------------------------------------------- 1 |
Fallback content
-------------------------------------------------------------------------------- /test/stubs/template-custom.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/html-evaluating-props-nothis.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/components/html-evaluating-props.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/if-component-style.webc: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /test/stubs/img-to-img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-104/root-override-attrs-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/looping/components/component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-webc-raw.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/slot-unused-default.webc: -------------------------------------------------------------------------------- 1 |
Text
-------------------------------------------------------------------------------- /examples/attributes/page.webc: -------------------------------------------------------------------------------- 1 | This is the default slot -------------------------------------------------------------------------------- /examples/imports/components/my-component.webc: -------------------------------------------------------------------------------- 1 | Components don’t need a root element, y’all. 2 | -------------------------------------------------------------------------------- /examples/properties/components/my-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/img-props.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-style-script-both-empty.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/with-uid.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/issue-118/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-alias.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/nested-content.webc: -------------------------------------------------------------------------------- 1 | Before 2 | Child content 3 | After -------------------------------------------------------------------------------- /test/stubs/nested-webc-keep.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/plaintext-transform.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/script-type.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/imports/components/my-dynamic-component.webc: -------------------------------------------------------------------------------- 1 | This other component is dynamically imported!!! -------------------------------------------------------------------------------- /examples/imports/components/my-remapped-component.webc: -------------------------------------------------------------------------------- 1 | And this other component is a remapped component! -------------------------------------------------------------------------------- /examples/lookup-attributes/components/my-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/child-root-empty-class.webc: -------------------------------------------------------------------------------- 1 |
SSR content
-------------------------------------------------------------------------------- /test/stubs/components/nested-child-style-keep.webc: -------------------------------------------------------------------------------- 1 | SSR content -------------------------------------------------------------------------------- /test/stubs/slot-named-fallback.webc: -------------------------------------------------------------------------------- 1 |
Fallback content
-------------------------------------------------------------------------------- /test/stubs/template-custom-keep.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/template-custom-notype.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/using-css-keep.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/using-css-root.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/using-css.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/class-mixins.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/global-data.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-135/component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/props-missing.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/setup-script/import-target.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return "This is imported!"; 3 | } -------------------------------------------------------------------------------- /test/stubs/style-merge.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/slot-nested-2.webc: -------------------------------------------------------------------------------- 1 |
TextText
-------------------------------------------------------------------------------- /test/stubs/slot-nested-3.webc: -------------------------------------------------------------------------------- 1 |
TextText
-------------------------------------------------------------------------------- /test/stubs/slot-nested.webc: -------------------------------------------------------------------------------- 1 |
TextText
-------------------------------------------------------------------------------- /test/stubs/slot-unused-2.webc: -------------------------------------------------------------------------------- 1 |
Text

-------------------------------------------------------------------------------- /examples/lookup-attributes/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-namedslot.webc: -------------------------------------------------------------------------------- 1 | SSR contentAfter slot content -------------------------------------------------------------------------------- /test/stubs/looping/components/card-actions.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/scoped.webc: -------------------------------------------------------------------------------- 1 | Light dom content -------------------------------------------------------------------------------- /test/stubs/using-css-root-override.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/using-img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/aggregation/components/my-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/bucket-inherit/child.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/components/assets.webc: -------------------------------------------------------------------------------- 1 | HTML 2 |

3 | 4 | -------------------------------------------------------------------------------- /test/stubs/components/with-uid-root.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/import-alias-nested.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/looping/issue-139.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/props-versus-text/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/using-uid.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/stubs/webc-raw-prop.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/components/child-root.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-slot-style.webc: -------------------------------------------------------------------------------- 1 | SSR contentAfter slot content -------------------------------------------------------------------------------- /test/stubs/components/scoped-style.webc: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/stubs/components/text-link-slot.webc: -------------------------------------------------------------------------------- 1 | Default link text 2 | -------------------------------------------------------------------------------- /test/stubs/deep-root/component.webc: -------------------------------------------------------------------------------- 1 |
Some component content
-------------------------------------------------------------------------------- /test/stubs/issue-138/page.webc: -------------------------------------------------------------------------------- 1 | An excited Zach is trying to finish this documentation -------------------------------------------------------------------------------- /test/stubs/looping/array-value.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/looping/array.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/looping/object-key.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/looping/scoped-data.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/nested-content-with-attr.webc: -------------------------------------------------------------------------------- 1 | Before 2 | Child content 3 | After -------------------------------------------------------------------------------- /test/stubs/props-missing-nothis.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/scoped-override.webc: -------------------------------------------------------------------------------- 1 | Light dom content -------------------------------------------------------------------------------- /test/stubs/using-img-plain.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/bucket-noinherit/component.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/dynamic-attributes-host-component-data/lol.webc: -------------------------------------------------------------------------------- 1 |
Test
-------------------------------------------------------------------------------- /test/stubs/issue-156/link.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-79/components/my-component.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/nested-reference.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Templates 2 | *.webc text diff=html 3 | 4 | # Reclassify .webc files as HTML: 5 | *.webc linguist-language=HTML -------------------------------------------------------------------------------- /examples/attributes/components/my-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/scoped-css/page.webc: -------------------------------------------------------------------------------- 1 | Default slot 2 | Prefixed slot -------------------------------------------------------------------------------- /test/stubs/alias-paragraph.webc: -------------------------------------------------------------------------------- 1 | Before 2 |

This is a paragraph—we still cool?

3 | After -------------------------------------------------------------------------------- /test/stubs/bucket-inherit-attrs/component.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/components/child-css-js-a.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/child-css-js-b.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/child-css-js-c.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/child-css-js-d.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/child-css-js-e.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/child-css-js-f.webc: -------------------------------------------------------------------------------- 1 | BeforeAfter -------------------------------------------------------------------------------- /test/stubs/components/root-style.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/import-keep.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/looping/array-object-keys.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/webc-raw-html-prop.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/custom-transforms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "markdown-it": "^13.0.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/helpers/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/raw/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/scoped-override.webc: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/stubs/dynamic-attributes-host-component-data/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-twice.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | Child content 4 | 5 | 6 | After -------------------------------------------------------------------------------- /test/stubs/template-custom-nested.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/override-parent.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/looping/array-object-values.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/looping/object.webc: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/stubs/externals/externals-scoped.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | -------------------------------------------------------------------------------- /test/stubs/issue-91/page.webc: -------------------------------------------------------------------------------- 1 | An excited Zach is trying to finish this documentation -------------------------------------------------------------------------------- /test/stubs/nested-alias-reference.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | After -------------------------------------------------------------------------------- /test/stubs/bucket-noinherit/index.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/components/nested-child-namedslot-style.webc: -------------------------------------------------------------------------------- 1 | SSR contentAfter slot content -------------------------------------------------------------------------------- /test/stubs/head/head.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a title 5 | -------------------------------------------------------------------------------- /test/stubs/issue-3/page.webc: -------------------------------------------------------------------------------- 1 | 2 |

Article Title

3 |

Subtitle

4 |

Content

5 |
-------------------------------------------------------------------------------- /test/stubs/looping/script-setup-data.webc: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/stubs/render-slots-parent.webc: -------------------------------------------------------------------------------- 1 | 2 | This is content that I want to be raw in JS -------------------------------------------------------------------------------- /examples/properties/page.webc: -------------------------------------------------------------------------------- 1 | > -------------------------------------------------------------------------------- /test/stubs/components/scoped-override-collision-b.webc: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/stubs/props.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/scoped-override-collision-a.webc: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/stubs/head/custom-head.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/stubs/import-alias-suffix.webc: -------------------------------------------------------------------------------- 1 | Before 2 |

This is a paragraph—we still cool?

3 | After -------------------------------------------------------------------------------- /test/stubs/import-alias.webc: -------------------------------------------------------------------------------- 1 | Before 2 |

This is a paragraph—we still cool?

3 | After -------------------------------------------------------------------------------- /test/stubs/issue-154/c-red.webc: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /test/stubs/issue-67/meta-social.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/scoped-css/components/my-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/externals/externals.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | 3 | -------------------------------------------------------------------------------- /test/stubs/issue-154/c-blue.webc: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | -------------------------------------------------------------------------------- /test/stubs/bucket-inherit-attrs/index.webc: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /test/stubs/components/shadowroot.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/head/meta.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/stubs/import-alias-nested-quickstart.webc: -------------------------------------------------------------------------------- 1 | Before 2 |

This is a paragraph—we still cool?

3 | After -------------------------------------------------------------------------------- /test/stubs/components/override-parent-scoped.webc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/stubs/components/render-slots.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/shadowrootmode.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/dynamic-bucket/component.webc: -------------------------------------------------------------------------------- 1 |

Hi

2 | 3 | -------------------------------------------------------------------------------- /test/stubs/externals/externals-dynamic.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | 3 | -------------------------------------------------------------------------------- /test/stubs/global-components/other-custom-element.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | 5 | -------------------------------------------------------------------------------- /examples/render/components/add-banner-to-css.webc: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /examples/scoped-css/components/my-component-prefixed.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-175/c-data-table.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
-------------------------------------------------------------------------------- /test/stubs/components/render-css-keep.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/render-css-root.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/externals/externals-keep.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | 3 | -------------------------------------------------------------------------------- /test/stubs/issue-104/component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-104/component-context.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/looping/components/card-thing.webc: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /test/stubs/attrs-dashes/my-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/render-css.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components/render-slots-raw.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-no-shadowdom.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 | Child content 4 | 5 | 6 | After -------------------------------------------------------------------------------- /test/stubs/externals/externals-urls.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | 3 | -------------------------------------------------------------------------------- /test/stubs/issue-67/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

My First Heading

8 |

My first paragraph.

9 | 10 | -------------------------------------------------------------------------------- /test/stubs/render-child-component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | 9 | [/test/**/*] 10 | trim_trailing_whitespace = false 11 | insert_final_newline = unset -------------------------------------------------------------------------------- /test/stubs/looping/components/card-content.webc: -------------------------------------------------------------------------------- 1 | 2 |
3 |
    4 |
  • 5 |
-------------------------------------------------------------------------------- /test/stubs/two-style.webc: -------------------------------------------------------------------------------- 1 | Light dom content 2 | Light dom content -------------------------------------------------------------------------------- /test/stubs/components/render-css-root-override.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-138/img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/render.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /test/stubs/render2.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /test/stubs/import-twice.webc: -------------------------------------------------------------------------------- 1 | Before 2 | Light dom content 3 | Light dom content 4 | After -------------------------------------------------------------------------------- /examples/html/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/stubs/defined-style-noroot.webc: -------------------------------------------------------------------------------- 1 | 9 | This will be green at first and then switch to red when JS has registered the component. -------------------------------------------------------------------------------- /test/stubs/issue-78/add-banner-to-css-root.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/issue-78/add-banner-to-css.webc: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /test/stubs/render-async.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /test/stubs/render-async2.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /test/stubs/render-raw.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |
-------------------------------------------------------------------------------- /test/stubs/render3.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /examples/page/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | 7 | let { html, css, js, components } = await page.compile(); 8 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /test/stubs/defined-style.webc: -------------------------------------------------------------------------------- 1 | 9 |
This will be green at first and then switch to red when JS has registered the component.
-------------------------------------------------------------------------------- /test/stubs/externals/externals-urls-keep.webc: -------------------------------------------------------------------------------- 1 |

This is another global component.

2 | 3 | -------------------------------------------------------------------------------- /examples/raw/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | 7 | let { html, css, js, components } = await page.compile(); 8 | console.log({ html, css, js, components }); 9 | -------------------------------------------------------------------------------- /examples/render/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* Some other CSS content */ 5 | -------------------------------------------------------------------------------- /test/stubs/issue-94/component.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/setup-script/component.webc: -------------------------------------------------------------------------------- 1 |
2 | 9 |
-------------------------------------------------------------------------------- /test/stubs/components/clientside.webc: -------------------------------------------------------------------------------- 1 | This is the web component content. 2 | -------------------------------------------------------------------------------- /test/stubs/issue-78/add-banner-to-css-render.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/components-order.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CHILD CONTENT 5 | 6 | SIBLING CONTENT 7 | 8 | AUNT CONTENT 9 | -------------------------------------------------------------------------------- /test/stubs/asset-buckets.webc: -------------------------------------------------------------------------------- 1 |

Hi

2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/stubs/bucket-inherit/component.webc: -------------------------------------------------------------------------------- 1 |

Hi

2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/stubs/issue-3/my-article.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
Shadow dom slot
7 | 8 |
-------------------------------------------------------------------------------- /test/stubs/issue-104/setup-component.webc: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/stubs/issue-208/page.webc: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/stubs/issue-78/img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/head/head-is-component.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a title 6 | -------------------------------------------------------------------------------- /test/stubs/html.webc: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/stubs/scoped-override-collisions.webc: -------------------------------------------------------------------------------- 1 | Light dom content 2 | Light dom content -------------------------------------------------------------------------------- /test/stubs/scoped-top.webc: -------------------------------------------------------------------------------- 1 | 13 |
Testing testing
-------------------------------------------------------------------------------- /examples/slots/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents("components/my-component.webc"); 7 | 8 | let { html } = await page.compile(); 9 | console.log({ html }); 10 | -------------------------------------------------------------------------------- /test/stubs/style-override.webc: -------------------------------------------------------------------------------- 1 | 13 |
Testing testing
-------------------------------------------------------------------------------- /test/stubs/issue-154/page.webc: -------------------------------------------------------------------------------- 1 | Hi I am red 2 | Hi I am blue 3 | 4 | 5 | I am red. 6 | Hi I am blue 7 | still red 8 | 9 | 10 | 11 | I am blue. 12 | Hi I am red 13 | still blue 14 | -------------------------------------------------------------------------------- /test/stubs/render-raw-nokeep.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 |
-------------------------------------------------------------------------------- /examples/imports/page.webc: -------------------------------------------------------------------------------- 1 | WebC Example 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /test/stubs/issue-85/page.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/scoped-css/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents("components/**.webc"); 7 | 8 | let { html, css, js, components } = await page.compile(); 9 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /examples/aggregation/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents("components/my-component.webc"); 7 | 8 | let { html, css, js, components } = await page.compile(); 9 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /examples/attributes/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents("components/my-component.webc"); 7 | 8 | let { html, css, js, components } = await page.compile(); 9 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /examples/properties/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents("components/my-component.webc"); 7 | 8 | let { html, css, js, components } = await page.compile(); 9 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /test/stubs/components-list.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CHILD CONTENT 5 | 6 | 7 | AUNT CONTENT 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/lookup-attributes/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents("components/my-component.webc"); 7 | 8 | let { html, css, js, components } = await page.compile(); 9 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /examples/render/components/img.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-multiple-slots.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 |

Before slot content!

4 |

Slot 1 content

5 |
6 | 7 |

Slot 2 content

8 |
9 |

After slot content!

10 |
11 | After -------------------------------------------------------------------------------- /test/stubs/page-capital-doctype-issue-24.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/html/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | 7 | let { html, css, js, components } = await page.compile({ 8 | data: { 9 | dataProperty: "dataValue", 10 | }, 11 | }); 12 | console.log({ html, css, js, components }); 13 | -------------------------------------------------------------------------------- /test/stubs/looping/complex/entry-point.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/stubs/attribute-quotes.webc: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
-------------------------------------------------------------------------------- /test/stubs/issue-78/page.webc: -------------------------------------------------------------------------------- 1 |

normal component

2 | 3 | 4 |

js

5 | 8 | 9 |

js with component

10 | -------------------------------------------------------------------------------- /test/stubs/fake_node_modules/@11ty/test/syntax-highlighter.webc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/stubs/nested-multiple-slots-raw.webc: -------------------------------------------------------------------------------- 1 | Before 2 | 3 |

Before slot content!

4 |

Slot 1 content

5 |
6 | 7 |

Slot 2 content

8 |
9 |

After slot content!

10 |
11 | After -------------------------------------------------------------------------------- /examples/helpers/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.setHelper("alwaysBlue", () => { 7 | return "I'm blue, da ba dee da ba di" 8 | }); 9 | 10 | let { html, css, js, components } = await page.compile(); 11 | console.log({ html, css, js, components }); 12 | -------------------------------------------------------------------------------- /examples/slots/components/my-component.webc: -------------------------------------------------------------------------------- 1 |

Hello World!

2 | 3 | 4 | 5 |
6 | 7 |
8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /test/stubs/issue-115/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Test 9 | 10 | 11 |

Hi, this is a test

12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/render/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | page.defineComponents({ 7 | "avatar-image": "components/img.webc", 8 | "add-banner-to-css": "components/add-banner-to-css.webc" 9 | }); 10 | 11 | let { html, css, js, components } = await page.compile(); 12 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /src/astCache.js: -------------------------------------------------------------------------------- 1 | import { parse } from "parse5"; 2 | 3 | class AstCache { 4 | constructor() { 5 | this.ast = {}; 6 | } 7 | 8 | get(contents) { 9 | if(!this.ast[contents]) { 10 | this.ast[contents] = parse(contents, { 11 | scriptingEnabled: true, 12 | sourceCodeLocationInfo: true, 13 | }); 14 | } 15 | 16 | return this.ast[contents]; 17 | } 18 | } 19 | 20 | export { AstCache }; -------------------------------------------------------------------------------- /test/comments-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Server-side comments", async t => { 5 | let component = new WebC(); 6 | component.setContent(`Hello`); 7 | 8 | let { html } = await component.compile(); 9 | t.is(html, `Hello`); 10 | }); -------------------------------------------------------------------------------- /src/path.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { TemplatePath } from "@11ty/eleventy-utils"; 3 | 4 | class Path { 5 | // cross browser normalize a file path to use / 6 | static normalizePath(filePath) { 7 | if(typeof filePath === "string") { 8 | return TemplatePath.addLeadingDotSlash(filePath.split(path.sep).join("/")); 9 | } 10 | return filePath; 11 | } 12 | } 13 | 14 | export { Path }; 15 | -------------------------------------------------------------------------------- /test/issue-85-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Missing props #85", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/issue-85/page.webc"); 8 | 9 | let { html } = await component.compile(); 10 | 11 | t.is(html.trim(), `Your image didn’t have an alt so you get this link instead.`); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/stream/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebC Example 6 | 14 | 17 | 18 | 19 | WebC *is* HTML. 20 | 21 | -------------------------------------------------------------------------------- /examples/custom-transforms/page.js: -------------------------------------------------------------------------------- 1 | import MarkdownIt from "markdown-it"; 2 | import { WebC } from "../../webc.js"; 3 | 4 | let page = new WebC(); 5 | 6 | page.setInputPath("page.webc"); 7 | 8 | let md = new MarkdownIt({ html: true }); 9 | 10 | page.setTransform("md", (content) => { 11 | return md.render(content); 12 | }); 13 | 14 | let { html, css, js, components } = await page.compile(); 15 | console.log({ html, css, js, components }); -------------------------------------------------------------------------------- /test/issue-94-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("non-string props #94", async t => { 5 | let component = new WebC(); 6 | 7 | component.defineComponents("./test/stubs/issue-94/component.webc"); 8 | 9 | component.setContent(`|`); 10 | 11 | let { html } = await component.compile(); 12 | 13 | t.is(html.trim(), `number|number`); 14 | }); -------------------------------------------------------------------------------- /test/props-versus-text-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Props versus @text", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/props-versus-text/page.webc"); 8 | 9 | let { html } = await component.compile({ 10 | data: { 11 | pdf: "hello" 12 | } 13 | }); 14 | 15 | t.is(html.trim(), ``); 16 | }); 17 | -------------------------------------------------------------------------------- /test/issue-135-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("script render function implied template should still be HTML-only #135", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/issue-135/page.webc"); 8 | component.defineComponents("./test/stubs/issue-135/component.webc"); 9 | 10 | let { html } = await component.compile(); 11 | 12 | t.is(html.trim(), `

hello

`); 13 | }); 14 | -------------------------------------------------------------------------------- /src/astModify.js: -------------------------------------------------------------------------------- 1 | // Take extreme care when using these utilities, they mutate the Live AST 2 | 3 | class AstModify { 4 | static addAttribute(node, name, value) { 5 | node.attrs.push({ name, value }); 6 | } 7 | 8 | // Not in use 9 | // static removeAttribute(node, name) { 10 | // let index = node.attrs.findIndex(attr => attr.name === name); 11 | // if(index !== -1) { 12 | // node.attrs.splice(index, 1); 13 | // } 14 | // } 15 | } 16 | 17 | export { AstModify }; -------------------------------------------------------------------------------- /test/issue-84-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | // This doesn’t technically test console.log output but it’s close enough for now 😅 5 | test("Non string output from webc:type=js #84", async t => { 6 | let component = new WebC(); 7 | component.setContent(``); 10 | 11 | let { html } = await component.compile(); 12 | 13 | t.is(html, `1`); 14 | }); 15 | -------------------------------------------------------------------------------- /test/stubs/issue-105/test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../../../webc.js"; 3 | 4 | test("Template content issue #105", async t => { 5 | let component = new WebC(); 6 | 7 | component.defineComponents("./test/stubs/issue-105/test-p.webc"); 8 | 9 | component.setContent(``); 10 | 11 | let { html } = await component.compile(); 12 | 13 | t.is(html.trim(), ``); 14 | }); -------------------------------------------------------------------------------- /examples/slots/page.webc: -------------------------------------------------------------------------------- 1 | WebC Example 2 | 3 | 4 | 5 | 6 | 7 | This is the default slot 8 | 9 | 10 | 11 |
This is a named slot
12 |
13 | 14 | 15 | 16 |
This is another named slot with slot tag
17 |
-------------------------------------------------------------------------------- /test/issue-138-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Circular dep error #138", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/issue-138/page.webc"); 8 | component.defineComponents("./test/stubs/issue-138/img.webc"); 9 | 10 | let { html } = await component.compile(); 11 | 12 | t.is(html.trim(), `An excited Zach is trying to finish this documentation`); 13 | }); 14 | -------------------------------------------------------------------------------- /test/stubs/props-no-this.webc: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 |

5 |

6 |

7 |

8 |

9 |

10 |

11 |

12 |

13 |

14 |

15 |

-------------------------------------------------------------------------------- /test/issue-79-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("webc:import in Components should be relative to component file #79", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/issue-79/pages/articles/my-page.webc"); 8 | component.defineComponents("./test/stubs/issue-79/components/**.webc"); 9 | 10 | let { html } = await component.compile(); 11 | 12 | t.is(html.trim(), ``); 13 | }); 14 | 15 | -------------------------------------------------------------------------------- /examples/page/page.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebC Example 6 | 11 | 14 | 17 | 18 | 19 | 24 | WebC *is* HTML. 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/stream/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | 7 | let { html, css, js } = await page.stream(); 8 | 9 | // html stream 10 | await printStream(html); 11 | 12 | // css stream 13 | await printStream(css); 14 | 15 | // js stream 16 | await printStream(js); 17 | 18 | async function printStream(stream) { 19 | const chunks = []; 20 | for await (const chunk of stream) { 21 | chunks.push(Buffer.from(chunk)); 22 | } 23 | console.log(Buffer.concat(chunks).toString("utf-8")); 24 | } -------------------------------------------------------------------------------- /test/issue-98-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Issue #3 slot inconsistency", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/issue-98/page.webc"); 8 | component.defineComponents("./test/stubs/issue-98/component.webc"); 9 | 10 | let { html, css, js, components } = await component.compile(); 11 | 12 | t.deepEqual(components, [ 13 | "./test/stubs/issue-98/page.webc", 14 | "./test/stubs/issue-98/component.webc", 15 | ]); 16 | 17 | t.is(html, `
`); 18 | }); -------------------------------------------------------------------------------- /test/stubs/looping/complex/data.js: -------------------------------------------------------------------------------- 1 | export const contacts = [ 2 | { 3 | "name":"Joey", 4 | "color":"blue", 5 | "numbers":[1,2,3] 6 | }, 7 | { 8 | "name":"Phoebe", 9 | "color":"pink", 10 | "numbers":[1,2,3] 11 | }, 12 | { 13 | "name":"Chandler", 14 | "color":"orange", 15 | "numbers":[1,2,3] 16 | }, 17 | { 18 | "name":"Rachel", 19 | "color":"violet", 20 | "numbers":[1,2,3] 21 | }, 22 | { 23 | "name":"Monica", 24 | "color":"green", 25 | "numbers":[1,2,3] 26 | }, 27 | { 28 | "name":"Ross", 29 | "color":"red", 30 | "numbers":[1,2,3] 31 | } 32 | ] -------------------------------------------------------------------------------- /test/issue-118-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Slottable webc:type transform #118", async t => { 5 | t.plan(2); 6 | let component = new WebC(); 7 | 8 | component.setInputPath("./test/stubs/issue-118/page.webc"); 9 | component.defineComponents("./test/stubs/issue-118/oh-no.webc"); 10 | component.setTransform("override", (content) => { 11 | t.truthy( content ); 12 | return `This is an override`; 13 | }); 14 | 15 | let { html } = await component.compile(); 16 | 17 | t.is(html.trim(), `
This is an override
`); 18 | }); -------------------------------------------------------------------------------- /examples/imports/page.js: -------------------------------------------------------------------------------- 1 | import { WebC } from "../../webc.js"; 2 | 3 | let page = new WebC(); 4 | 5 | page.setInputPath("page.webc"); 6 | 7 | // Pass in a glob, using the file name as component name 8 | page.defineComponents("components/**.webc"); 9 | 10 | // Array of file names, using file name as component name 11 | // page.defineComponents(["components/my-component.webc"]); 12 | 13 | // Object maps component name to file name 14 | // page.defineComponents({ 15 | // "my-component": "components/my-component.webc" 16 | // }); 17 | 18 | let { html } = await page.compile(); 19 | console.log({ html }); -------------------------------------------------------------------------------- /test/issue-91-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Issue #91 render functions require template", async t => { 5 | let component = new WebC(); 6 | 7 | component.setBundlerMode(true); 8 | component.setInputPath("./test/stubs/issue-91/page.webc"); 9 | component.defineComponents("./test/stubs/issue-91/img.webc"); 10 | 11 | let { html, css, js, components } = await component.compile(); 12 | 13 | t.is(html, `An excited Zach is trying to finish this documentation`); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/custom-transforms/page.webc: -------------------------------------------------------------------------------- 1 | 31 | 32 | -------------------------------------------------------------------------------- /test/issue-115-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("New line at beginning issue #115", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/issue-115/page.webc"); 8 | 9 | let { html } = await component.compile(); 10 | 11 | t.is(html.trim(), ` 12 | 13 | 14 | 15 | 16 | 17 | Test 18 | 19 | 20 |

Hi, this is a test

21 | 22 | 23 | 24 | `); 25 | }); 26 | -------------------------------------------------------------------------------- /test/issue-83-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | // This doesn’t technically test console.log output but it’s close enough for now 😅 5 | test("console log in webc:type=js #83", async t => { 6 | t.plan(2); 7 | 8 | let component = new WebC(); 9 | component.setContent(``); 16 | 17 | let { html } = await component.compile({ 18 | data: { 19 | console: { 20 | log: function(message) { 21 | t.is(message, 2); 22 | } 23 | } 24 | } 25 | }); 26 | 27 | t.is(html, `hello 2`); 28 | }); 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | types: [opened, reopened, synchronize] 7 | 8 | env: 9 | YARN_GPG: no 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 17 | node: ["18", "20", "22", "24"] 18 | name: Node.js ${{ matrix.node }} on ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node }} 25 | - name: Cache node_modules 26 | uses: actions/cache@v3 27 | id: cache 28 | with: 29 | path: node_modules 30 | key: ${{ runner.os }}-node_modules-${{ hashFiles('**/package.json') }} 31 | 32 | - run: npm install 33 | 34 | - run: npm test 35 | -------------------------------------------------------------------------------- /test/stubs/render-import.webc: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /test/issue-145-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("@keyframes percentage webc:scoped #145", async t => { 5 | let component = new WebC(); 6 | 7 | component.setContent(``); 13 | 14 | let { html } = await component.compile(); 15 | 16 | t.is(html.trim(), ``); 17 | }); 18 | 19 | test("@keyframes from/to webc:scoped #145", async t => { 20 | let component = new WebC(); 21 | 22 | component.setContent(``); 28 | 29 | let { html } = await component.compile(); 30 | 31 | t.is(html.trim(), ``); 32 | }); -------------------------------------------------------------------------------- /test/stubs/render-require.webc: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /test/stubs/render-js-import.webc: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /test/stubs/render-setup-import.webc: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /test/issue-156-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("HTML entities in 35 | Hello World! 36 |
37 | After`); 38 | }); 39 | 40 | test("Using a web component with a declarative shadow root using shadowrootmode", async t => { 41 | let { html, css, js, components } = await testGetResultFor("./test/stubs/nested.webc", { 42 | "web-component": "./test/stubs/components/shadowrootmode.webc" 43 | }, {}, { globalData: "World" }); 44 | 45 | t.deepEqual(js, []); 46 | t.deepEqual(css, []); 47 | t.deepEqual(components, [ 48 | "./test/stubs/nested.webc", 49 | "./test/stubs/components/shadowrootmode.webc" 50 | ]); 51 | t.is(html, `Before 52 | 58 | After`); 59 | }); -------------------------------------------------------------------------------- /src/dataCascade.js: -------------------------------------------------------------------------------- 1 | class DataCascade { 2 | constructor() { 3 | this.helpers = {}; 4 | this.scopedHelpers = {}; 5 | } 6 | 7 | setGlobalData(data) { 8 | this.globalData = data; 9 | } 10 | 11 | setHelper(name, callback, isScoped = false) { 12 | if(isScoped) { 13 | this.scopedHelpers[name] = callback; 14 | } else { 15 | this.helpers[name] = callback; 16 | } 17 | } 18 | 19 | // the renderAttributes function is one of these 20 | setWebCGlobals(globals) { 21 | this.webcGlobals = globals; 22 | } 23 | 24 | getHelpers() { 25 | // unscoped 26 | return this.helpers; 27 | } 28 | 29 | getSetupScriptData() { 30 | return { 31 | // Breaking v0.12 dropped global data on top: use $data instead 32 | // ...this.globalData, 33 | ...this.helpers, 34 | $data: { 35 | ...this.globalData, 36 | }, 37 | webc: { 38 | helpers: this.scopedHelpers, 39 | ...this.webcGlobals, 40 | } 41 | } 42 | } 43 | 44 | /* 45 | * When `isTopLevelComponent` is not true (for inner components, not page-level) this scopes: 46 | * - global data under $data 47 | * - helpers under webc.* 48 | * 49 | * This prevents global data leaking into inner components. 50 | * Notably webc:setup always operates in top level component mode. 51 | */ 52 | getData(useGlobalDataAtTopLevel, attributes, ...additionalObjects) { 53 | let self = this; 54 | let objs = additionalObjects.reverse(); 55 | let globals = useGlobalDataAtTopLevel ? this.globalData : undefined; 56 | 57 | let ret = Object.assign({}, globals, this.helpers, ...objs, attributes, { 58 | get $data() { 59 | return self.globalData; 60 | }, 61 | webc: { 62 | helpers: this.scopedHelpers, 63 | attributes, 64 | ...this.webcGlobals, 65 | } 66 | }); 67 | 68 | return ret; 69 | } 70 | } 71 | 72 | export { DataCascade }; -------------------------------------------------------------------------------- /test/classAttributeTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Using this.class in an attribute #45", async (t) => { 5 | let component = new WebC(); 6 | component.setContent(`
`); 7 | component.setInputPath("./test/stubs/component-script-html.webc"); 8 | 9 | let { html } = await component.compile({ 10 | data: { 11 | class: "test-class" 12 | } 13 | }); 14 | t.is(html, `
`); 15 | }); 16 | 17 | test("Throw a better error message when attempting to use `class` in dynamic attributes without this #45", async (t) => { 18 | let component = new WebC(); 19 | component.setContent(`
`); 20 | component.setInputPath("./test/stubs/component-script-html.webc"); 21 | 22 | await t.throwsAsync(component.compile({ 23 | data: { 24 | class: "test-class" 25 | } 26 | }), { 27 | message: 'Error parsing dynamic attribute failed: `:class="class"`. `class` is a reserved word in JavaScript. Change `class` to `this.class` instead!' 28 | }); 29 | }); 30 | 31 | test("Real class in dynamic attributes #45", async (t) => { 32 | let component = new WebC(); 33 | component.setContent(`
`); 34 | component.setInputPath("./test/stubs/component-script-html.webc"); 35 | 36 | let { html } = await component.compile({ 37 | data: { 38 | class: "test-class" 39 | } 40 | }); 41 | t.is(html, `
`); 42 | }); 43 | 44 | test("Using Math!", async (t) => { 45 | let component = new WebC(); 46 | component.setContent(`
`); 47 | 48 | let { html } = await component.compile(); 49 | t.true(html.startsWith(`
`)); 51 | 52 | let num = parseFloat(html.slice(`
`.length), 10); 53 | t.is(typeof NaN, "number"); 54 | t.is(typeof num, "number"); 55 | t.true(!isNaN(num)); 56 | }); 57 | -------------------------------------------------------------------------------- /test/attributeSerializerTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | 3 | import { AttributeSerializer } from "../src/attributeSerializer.js"; 4 | 5 | // Inputs are guaranteed to be lower case (per the HTML specification) 6 | test("Normalize attribute", async t => { 7 | t.deepEqual(await AttributeSerializer.evaluateAttribute("test", "value"), { name: "test", value: "value", evaluation: false, privacy: "public", rawName: "test", rawValue: "value" }); 8 | t.deepEqual(await AttributeSerializer.evaluateAttribute("@test", "value"), { name: "test", value: "value", evaluation: false, privacy: "private", rawName: "@test", rawValue: "value" }); 9 | t.deepEqual(await AttributeSerializer.evaluateAttribute(":test", "value", { value: 1 }), { name: "test", value: 1, evaluation: "script", privacy: "public", rawName: ":test", rawValue: "value" }); 10 | }); 11 | 12 | test("Normalize attribute name", async t => { 13 | t.is(AttributeSerializer.camelCaseAttributeName("test"), "test"); 14 | t.is(AttributeSerializer.camelCaseAttributeName("my-test"), "myTest"); 15 | t.is(AttributeSerializer.camelCaseAttributeName("my-other-test"), "myOtherTest"); 16 | t.is(AttributeSerializer.camelCaseAttributeName("my-other--test"), "myOtherTest"); 17 | t.is(AttributeSerializer.camelCaseAttributeName("my-_other-test"), "my_otherTest"); 18 | t.is(AttributeSerializer.camelCaseAttributeName("-my-other-test"), "MyOtherTest"); 19 | t.is(AttributeSerializer.camelCaseAttributeName("my-other-test-"), "myOtherTest"); 20 | t.is(AttributeSerializer.camelCaseAttributeName("my-other-test------"), "myOtherTest"); 21 | t.is(AttributeSerializer.camelCaseAttributeName("m-y-other-test"), "mYOtherTest"); 22 | }); 23 | 24 | test("Normalize attributes for data", async t => { 25 | t.deepEqual(await AttributeSerializer.normalizeAttributesForData({"test": 1 }), {"test": 1 }); 26 | t.deepEqual(await AttributeSerializer.normalizeAttributesForData({"my-test": 1 }), {"myTest": 1 }); 27 | t.deepEqual(await AttributeSerializer.normalizeAttributesForData({"my-other-test": 1 }), {"myOtherTest": 1 }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/issue-208-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("HTML entities in 10 | 11 | `); 12 | 13 | let { html, css, js, components } = await component.compile(); 14 | 15 | t.deepEqual(js, []); 16 | t.deepEqual(css, []); 17 | t.deepEqual(components, []); 18 | t.is(html.trim(), ` 19 | 20 | `); 21 | }); 22 | 23 | test("HTML entities in 30 | 31 | `); 32 | 33 | let { html, css, js, components } = await component.compile(); 34 | 35 | t.deepEqual(js, []); 36 | t.deepEqual(css, []); 37 | t.deepEqual(components, []); 38 | t.is(html.trim(), ` 39 | 40 | `); 41 | }); 42 | 43 | test("Escaping in 53 | 54 | `); 55 | }); -------------------------------------------------------------------------------- /test/globalComponentsTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | 3 | import { WebC } from "../webc.js"; 4 | 5 | test("Uses a global component (register by glob)", async t => { 6 | let component = new WebC(); 7 | component.setContent(``); 8 | component.defineComponents("./test/stubs/global-components/*"); 9 | 10 | let { html, css, js, components } = await component.compile(); 11 | 12 | t.is(html, `This is a global component.`); 13 | 14 | t.deepEqual(js, []); 15 | t.deepEqual(css, []); 16 | t.deepEqual(components, ["./test/stubs/global-components/my-custom-element.webc"]); 17 | }); 18 | 19 | test("Uses a global component (registered explicitly with object)", async t => { 20 | let component = new WebC(); 21 | component.setContent(``); 22 | 23 | component.defineComponents({ 24 | "my-custom-element": "./test/stubs/global-components/my-custom-element.webc" 25 | }); 26 | 27 | let { html, css, js, components } = await component.compile(); 28 | 29 | t.is(html, `This is a global component.`); 30 | 31 | t.deepEqual(js, []); 32 | t.deepEqual(css, []); 33 | t.deepEqual(components, ["./test/stubs/global-components/my-custom-element.webc"]); 34 | }); 35 | 36 | test("Uses a global component (registered explicitly with Array)", async t => { 37 | let component = new WebC(); 38 | component.setContent(``); 39 | 40 | component.defineComponents(["./test/stubs/global-components/my-custom-element.webc"]); 41 | 42 | let { html, css, js, components } = await component.compile(); 43 | 44 | t.is(html, `This is a global component.`); 45 | 46 | t.deepEqual(js, []); 47 | t.deepEqual(css, []); 48 | t.deepEqual(components, ["./test/stubs/global-components/my-custom-element.webc"]); 49 | }); 50 | 51 | 52 | 53 | test("Uses a global component with CSS and JS", async t => { 54 | let component = new WebC(); 55 | component.setContent(``); 56 | component.defineComponents("./test/stubs/global-components/*"); 57 | component.setBundlerMode(true); 58 | 59 | let { html, css, js, components } = await component.compile(); 60 | 61 | t.is(html, `

This is another global component.

62 | 63 |
`); 64 | 65 | t.deepEqual(js, [` 66 | alert("hi"); 67 | `]); 68 | t.deepEqual(css, [` 69 | p { color: blue; } 70 | `]); 71 | t.deepEqual(components, ["./test/stubs/global-components/other-custom-element.webc"]); 72 | }); 73 | 74 | test("Naming collision errors", async t => { 75 | let component = new WebC(); 76 | t.throws(() => component.defineComponents("./test/stubs/global-components/**")); 77 | }); 78 | -------------------------------------------------------------------------------- /src/moduleResolution.js: -------------------------------------------------------------------------------- 1 | import { TemplatePath } from "@11ty/eleventy-utils"; 2 | import path from "node:path"; 3 | import { Path } from "./path.js"; 4 | 5 | class ModuleResolution { 6 | constructor(aliases) { 7 | this.setAliases(aliases); 8 | } 9 | 10 | static REGEX = { 11 | startsWithAlias: /^([^\:]+)\:/i 12 | }; 13 | 14 | setAliases(aliases = {}) { 15 | // TODO project root alias? 16 | this.aliases = Object.assign({ 17 | "npm": "./node_modules/" 18 | }, aliases); 19 | } 20 | 21 | setTagName(tagName) { 22 | this.tagName = tagName; 23 | } 24 | 25 | checkLocalPath(resolvedPath) { 26 | let projectDir = TemplatePath.getWorkingDir(); 27 | let modulePath = TemplatePath.absolutePath(projectDir, resolvedPath); 28 | 29 | // No references outside of the project are allowed 30 | if (!modulePath.startsWith(projectDir)) { 31 | throw new Error("Invalid import reference (must be in the project root), received: " + resolvedPath ); 32 | } 33 | } 34 | 35 | hasValidAlias(fullPath) { 36 | let starts = Object.keys(this.aliases); 37 | for(let start of starts) { 38 | if(fullPath.startsWith(`${start}:`)) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | static getAlias(fullPath) { 46 | let match = fullPath.match(ModuleResolution.REGEX.startsWithAlias); 47 | if(match && match[1]) { 48 | return match[1]; 49 | } 50 | return undefined; 51 | } 52 | 53 | resolveAliases(fullPath) { 54 | let alias = ModuleResolution.getAlias(fullPath); 55 | 56 | // unaliased, relative from component path 57 | if(!alias) { 58 | return Path.normalizePath(fullPath); 59 | } else if(!this.aliases[alias]) { 60 | throw new Error(`Invalid WebC aliased import path, requested: ${fullPath} (known aliases: ${Object.keys(this.aliases).join(", ")})`); 61 | } 62 | 63 | // aliases, are relative from project root 64 | let unprefixedPath = fullPath.slice(alias.length + 1); 65 | return Path.normalizePath(path.join(this.aliases[alias], unprefixedPath)); 66 | } 67 | 68 | // npm:@11ty/eleventy is supported when tag name is supplied by WebC (returns `node_modules/@11ty/eleventy/tagName.webc`) 69 | // npm:@11ty/eleventy/folderName deep folder name is not supported 70 | // npm:@11ty/eleventy/module.webc direct reference is supported (with deep folder names too) 71 | resolve(fullPath) { 72 | // resolve aliases first 73 | let resolvedPath = this.resolveAliases(fullPath); 74 | 75 | // make sure file is local to the project 76 | this.checkLocalPath(resolvedPath); 77 | 78 | // direct link to a webc file 79 | if(resolvedPath.endsWith(".webc")) { 80 | return resolvedPath; 81 | } 82 | 83 | if(this.tagName) { 84 | // Add the tagName and webc suffix 85 | return `${resolvedPath}/${this.tagName}.webc` 86 | } 87 | 88 | return resolvedPath; 89 | } 90 | } 91 | 92 | export { ModuleResolution }; 93 | -------------------------------------------------------------------------------- /test/issue-152-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Slotted global data access #152", async t => { 5 | let component = new WebC(); 6 | 7 | component.setBundlerMode(true); 8 | component.setContent(`Hello I am in the default slot.`); 9 | component.defineComponents("./test/stubs/issue-152/parent.webc"); 10 | 11 | let { html } = await component.compile({ 12 | data: { 13 | globalData: "Hello" 14 | } 15 | }); 16 | 17 | t.is(html.trim(), `

Hello I am in the default slot.Hello

`); 18 | }); 19 | 20 | test("Slotted global data access nested #152", async t => { 21 | let component = new WebC(); 22 | 23 | component.setBundlerMode(true); 24 | component.setContent(``); 25 | component.defineComponents("./test/stubs/issue-152/parent.webc"); 26 | component.defineComponents("./test/stubs/issue-152/child.webc"); 27 | 28 | let { html } = await component.compile({ 29 | data: { 30 | globalData: "Hello" 31 | } 32 | }); 33 | 34 | t.is(html.trim(), `

Hello

`); 35 | }); 36 | 37 | test("Slotted global data access even more nested #152", async t => { 38 | let component = new WebC(); 39 | 40 | component.setBundlerMode(true); 41 | component.setContent(``); 42 | component.defineComponents("./test/stubs/issue-152/parent.webc"); 43 | component.defineComponents("./test/stubs/issue-152/child.webc"); 44 | 45 | let { html } = await component.compile({ 46 | data: { 47 | globalData: "Hello" 48 | } 49 | }); 50 | 51 | t.is(html.trim(), `

Hello

`); 52 | }); 53 | 54 | test("Slotted global data access parent without slot #152", async t => { 55 | let component = new WebC(); 56 | 57 | component.setBundlerMode(true); 58 | component.setContent(``); 59 | component.defineComponents("./test/stubs/issue-152/parent-nohtml.webc"); 60 | component.defineComponents("./test/stubs/issue-152/child.webc"); 61 | 62 | let { html } = await component.compile({ 63 | data: { 64 | globalData: "Hello" 65 | } 66 | }); 67 | 68 | t.is(html.trim(), `

Hello

`); 69 | }); 70 | 71 | test("Slotted global data access parent without slot even nestier #152", async t => { 72 | let component = new WebC(); 73 | 74 | component.setBundlerMode(true); 75 | component.setContent(``); 76 | component.defineComponents("./test/stubs/issue-152/parent-nohtml.webc"); 77 | component.defineComponents("./test/stubs/issue-152/child.webc"); 78 | 79 | let { html } = await component.compile({ 80 | data: { 81 | globalData: "Hello" 82 | } 83 | }); 84 | 85 | t.is(html.trim(), `

Hello

`); 86 | }); 87 | -------------------------------------------------------------------------------- /test/scoped-type-js-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("webc:scoped with webc:type=js", async t => { 5 | let component = new WebC(); 6 | component.setBundlerMode(true); 7 | component.setContent(``); 10 | 11 | let { html, css } = await component.compile(); 12 | 13 | t.is(html.trim(), ""); 14 | t.deepEqual(css, [".wnqfewpis div{color:red}"]); 15 | }); 16 | 17 | 18 | test("webc:scoped webc:keep with webc:type=js", async t => { 19 | let component = new WebC(); 20 | component.setBundlerMode(true); 21 | component.setContent(``); 24 | 25 | let { html, css } = await component.compile(); 26 | 27 | t.is(html.trim(), ``); 28 | t.deepEqual(css, []); 29 | }); 30 | 31 | test("webc:scoped webc:keep with webc:type=js with wrapper element", async t => { 32 | let component = new WebC(); 33 | component.setBundlerMode(true); 34 | component.setContent(`
35 | 38 |
`); 39 | 40 | await t.throwsAsync(async () => component.compile(), { 41 | message: "Could not find any top level \`; 50 | `); 51 | 52 | let { html, css } = await component.compile(); 53 | 54 | t.is(html.trim(), ``); 55 | t.deepEqual(css, ["div { color: red; }"]); 56 | }); 57 | 58 | test("nested content, style webc:scoped from a webc:type=js", async t => { 59 | let component = new WebC(); 60 | component.setBundlerMode(true); 61 | component.setContent(``); 64 | 65 | await t.throwsAsync(async () => component.compile(), { 66 | message: "Could not find any top level `); 95 | }); 96 | 97 | test("Docs css example using wrapper element #78", async t => { 98 | let component = new WebC(); 99 | 100 | component.setContent(``); 103 | component.defineComponents("./test/stubs/issue-78/add-banner-to-css-root.webc"); 104 | 105 | let { html } = await component.compile(); 106 | 107 | t.is(html.trim(), ``); 113 | }); 114 | 115 | test("Docs css example using render #78", async t => { 116 | let component = new WebC(); 117 | 118 | component.setContent(``); 121 | component.defineComponents("./test/stubs/issue-78/add-banner-to-css-render.webc"); 122 | 123 | let { html } = await component.compile(); 124 | 125 | t.is(html.trim(), ``); 131 | }); 132 | -------------------------------------------------------------------------------- /src/css.js: -------------------------------------------------------------------------------- 1 | import { generate, parse, walk } from "css-tree"; 2 | 3 | class CssPrefixer { 4 | constructor(prefix) { 5 | if(!prefix) { 6 | throw new Error("No prefix was passed to the CSS prefixer!"); 7 | } 8 | this.prefix = prefix; 9 | } 10 | 11 | static processWithoutTransformation(str) { 12 | let ast = parse(str, { 13 | positions: true 14 | }); 15 | 16 | return generate(ast); 17 | } 18 | 19 | setFilePath(filePath) { 20 | this.filePath = filePath; 21 | } 22 | 23 | parse(str) { 24 | return parse(str, { 25 | // filename: this.filePath, 26 | positions: true 27 | }) 28 | } 29 | 30 | process(cssString) { 31 | let ast = this.parse(cssString); 32 | 33 | let skipLevel = 0; 34 | 35 | walk(ast, { 36 | visit: "Selector", 37 | enter: (node, item, list) => { 38 | let first = node.children.first; 39 | 40 | const shouldSkip = 41 | skipLevel > 0 || 42 | // from/to in @keyframes 43 | (first.type === "TypeSelector" && first.name === "from") || 44 | (first.type === "TypeSelector" && first.name === "to") || 45 | // percentage selectors in @keyframes 46 | (first.type === "Percentage"); 47 | 48 | if (shouldSkip) { 49 | // do nothing 50 | } else if ( 51 | first.type === "PseudoClassSelector" && 52 | (first.name === "host" || first.name === "host-context") 53 | ) { 54 | // Transform :host and :host-context pseudo classes to 55 | // use the prefix class 56 | node.children.shift(); 57 | 58 | const pseudoClassParamChildren = first.children ? first.children : null; 59 | 60 | if (first.name === "host") { 61 | // Replace :host with the prefix class 62 | if (pseudoClassParamChildren) { 63 | // Any param children of a :host() functional pseudo class should be moved up to 64 | // be directly after the prefix class 65 | // ie, :host(.foo) -> .prefix.foo 66 | node.children.prependList( 67 | // :host can only accept one param, so we can safely use the first child 68 | pseudoClassParamChildren.first.children 69 | ); 70 | } 71 | node.children.prepend( 72 | list.createItem({ 73 | type: "ClassSelector", 74 | name: this.prefix, 75 | }) 76 | ); 77 | } else if (first.name === "host-context") { 78 | // Replace :host-context with the prefix class and 79 | // place any param children appropriately before the prefix class 80 | node.children.prepend( 81 | list.createItem({ 82 | type: "ClassSelector", 83 | name: this.prefix, 84 | }) 85 | ); 86 | 87 | if (pseudoClassParamChildren) { 88 | // Any param children of a :host-context() functional pseudo class should be moved up to 89 | // be parents before the prefix class 90 | // ie, :host-context(.foo) div -> .foo .prefix div 91 | node.children.prepend( 92 | list.createItem({ 93 | type: "Combinator", 94 | name: " ", 95 | }) 96 | ); 97 | node.children.prependList( 98 | // :host-context can only accept one param, so we can safely use the first child 99 | pseudoClassParamChildren.first.children 100 | ); 101 | } 102 | } 103 | } else { 104 | // list won’t exist for `@supports selector(*)` (but we don’t want to prefix that anyway) 105 | if(list) { 106 | // Prepand the prefix class in front of all selectors 107 | // which don't include :host or :host-context 108 | node.children.prepend( 109 | list.createItem({ 110 | type: "Combinator", 111 | name: " ", 112 | }) 113 | ); 114 | node.children.prepend( 115 | list.createItem({ 116 | type: "ClassSelector", 117 | name: this.prefix, 118 | }) 119 | ); 120 | } 121 | } 122 | 123 | node.children.forEach((node, item, list) => { 124 | if(node.type === "PseudoClassSelector") { 125 | skipLevel++; 126 | } 127 | }); 128 | }, 129 | leave: (node, item, list) => { 130 | node.children.forEach((node) => { 131 | if(node.type === "PseudoClassSelector") { 132 | skipLevel--; 133 | } 134 | }); 135 | } 136 | }); 137 | 138 | return generate(ast, { 139 | sourceMap: false 140 | }); 141 | } 142 | } 143 | 144 | export { CssPrefixer }; 145 | -------------------------------------------------------------------------------- /test/stubs/setup-script/test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import test from "ava"; 3 | import { WebC } from "../../../webc.js"; 4 | 5 | test("webc:setup #87", async t => { 6 | let component = new WebC(); 7 | 8 | component.setContent(`
9 | 16 |
`); 17 | 18 | let { html } = await component.compile(); 19 | 20 | t.is(html.trim(), `
1
21 | 22 |
blue
`); 23 | }); 24 | 25 | test("webc:setup with a helper #87", async t => { 26 | let component = new WebC(); 27 | 28 | component.setHelper("alwaysYellow", () => "yellow"); 29 | 30 | component.setContent(` 35 |
`); 36 | 37 | let { html } = await component.compile(); 38 | 39 | t.is(html.trim(), `
yellow
`); 40 | }); 41 | 42 | test("webc:setup with global data #87", async t => { 43 | let component = new WebC(); 44 | 45 | component.setHelper("alwaysYellow", () => "yellow"); 46 | 47 | component.setContent(` 52 |
`); 53 | 54 | let { html } = await component.compile({ 55 | data: { 56 | globalDataValue: "hello" 57 | } 58 | }); 59 | 60 | t.is(html.trim(), `
hello
`); 61 | }); 62 | 63 | 64 | test("webc:setup with child component #87", async t => { 65 | let component = new WebC(); 66 | component.setHelper("globalFunction", (a) => a); 67 | 68 | component.defineComponents("./test/stubs/setup-script/component.webc"); 69 | 70 | component.setContent(`
71 | 78 | 79 |
`); 80 | 81 | let { html } = await component.compile(); 82 | 83 | t.is(html.trim(), `
1
84 | 85 |
2
86 | 87 |
red
88 |
blue
`); 89 | }); 90 | 91 | test("webc:setup without export", async t => { 92 | let component = new WebC(); 93 | 94 | component.setContent(`
95 | `); 98 | 99 | let { html } = await component.compile(); 100 | 101 | // should be empty (no export) 102 | t.is(html.trim(), `
`); 103 | }); 104 | 105 | test("webc:setup with export", async t => { 106 | let component = new WebC(); 107 | 108 | component.setContent(`
109 | `); 112 | 113 | let { html } = await component.compile(); 114 | 115 | t.is(html.trim(), `
1
`); 116 | }); 117 | 118 | test("webc:setup with function (no export)", async t => { 119 | let component = new WebC(); 120 | 121 | component.setContent(` 126 |
`); 127 | 128 | let e = await t.throwsAsync(() => component.compile()); 129 | t.true(e.message.startsWith(`Evaluating a dynamic attribute failed: \`@html="alwaysBlue()"\``)); 130 | }); 131 | 132 | test("webc:setup with function (export)", async t => { 133 | let component = new WebC(); 134 | 135 | component.setContent(` 140 |
`); 141 | 142 | let { html } = await component.compile(); 143 | 144 | t.is(html.trim(), `
blue
`); 145 | }); 146 | 147 | test("webc:setup with import and export", async t => { 148 | let component = new WebC(); 149 | 150 | // Absolute path used here 151 | component.setInputPath(path.resolve("./test/stubs/setup-script/test.webc")); 152 | component.setContent(` 158 |
`); 159 | 160 | let { html } = await component.compile(); 161 | 162 | t.is(html.trim(), `
This is imported!
`); 163 | }); 164 | 165 | // TODO swap `export` to `import` 166 | // probably a bug in `import-module-string` 167 | test.skip("webc:setup with import combo export", async t => { 168 | let component = new WebC(); 169 | 170 | // Absolute path used here 171 | component.setInputPath(path.resolve("./test/stubs/setup-script/test.webc")); 172 | component.setContent(` 175 |
`); 176 | 177 | let { html } = await component.compile(); 178 | 179 | t.is(html.trim(), `
This is imported!
`); 180 | }); 181 | 182 | test("webc:setup with encodeURIComponent", async t => { 183 | let component = new WebC(); 184 | 185 | component.setContent(` 190 |
`); 191 | 192 | let { html } = await component.compile(); 193 | 194 | t.is(html.trim(), `
this%20is%20a%20test
`); 195 | }); -------------------------------------------------------------------------------- /test/renderFunctionJsTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Using webc:type=js", async t => { 5 | let component = new WebC(); 6 | 7 | // identical to 8 | component.setContent(` 9 | 10 | 13 | `); 14 | 15 | let { html, css, js, components } = await component.compile({ 16 | data: { 17 | myArray: [1,2,3,4] 18 | } 19 | }); 20 | 21 | t.deepEqual(js, []); 22 | t.deepEqual(css, []); 23 | t.deepEqual(components, []); 24 | 25 | t.is(html, ` 26 | 27 | 1/2/3/4 28 | 29 | `); 30 | }); 31 | 32 | test("Using webc:type=js with require", async t => { 33 | let component = new WebC(); 34 | 35 | // Needs a filePath for require/import 36 | component.setInputPath("./component.webc"); 37 | 38 | // identical to 39 | component.setContent(` 40 | 41 | 42 | 46 | 47 | `); 48 | 49 | let { html, css, js, components } = await component.compile(); 50 | 51 | t.deepEqual(js, []); 52 | t.deepEqual(css, []); 53 | t.deepEqual(components, ["./component.webc"]); 54 | 55 | t.is(html, ` 56 | 57 | 58 | Imported 59 | 60 | 61 | `); 62 | }); 63 | 64 | // Seems like this ignores webc:type and outputs the 71 | `); 72 | 73 | let { html, css, js, components } = await component.compile({ 74 | data: { 75 | myArray: [1,2,3,4] 76 | } 77 | }); 78 | 79 | t.deepEqual(js, []); 80 | t.deepEqual(css, []); 81 | t.deepEqual(components, []); 82 | 83 | t.is(html, ` 84 | 85 | 1/2/3/4 86 | 87 | `); 88 | }); 89 | 90 | test("Using webc:type=js and promises", async t => { 91 | let component = new WebC(); 92 | 93 | component.setContent(` 94 | 95 | 96 | 105 | 106 | `); 107 | 108 | let { html, css, js, components } = await component.compile({ 109 | data: { 110 | myArray: [1,2,3,4] 111 | } 112 | }); 113 | 114 | t.deepEqual(js, []); 115 | t.deepEqual(css, []); 116 | t.deepEqual(components, []); 117 | 118 | t.is(html, ` 119 | 120 | 121 | 1,2,3,4 122 | 123 | 124 | `); 125 | }); 126 | 127 | test("Using webc:type=js and async function", async t => { 128 | let component = new WebC(); 129 | 130 | component.setContent(` 131 | 132 | 133 | 146 | 147 | `); 148 | 149 | let { html, css, js, components } = await component.compile({ 150 | data: { 151 | myArray: [1,2,3,4] 152 | } 153 | }); 154 | 155 | t.deepEqual(js, []); 156 | t.deepEqual(css, []); 157 | t.deepEqual(components, []); 158 | 159 | t.is(html, ` 160 | 161 | 162 | 1,2,3,4 163 | 164 | 165 | `); 166 | }); 167 | 168 | 169 | test("Using webc:type=js and if true", async t => { 170 | let component = new WebC(); 171 | 172 | component.setContent(` 173 | 174 | 175 | 184 | 185 | `); 186 | 187 | let { html, css, js, components } = await component.compile(); 188 | 189 | t.deepEqual(js, []); 190 | t.deepEqual(css, []); 191 | t.deepEqual(components, []); 192 | 193 | t.is(html, ` 194 | 195 | 196 | true 197 | 198 | 199 | `); 200 | }); 201 | 202 | test("Using webc:type=js and if false", async t => { 203 | let component = new WebC(); 204 | 205 | component.setContent(` 206 | 207 | 208 | 217 | 218 | `); 219 | 220 | let { html, css, js, components } = await component.compile(); 221 | 222 | t.deepEqual(js, []); 223 | t.deepEqual(css, []); 224 | t.deepEqual(components, []); 225 | 226 | t.is(html, ` 227 | 228 | 229 | false 230 | 231 | 232 | `); 233 | }); 234 | -------------------------------------------------------------------------------- /test/cssTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { CssPrefixer } from "../src/css.js"; 3 | 4 | test("Selector tests", t => { 5 | let c = new CssPrefixer("my-prefix"); 6 | 7 | t.is(c.process(""), ""); 8 | t.is(c.process("div {}"), ".my-prefix div{}"); 9 | t.is(c.process("* {}"), ".my-prefix *{}"); /* huh? */ 10 | t.is(c.process("*.warning {}"), ".my-prefix *.warning{}"); 11 | t.is(c.process("* [lang^=en] {}"), ".my-prefix * [lang^=en]{}"); 12 | t.is(c.process(":before {}"), ".my-prefix :before{}"); 13 | }); 14 | 15 | test("Class, sibling selectors", t => { 16 | let c = new CssPrefixer("my-prefix"); 17 | 18 | t.is(c.process(".class1 {}"), ".my-prefix .class1{}"); 19 | t.is(c.process(".class1 + .class2 {}"), ".my-prefix .class1+.class2{}"); 20 | t.is(c.process(".class1 ~ .class2 {}"), ".my-prefix .class1~.class2{}"); 21 | }); 22 | 23 | test("ID selectors", t => { 24 | let c = new CssPrefixer("my-prefix"); 25 | 26 | t.is(c.process("#test {}"), ".my-prefix #test{}"); 27 | }); 28 | 29 | test("Attribute selectors", t => { 30 | let c = new CssPrefixer("my-prefix"); 31 | 32 | t.is(c.process("[class~=class_name] {}"), ".my-prefix [class~=class_name]{}"); 33 | t.is(c.process("a[title] {}"), ".my-prefix a[title]{}"); 34 | t.is(c.process(`a[href$=".org"] {}`), `.my-prefix a[href$=".org"]{}`); 35 | t.is(c.process(`a[href^="https"][href$=".org"] {}`), `.my-prefix a[href^="https"][href$=".org"]{}`); 36 | t.is(c.process(`div[lang|="zh"] {}`), `.my-prefix div[lang|="zh"]{}`); 37 | }); 38 | 39 | test("List selectors", t => { 40 | let c = new CssPrefixer("my-prefix"); 41 | 42 | t.is(c.process(`span, div {}`), `.my-prefix span,.my-prefix div{}`); 43 | t.is(c.process(`h1,h2, h3, h4 ,h5, h6 {}`), `.my-prefix h1,.my-prefix h2,.my-prefix h3,.my-prefix h4,.my-prefix h5,.my-prefix h6{}`); 44 | t.is(c.process(`h1, h2:maybe-unsupported, h3 { font-family: sans-serif }`), `.my-prefix h1,.my-prefix h2:maybe-unsupported,.my-prefix h3{font-family:sans-serif}`); 45 | t.is(c.process(`:is(h1, h2:maybe-unsupported, h3) { font-family: sans-serif }`), `.my-prefix :is(h1,h2:maybe-unsupported,h3){font-family:sans-serif}`); 46 | t.is(c.process(`:is(h1, :not(h2), h3) { font-family: sans-serif }`), `.my-prefix :is(h1,:not(h2),h3){font-family:sans-serif}`); 47 | }); 48 | 49 | test("Descendent selectors", t => { 50 | let c = new CssPrefixer("my-prefix"); 51 | 52 | t.is(c.process(`li li {}`), `.my-prefix li li{}`); 53 | t.is(c.process(`li > li {}`), `.my-prefix li>li{}`); 54 | }); 55 | 56 | test("Pseudo classes", t => { 57 | let c = new CssPrefixer("my-prefix"); 58 | 59 | t.is(c.process(`:not(p) {}`), `.my-prefix :not(p){}`); 60 | t.is(c.process(`div:not([lang]) {}`), `.my-prefix div:not([lang]){}`); 61 | 62 | t.is(c.process(`li:first-of-type li {}`), `.my-prefix li:first-of-type li{}`); 63 | t.is(c.process(`:hover {}`), `.my-prefix :hover{}`); 64 | t.is(c.process(`:lang(en-US) {}`), `.my-prefix :lang(en-US){}`); 65 | t.is(c.process(`:empty {}`), `.my-prefix :empty{}`); 66 | t.is(c.process(`a:has(> img) {}`), `.my-prefix a:has(>img){}`); 67 | 68 | t.is(c.process(`:host {}`), `.my-prefix{}`); 69 | t.is(c.process(`:host:not(p) div {}`), `.my-prefix:not(p) div{}`); 70 | t.is(c.process(`:host.footer div {}`), `.my-prefix.footer div{}`); // same as :host(.footer) 71 | 72 | t.is(c.process(`:host(.footer) div {}`), `.my-prefix.footer div{}`); 73 | t.is( 74 | c.process(`:host(.footer:not([lang])) .link {}`), 75 | `.my-prefix.footer:not([lang]) .link{}` 76 | ); 77 | t.is( 78 | c.process(`:host(:is(div, span)) {}`), 79 | `.my-prefix:is(div,span){}` 80 | ); 81 | 82 | t.is(c.process(`:host-context(html) div {}`), `html .my-prefix div{}`); 83 | t.is( 84 | c.process(`:host-context(html body.dark-theme) div {}`), 85 | `html body.dark-theme .my-prefix div{}` 86 | ); 87 | t.is( 88 | c.process(`:host-context(html body:is(.dark-theme, .light-theme)) div {}`), 89 | `html body:is(.dark-theme,.light-theme) .my-prefix div{}` 90 | ); 91 | }); 92 | 93 | test("Pseudo elements", t => { 94 | let c = new CssPrefixer("my-prefix"); 95 | 96 | t.is(c.process(`:before {}`), `.my-prefix :before{}`); 97 | t.is(c.process(`::before {}`), `.my-prefix ::before{}`); 98 | }); 99 | 100 | test("@keyframes", t => { 101 | let c = new CssPrefixer("my-prefix"); 102 | 103 | t.is(c.process(`@keyframes slidein { 104 | from { 105 | transform: translateX(0%); 106 | } 107 | 108 | to { 109 | transform: translateX(100%); 110 | } 111 | }`), `@keyframes slidein{from{transform:translateX(0%)}to{transform:translateX(100%)}}`); 112 | }); 113 | 114 | test("@font-face", t => { 115 | let c = new CssPrefixer("my-prefix"); 116 | 117 | t.is(c.process(`@font-face { 118 | font-family: "Open Sans"; 119 | src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"); 120 | }`), `@font-face{font-family:"Open Sans";src:url(/fonts/OpenSans-Regular-webfont.woff2)format("woff2")}`); 121 | }); 122 | 123 | test("@media (min-width)", t => { 124 | let c = new CssPrefixer("my-prefix"); 125 | 126 | t.is(c.process(`@media (min-width: 20em) { 127 | div { color: red; } 128 | }`), `@media (min-width:20em){.my-prefix div{color:red}}`); 129 | }); 130 | 131 | test.skip("lch() color, Issue #82", t => { 132 | // let c = new CssPrefixer("my-prefix"); 133 | // t.is(c.process(`div { color: lch(97% 44.2 240); }`), `.my-prefix div{color:lch(97% 44.2 240)}`); 134 | 135 | t.is(CssPrefixer.processWithoutTransformation(`div { color: lch(67.5345% 42.5 258.2); }`), `div { color: lch(67.5345% 42.5 258.2); }`); 136 | }); 137 | 138 | test("@supports selector(*) issue #232", t => { 139 | let c = new CssPrefixer("my-prefix"); 140 | 141 | t.is(c.process(`@supports selector(*) { 142 | div { color: red; } 143 | }`), `@supports selector(*){.my-prefix div{color:red}}`); 144 | }); 145 | -------------------------------------------------------------------------------- /test/looping-test.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | test("Basic webc:for (complex key) over Array", async t => { 5 | let component = new WebC(); 6 | 7 | component.setInputPath("./test/stubs/looping/array.webc"); 8 | 9 | let { html } = await component.compile(); 10 | 11 | t.is(html.trim(), `
2-1
12 |
3-2
13 |
4-3
`); 14 | }); 15 | 16 | test("Basic webc:for (simple key) over Array", async t => { 17 | let component = new WebC(); 18 | 19 | component.setInputPath("./test/stubs/looping/array-value.webc"); 20 | 21 | let { html } = await component.compile(); 22 | 23 | t.is(html.trim(), `
2-undefined
24 |
3-undefined
25 |
4-undefined
`); 26 | }); 27 | 28 | test("webc:for over Array has injected data available on child nodes", async t => { 29 | let component = new WebC(); 30 | 31 | component.setInputPath("./test/stubs/looping/scoped-data.webc"); 32 | 33 | let { html } = await component.compile(); 34 | 35 | t.is(html.trim(), `
1-0
36 |
2-1
37 |
3-2
38 |
4-3
`); 39 | }); 40 | 41 | 42 | test("Basic webc:for (complex key) over Object", async t => { 43 | let component = new WebC(); 44 | 45 | component.setInputPath("./test/stubs/looping/object.webc"); 46 | 47 | let { html } = await component.compile(); 48 | 49 | t.is(html.trim(), `
a-1-0
50 |
c-4-2
`); 51 | }); 52 | 53 | test("Basic webc:for (simple key) over Object", async t => { 54 | let component = new WebC(); 55 | 56 | component.setInputPath("./test/stubs/looping/object-key.webc"); 57 | 58 | let { html } = await component.compile(); 59 | 60 | t.is(html.trim(), `
a-undefined
61 |
c-undefined
`); 62 | }); 63 | 64 | test("webc:for using Object.keys to convert to Array", async t => { 65 | let component = new WebC(); 66 | 67 | component.setInputPath("./test/stubs/looping/array-object-keys.webc"); 68 | 69 | let { html } = await component.compile(); 70 | 71 | t.is(html.trim(), `
a
72 |
c
`); 73 | }); 74 | 75 | test("webc:for using Object.values to convert to Array", async t => { 76 | let component = new WebC(); 77 | 78 | component.setInputPath("./test/stubs/looping/array-object-values.webc"); 79 | 80 | let { html } = await component.compile(); 81 | 82 | t.is(html.trim(), `
1
83 |
4
`); 84 | }); 85 | 86 | test("webc:for issue #139", async t => { 87 | let component = new WebC(); 88 | 89 | component.setInputPath("./test/stubs/looping/issue-139.webc"); 90 | component.defineComponents("./test/stubs/looping/components/component.webc"); 91 | 92 | let { html } = await component.compile(); 93 | 94 | t.is(html.trim(), `1 95 | 2 96 | 3 97 | 1 98 | 2 99 | 3`); 100 | }); 101 | 102 | test("script webc:setup feeds data for looping", async t => { 103 | let component = new WebC(); 104 | 105 | component.setInputPath("./test/stubs/looping/script-setup-data.webc"); 106 | 107 | let { html } = await component.compile(); 108 | 109 | t.is(html.trim(), `1 110 | 2 111 | 3`); 112 | }); 113 | 114 | test("nesting webc:for over component hierarchy", async t => { 115 | let component = new WebC(); 116 | let {contacts} = await import("./stubs/looping/complex/data.js"); 117 | 118 | component.defineComponents("./test/stubs/looping/components/*.webc"); 119 | component.setInputPath("./test/stubs/looping/complex/entry-point.webc"); 120 | 121 | let { html } = await component.compile({data:{contacts}}); 122 | 123 | t.true(html.indexOf(``) > -1) 124 | t.true(html.indexOf(`
  • Ross - 1
  • `) > -1) 125 | t.true(html.indexOf(`
    `) > -1) 126 | t.true(html.indexOf(`
    Chandler
    `) > -1) 127 | t.true(html.indexOf(`border: 1px solid green;`) > -1) 128 | }); 129 | 130 | test("script webc:for over a Set", async t => { 131 | let component = new WebC(); 132 | component.setContent('
    '); 133 | 134 | let { html } = await component.compile({ 135 | data: { 136 | source: new Set([1,2,3]), 137 | // source: [1,2,3], 138 | } 139 | }); 140 | 141 | t.is(html.trim(), `
    1
    142 |
    2
    143 |
    3
    `); 144 | }); 145 | 146 | test("script webc:for over a Map", async t => { 147 | let component = new WebC(); 148 | component.setContent('
    '); 149 | 150 | let source = new Map(); 151 | source.set("first", 1); 152 | source.set("second", 2); 153 | source.set("third", 3); 154 | 155 | let { html } = await component.compile({ 156 | data: { 157 | source, 158 | } 159 | }); 160 | 161 | t.is(html.trim(), `
    first,1
    162 |
    second,2
    163 |
    third,3
    `); 164 | }); 165 | 166 | test("Nested script webc:for #175", async t => { 167 | let component = new WebC(); 168 | component.setContent('
    outer:
    '); 169 | 170 | let source = [["1"], ["2a", "2b"], "3"]; 171 | 172 | let { html } = await component.compile({ 173 | data: { 174 | source, 175 | } 176 | }); 177 | 178 | t.is(html.trim(), `
    outer:1string 1
    179 |
    outer:2a,2bstring 2a 180 | string 2b
    181 |
    outer:3string 3
    `); 182 | }); 183 | 184 | test("Test case from #175 nested webc:for", async t => { 185 | let component = new WebC(); 186 | component.setContent(''); 187 | component.defineComponents("./test/stubs/issue-175/c-data-table.webc"); 188 | 189 | let source = { 190 | users: [ 191 | { 192 | id: 1, 193 | name: 'Alex', 194 | email: 'alex@example.com' 195 | }, 196 | { 197 | id: 2, 198 | name: 'Bob', 199 | email: 'bob@example.com' 200 | }, 201 | { 202 | id: 3, 203 | name: 'Steave', 204 | email: 'steave@example.com' 205 | } 206 | ], 207 | 208 | columns: ['id', 'name', 'email'] 209 | }; 210 | 211 | let { html } = await component.compile({ 212 | data: source, 213 | }); 214 | 215 | t.is(html.trim(), ` 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 |
    1Alexalex@example.com
    2Bobbob@example.com
    3Steavesteave@example.com
    `); 234 | }); -------------------------------------------------------------------------------- /src/astQuery.js: -------------------------------------------------------------------------------- 1 | 2 | import { AstSerializer } from "./ast.js"; 3 | 4 | class AstQuery { 5 | // List from the parse5 serializer 6 | // https://github.com/inikulin/parse5/blob/3955dcc158031cc773a18517d2eabe8b17107aa3/packages/parse5/lib/serializer/index.ts 7 | static voidElements = { 8 | area: true, 9 | base: true, 10 | basefont: true, 11 | bgsound: true, 12 | br: true, 13 | col: true, 14 | embed: true, 15 | frame: true, 16 | hr: true, 17 | img: true, 18 | input: true, 19 | keygen: true, 20 | link: true, 21 | meta: true, 22 | param: true, 23 | source: true, 24 | track: true, 25 | wbr: true, 26 | }; 27 | 28 | /* Tag Names */ 29 | static getTagName(node) { 30 | let is = AstQuery.getAttributeValue(node, AstSerializer.attrs.IS); 31 | if(is) { 32 | return is; 33 | } 34 | 35 | return node.tagName; 36 | } 37 | 38 | static isVoidElement(tagName) { 39 | return AstQuery.voidElements[tagName] || false; 40 | } 41 | 42 | /* Specific queries */ 43 | static getSlotTargets(node) { 44 | let targetNodes = AstQuery.findAllElements(node, "slot"); 45 | let map = {}; 46 | for(let target of targetNodes) { 47 | let name = AstQuery.getAttributeValue(target, "name") || "default"; 48 | map[name] = true; 49 | } 50 | return map; 51 | } 52 | 53 | static isLinkStylesheetNode(tagName, node) { 54 | return tagName === "link" && AstQuery.getAttributeValue(node, "rel") === "stylesheet"; 55 | } 56 | 57 | // filter out webc:setup 58 | static isScriptNode(tagName, node) { 59 | return tagName === "script" && !AstQuery.hasAttribute(node, AstSerializer.attrs.SETUP); 60 | } 61 | 62 | static getExternalSource(tagName, node) { 63 | if(AstQuery.isLinkStylesheetNode(tagName, node)) { 64 | return AstQuery.getAttributeValue(node, "href"); 65 | } 66 | 67 | if(AstQuery.isScriptNode(tagName, node)) { 68 | return AstQuery.getAttributeValue(node, "src"); 69 | } 70 | } 71 | 72 | /* Attributes */ 73 | static hasAttribute(node, attributeName) { 74 | return (node.attrs || []).find(({name}) => name === attributeName) !== undefined; 75 | } 76 | 77 | static hasAnyAttribute(node, attributes) { 78 | let lookup = {}; 79 | for(let name of attributes) { 80 | lookup[name] = true; 81 | } 82 | return (node.attrs || []).find(({name}) => lookup[name]) !== undefined; 83 | } 84 | 85 | static getAttributeValue(node, attributeName) { 86 | let nameAttr = (node.attrs || []).find(({name}) => name === attributeName); 87 | 88 | if(!nameAttr) { 89 | // Same as Element.getAttribute 90 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute 91 | return null; 92 | } 93 | 94 | return nameAttr?.value; 95 | } 96 | 97 | static getRootNodeMode(node) { 98 | // override is when child component definitions override the host component tag 99 | let rootAttributeValue = AstQuery.getAttributeValue(node, AstSerializer.attrs.ROOT); 100 | if(rootAttributeValue) { 101 | return rootAttributeValue; 102 | } 103 | // merge is when webc:root attributes flow up to the host component (and the child component tag is ignored) 104 | if(rootAttributeValue === "") { 105 | return "merge"; 106 | } 107 | return false; 108 | } 109 | 110 | static getRootAttributes(component, scopedStyleHash) { 111 | let attrs = []; 112 | 113 | // webc:root Attributes 114 | let tops = AstQuery.getTopLevelNodes(component, [], [AstSerializer.attrs.ROOT]); 115 | for(let root of tops) { 116 | for(let attr of root.attrs) { 117 | if(attr.name !== AstSerializer.attrs.ROOT) { 118 | attrs.push({ name: attr.name, value: attr.value }); 119 | } 120 | } 121 | } 122 | 123 | if(scopedStyleHash) { 124 | // it’s okay if there are other `class` attributes, we merge them later 125 | attrs.push({ name: "class", value: scopedStyleHash }); 126 | } 127 | 128 | return attrs; 129 | } 130 | 131 | /* Declarative Shadow DOM */ 132 | static isDeclarativeShadowDomNode(node) { 133 | let tagName = AstQuery.getTagName(node); 134 | return tagName === "template" && (AstQuery.hasAttribute(node, "shadowroot") || AstQuery.hasAttribute(node, "shadowrootmode")) 135 | } 136 | 137 | static hasDeclarativeShadowDomChild(node) { 138 | return AstQuery.findElement(node, "template", ["shadowroot", "shadowrootmode"]); 139 | } 140 | 141 | /* Content */ 142 | static getTextContent(node) { // used for style hashes 143 | let content = []; 144 | for(let child of node.childNodes || []) { 145 | if(child.nodeName === "#text") { 146 | content.push(child.value); 147 | } 148 | } 149 | return content; 150 | } 151 | 152 | static hasTextContent(node) { 153 | return AstQuery.getTextContent(node).find(entry => entry.trim().length > 0) !== undefined; 154 | } 155 | 156 | 157 | /* Shallow element finds */ 158 | static getImplicitRootNodes(node) { 159 | return [ 160 | AstQuery.findElement(node, "body"), 161 | AstQuery.findElement(node, "head") 162 | ].filter(node => !!node); 163 | } 164 | 165 | static getTopLevelNodes(node, tagNames = [], webcAttrs = []) { 166 | let roots = AstQuery.getImplicitRootNodes(node); 167 | if(roots.length === 0) { 168 | throw new Error("Unable to find component root, expected an implicit or "); 169 | } 170 | 171 | let children = []; 172 | for(let root of roots) { 173 | for(let child of AstQuery.getChildren(root, tagNames, webcAttrs)) { 174 | children.push(child); 175 | } 176 | } 177 | return children; 178 | } 179 | 180 | static getChildren(parentNode, tagNames = [], attrCheck = []) { 181 | if(!parentNode) { 182 | return []; 183 | } 184 | if(typeof tagNames === "string") { 185 | tagNames = [tagNames]; 186 | } 187 | if(!tagNames || Array.isArray(tagNames)) { 188 | tagNames = new Set(tagNames); 189 | } 190 | 191 | let results = []; 192 | for(let child of parentNode.childNodes || []) { 193 | let tagName = AstQuery.getTagName(child); 194 | if(tagNames.size === 0 || tagNames.has(tagName)) { 195 | if(attrCheck.length === 0 || attrCheck.find(attr => AstQuery.hasAttribute(child, attr))) { 196 | results.push(child); 197 | } 198 | } 199 | } 200 | 201 | return results; 202 | } 203 | 204 | static getFirstTopLevelNode(node, tagName, attrName) { 205 | let roots = AstQuery.getImplicitRootNodes(node); 206 | if(roots.length === 0) { 207 | throw new Error("Unable to find component root, expected an implicit or "); 208 | } 209 | 210 | for(let root of roots) { 211 | let match = AstQuery.findFirstChild(root, tagName, attrName); 212 | if(match) { 213 | return match; 214 | } 215 | } 216 | } 217 | 218 | static findFirstChild(parentNode, tagName, attrName) { 219 | for(let child of parentNode?.childNodes || []) { 220 | if(!tagName || tagName === AstQuery.getTagName(child)) { 221 | if(!attrName || AstQuery.hasAttribute(child, attrName)) { 222 | return child; 223 | } 224 | } 225 | } 226 | } 227 | 228 | /* Deep element finds */ 229 | static findAllElements(root, tagName) { 230 | let results = []; 231 | let rootTagName = AstQuery.getTagName(root); 232 | if(rootTagName === tagName) { 233 | results.push(root); 234 | } 235 | for(let child of root.childNodes || []) { 236 | for(let node of AstQuery.findAllElements(child, tagName)) { 237 | results.push(node); 238 | } 239 | } 240 | 241 | return results; 242 | } 243 | 244 | static findElement(root, tagName, attrCheck = []) { 245 | let rootTagName = AstQuery.getTagName(root); 246 | if(rootTagName === tagName) { 247 | if(attrCheck.length === 0 || attrCheck.find(attr => AstQuery.hasAttribute(root, attr))) { 248 | return root; 249 | } 250 | } 251 | for(let child of root.childNodes || []) { 252 | let node = AstQuery.findElement(child, tagName, attrCheck); 253 | if(node) { 254 | return node; 255 | } 256 | } 257 | } 258 | } 259 | 260 | export { AstQuery }; -------------------------------------------------------------------------------- /test/contentPropsTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import { WebC } from "../webc.js"; 3 | 4 | 5 | test("Using @html", async t => { 6 | let component = new WebC(); 7 | 8 | component.setInputPath("./test/stubs/html.webc"); 9 | 10 | let { html, css, js, components } = await component.compile({ 11 | data: { 12 | variable1: "value1" 13 | } 14 | }); 15 | 16 | t.deepEqual(js, []); 17 | t.deepEqual(css, []); 18 | t.deepEqual(components, [ 19 | "./test/stubs/html.webc", 20 | ]); 21 | 22 | t.is(html, `

    Paragraph HTML

    23 |

    value1

    24 | 25 | 26 | Template HTML Nokeep 27 | `); 28 | }); 29 | 30 | test("Using @text", async t => { 31 | let component = new WebC(); 32 | 33 | component.setContent(` 34 | 35 | 36 | `); 37 | 38 | let { html, css, js, components } = await component.compile({ 39 | data: { 40 | myText: "

    This is text

    " 41 | } 42 | }); 43 | 44 | t.deepEqual(js, []); 45 | t.deepEqual(css, []); 46 | t.deepEqual(components, []); 47 | 48 | t.is(html, ` 49 | 50 | <p>This is text</p> 51 | `); 52 | }); 53 | 54 | test("Using a helper in dynamic attribute and @html", async(t) => { 55 | let component = new WebC(); 56 | component.setHelper("helper", function(a) { 57 | return a+"Blue"; 58 | }); 59 | component.setContent(``); 60 | 61 | let { html } = await component.compile(); 62 | 63 | t.is(html, ``); 64 | }); 65 | 66 | test("Using a helper (without this) in dynamic attribute and @html", async(t) => { 67 | let component = new WebC(); 68 | component.setHelper("helper", (a) => { return a+"Blue"; }); 69 | component.setContent(``); 70 | 71 | let { html } = await component.compile(); 72 | 73 | t.is(html, ``); 74 | }); 75 | 76 | 77 | test("Use @html with undefined properties or helpers", async (t) => { 78 | let component = new WebC(); 79 | component.setInputPath("./test/stubs/props-missing.webc"); 80 | 81 | let { html, css, js, components } = await component.compile(); 82 | 83 | t.is(html, ` 84 | `); 85 | }); 86 | 87 | test("Use @html with undefined properties or helpers (without this)", async (t) => { 88 | let component = new WebC(); 89 | component.setInputPath("./test/stubs/props-missing-nothis.webc"); 90 | 91 | let { html, css, js, components } = await component.compile(); 92 | 93 | t.is(html, ` 94 | 95 | `); 96 | }); 97 | 98 | 99 | test("Setting @html and/or :attr to a number (non-string)", async t => { 100 | let component = new WebC(); 101 | component.setInputPath("./test/stubs/html-number.webc"); 102 | 103 | let { html, css, js, components } = await component.compile(); 104 | 105 | t.deepEqual(js, []); 106 | t.deepEqual(css, []); 107 | t.deepEqual(components, [ 108 | "./test/stubs/html-number.webc", 109 | ]); 110 | 111 | t.is(html, `2`); 112 | }); 113 | 114 | 115 | test("Using a component with @html should still aggregate assets (issue #29)", async (t) => { 116 | let component = new WebC(); 117 | component.setBundlerMode(true); 118 | 119 | component.setInputPath("./test/stubs/component-html-assets.webc"); 120 | component.defineComponents({ 121 | "my-component": "./test/stubs/components/assets.webc", 122 | }); 123 | 124 | let { html, css, js } = await component.compile(); 125 | t.is(html, `HTML 126 |

    Test

    127 | 128 |
    `); 129 | t.deepEqual(css, [`/* CSS */`]); 130 | t.deepEqual(js, [`/* JS */`]); 131 | }); 132 | 133 | test("Returning component markup from @html should resolve components (via issue #29)", async (t) => { 134 | let component = new WebC(); 135 | component.setBundlerMode(true); 136 | 137 | component.setInputPath("./test/stubs/component-html-resolve.webc"); 138 | component.defineComponents({ 139 | "child": "./test/stubs/components/nested-child.webc", 140 | }); 141 | 142 | let { html, css, js } = await component.compile({ 143 | data: { 144 | sampleChildMarkup: "" 145 | } 146 | }); 147 | t.is(html, `

    SSR content

    `); 148 | t.deepEqual(css, []); 149 | t.deepEqual(js, []); 150 | }); 151 | 152 | test("script @html should bundle (issue #16, maybe via issue #29)", async (t) => { 153 | let component = new WebC(); 154 | component.setBundlerMode(true); 155 | component.setInputPath("./test/stubs/component-script-html.webc"); 156 | component.defineComponents({ 157 | "my-component": "./test/stubs/components/script-html.webc", 158 | }); 159 | 160 | let { html, css, js } = await component.compile({ 161 | data: { 162 | sampleScript: "alert('hi');" 163 | } 164 | }); 165 | t.is(html, ``); 166 | t.deepEqual(css, []); 167 | t.deepEqual(js, [`alert('hi');`]); 168 | }); 169 | 170 | test("raw template with nokeep and @html", async t => { 171 | let component = new WebC(); 172 | 173 | component.setInputPath("./test/stubs/webc-raw-html-prop.webc"); 174 | component.defineComponents("./test/stubs/components/nested-child.webc"); 175 | 176 | let { html, css, js, components } = await component.compile({ 177 | data: { 178 | content: "" 179 | } 180 | }); 181 | 182 | t.deepEqual(js, []); 183 | t.deepEqual(css, []); 184 | t.deepEqual(components, [ 185 | "./test/stubs/webc-raw-html-prop.webc", 186 | ]); 187 | 188 | t.is(html, ` 189 | 190 | 191 | `); 192 | }); 193 | 194 | test("No reprocessing when using @raw, #70 (related to issue #33)", async (t) => { 195 | let component = new WebC(); 196 | 197 | component.setInputPath("./test/stubs/webc-raw-prop.webc"); 198 | component.defineComponents("./test/stubs/components/nested-child.webc"); 199 | 200 | let { html, css, js, components } = await component.compile({ 201 | data: { 202 | content: `` 203 | } 204 | }); 205 | 206 | t.is(html, ` 207 | 208 | 209 | `); 210 | 211 | t.deepEqual(js, []); 212 | t.deepEqual(css, []); 213 | t.deepEqual(components, [ 214 | "./test/stubs/webc-raw-prop.webc", 215 | ]); 216 | }); 217 | 218 | test("@html and @text", async (t) => { 219 | let component = new WebC(); 220 | component.setContent(``); 221 | 222 | await t.throwsAsync(component.compile(), { 223 | message: `Node template cannot have more than one of: @html="'Test'", @text="'Test'", or @raw. Pick one!` 224 | }); 225 | }); 226 | 227 | test("@raw and @text", async (t) => { 228 | let component = new WebC(); 229 | component.setContent(``); 230 | 231 | await t.throwsAsync(component.compile(), { 232 | message: `Node template cannot have more than one of: @html, @text="'Test'", or @raw="'Test'". Pick one!` 233 | }); 234 | }); 235 | 236 | test("@html and @raw", async (t) => { 237 | let component = new WebC(); 238 | component.setContent(``); 239 | 240 | await t.throwsAsync(component.compile(), { 241 | message: `Node template cannot have more than one of: @html="'Test'", @text, or @raw="'Test'". Pick one!` 242 | }); 243 | }); 244 | 245 | test("@html and @text and @raw", async (t) => { 246 | let component = new WebC(); 247 | component.setContent(``); 248 | 249 | await t.throwsAsync(component.compile(), { 250 | message: `Node template cannot have more than one of: @html="'Test'", @text="'Test'", or @raw="'Test'". Pick one!` 251 | }); 252 | }); 253 | 254 | -------------------------------------------------------------------------------- /webc.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import fastglob from "fast-glob"; 3 | import isGlob from "is-glob"; 4 | import path from "node:path"; 5 | 6 | import { Path } from "./src/path.js"; 7 | import { AstSerializer } from "./src/ast.js"; 8 | import { AstCache } from "./src/astCache.js"; 9 | import { ModuleResolution } from "./src/moduleResolution.js"; 10 | 11 | export { ComponentManager } from "./src/componentManager.js"; 12 | 13 | const localAstCache = new AstCache(); 14 | 15 | export class WebC { 16 | constructor(options = {}) { 17 | let { file, input } = options; 18 | 19 | this.customTransforms = {}; 20 | this.customHelpers = {}; 21 | this.customScopedHelpers = {}; 22 | this.globalComponents = {}; 23 | this.astOptions = {}; 24 | this.bundlerMode = false; 25 | this.ignores = options.ignores || []; 26 | 27 | if(input || input === "") { 28 | this.rawInput = input; 29 | } 30 | if(file) { 31 | this.setInputPath(file); 32 | } 33 | } 34 | 35 | setInputPath(file) { 36 | file = Path.normalizePath(file); 37 | this.filePath = file; 38 | this.astOptions.filePath = file; 39 | } 40 | 41 | setContent(input, filePath) { 42 | this.rawInput = input; 43 | 44 | if(filePath) { 45 | this.astOptions.filePath = filePath; 46 | } 47 | } 48 | 49 | setGlobalComponentManager(manager) { 50 | this.globalComponentManager = manager; 51 | } 52 | 53 | getRenderingMode(content) { 54 | if(!content.startsWith(") 82 | if(mode === "component" || !content.startsWith("${content}`; 84 | } 85 | 86 | return { 87 | content, 88 | mode, 89 | }; 90 | } 91 | 92 | static async getASTFromString(string) { 93 | let wc = new WebC({ 94 | input: string 95 | }); 96 | let { content } = wc.getContent(); 97 | return wc.getAST(content); 98 | } 99 | 100 | // @deprecated for getFromFilePath 101 | static async getASTFromFilePath(filePath) { 102 | let wc = new WebC({ 103 | file: filePath 104 | }); 105 | let { content } = wc.getContent(); 106 | return wc.getAST(content); 107 | } 108 | 109 | static async getFromFilePath(filePath) { 110 | let wc = new WebC({ 111 | file: filePath 112 | }); 113 | let { content, mode } = wc.getContent(); 114 | 115 | return { 116 | content, 117 | ast: await wc.getAST(content), 118 | mode, 119 | }; 120 | } 121 | 122 | getAST(content) { 123 | if(!content) { 124 | throw new Error("WebC.getAST() expects a content argument."); 125 | } 126 | 127 | return localAstCache.get(content); 128 | } 129 | 130 | setTransform(key, callback) { 131 | this.customTransforms[key] = callback; 132 | } 133 | 134 | setHelper(key, callback, isScoped = false) { 135 | if(isScoped) { 136 | this.customScopedHelpers[key] = callback; 137 | } else { 138 | this.customHelpers[key] = callback; 139 | } 140 | } 141 | 142 | setAlias(key, folder) { 143 | if(!this.aliases) { 144 | this.aliases = {}; 145 | } 146 | 147 | this.aliases[key] = folder; 148 | } 149 | 150 | async _defineComponentsObject(obj = {}) { 151 | for(let name in obj) { 152 | let file = obj[name]; 153 | if(this.globalComponents[name]) { 154 | throw new Error(`Global component name collision on "${name}" between: ${this.globalComponents[name]} and ${file}`) 155 | } 156 | this.globalComponents[name] = file; 157 | } 158 | } 159 | 160 | static findGlob(glob, ignores = []) { 161 | return fastglob.sync(glob, { 162 | ignore: ignores, 163 | caseSensitiveMatch: false, 164 | dot: false, 165 | }); 166 | } 167 | 168 | static getComponentsMap(globOrObject, ignores) { 169 | let rawFiles = globOrObject; 170 | 171 | // Passing in a single string is assumed to be a glob 172 | if(typeof rawFiles === "string") { 173 | rawFiles = WebC.findGlob(globOrObject, ignores); 174 | } 175 | 176 | if(Array.isArray(rawFiles)) { 177 | let moduleResolver = new ModuleResolution(); 178 | let resolvedFiles = new Set(); 179 | for(let file of rawFiles) { 180 | // Resolve `npm:` aliases 181 | let hasValidAlias = moduleResolver.hasValidAlias(file); 182 | if(hasValidAlias) { 183 | file = moduleResolver.resolveAliases(file); 184 | } 185 | 186 | // Multiple glob searches 187 | if(isGlob(file)) { 188 | let globResults = WebC.findGlob(file, ignores); 189 | for(let globFile of globResults) { 190 | resolvedFiles.add(globFile); 191 | } 192 | } else { 193 | resolvedFiles.add(file); 194 | } 195 | } 196 | 197 | let obj = {}; 198 | for(let file of resolvedFiles) { 199 | let {name} = path.parse(file); 200 | if(obj[name]) { 201 | throw new Error(`Global component name collision on "${name}" between: ${obj[name]} and ${file}`) 202 | } 203 | obj[name] = file; 204 | } 205 | 206 | return obj; 207 | } 208 | 209 | return globOrObject; 210 | } 211 | 212 | defineComponents(globOrObject) { 213 | this._defineComponentsObject(WebC.getComponentsMap(globOrObject, this.ignores)); 214 | } 215 | 216 | setUidFunction(fn) { 217 | this.uidFn = fn; 218 | } 219 | 220 | async setup(options = {}) { 221 | let { content, mode } = this.getContent(); 222 | let rawAst = this.getAST(content); 223 | 224 | let ast = new AstSerializer(this.astOptions); 225 | ast.setComponentManager(this.globalComponentManager); 226 | ast.setBundlerMode(this.bundlerMode); 227 | ast.setMode(mode); 228 | ast.setContent(content); 229 | ast.setData(options.data); 230 | 231 | if(this.aliases && Object.keys(this.aliases).length) { 232 | ast.setAliases(this.aliases); 233 | } 234 | 235 | if(this.uidFn) { 236 | ast.setUidFunction(this.uidFn); 237 | } 238 | 239 | for(let name in this.customTransforms) { 240 | ast.setTransform(name, this.customTransforms[name]); 241 | } 242 | 243 | for(let name in this.customHelpers) { 244 | ast.setHelper(name, this.customHelpers[name], false); 245 | } 246 | for(let name in this.customScopedHelpers) { 247 | ast.setHelper(name, this.customScopedHelpers[name], true); 248 | } 249 | 250 | await ast.setComponentsByFilePath(this.globalComponents); 251 | await ast.setComponentsByFilePath(options.components); 252 | 253 | return { 254 | ast: rawAst, 255 | serializer: ast, 256 | }; 257 | } 258 | 259 | getComponents(setup) { 260 | let { ast, serializer } = setup; 261 | let obj = serializer.getComponentList(ast); 262 | return Object.keys(obj); 263 | } 264 | 265 | setBundlerMode(mode) { 266 | this.bundlerMode = !!mode; 267 | } 268 | 269 | async stream(options = {}) { 270 | let { ast, serializer } = await this.setup(options); 271 | 272 | serializer.streams.start(); 273 | 274 | serializer.compile(ast, options.slots).catch(() => { 275 | // Node requires this to avoid unhandled rejection errors (yes, even with `finally`) 276 | serializer.streams.end(); 277 | }).finally(() => { 278 | serializer.streams.end(); 279 | }); 280 | 281 | return serializer.streams.get(); 282 | } 283 | 284 | async compile(options = {}) { 285 | let { ast, serializer } = await this.setup(options); 286 | 287 | return serializer.compile(ast, options.slots); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/componentManager.js: -------------------------------------------------------------------------------- 1 | import { createHash } from "node:crypto"; 2 | import os from "node:os"; 3 | 4 | import { WebC } from "../webc.js"; 5 | import { AstQuery } from "./astQuery.js"; 6 | import { AstModify } from "./astModify.js"; 7 | import { AstSerializer } from "./ast.js"; 8 | import { wrapAndExecute } from "./dynamicScript.js"; 9 | 10 | class ComponentManager { 11 | constructor() { 12 | this.parsingPromises = {}; 13 | this.components = {}; 14 | this.hashOverrides = {}; 15 | } 16 | 17 | async getSetupScriptValue(component, filePath, dataCascade) { 18 | //