Skip to content

Commit 471422d

Browse files
committed
BREAKING webc:type="js" attrs, webc:root="override"
1. [Breaking] webc:type="js" has implied webc:is="template" (unless otherwise specified) and webc:nokeep (unless webc:keep specified) (fixes #91) 2. [Breaking] Instead of `webc:root webc:keep`, you must use `webc:root="override"` instead 3. Ease up on circular component references when using webc:root 4. Fix for attribute merging `class` and `style` when using webc:root and those attributes do not exist in host component
1 parent 77f3846 commit 471422d

19 files changed

+133
-59
lines changed

src/ast.js

+62-29
Original file line numberDiff line numberDiff line change
@@ -359,46 +359,42 @@ class AstSerializer {
359359
// Has <* webc:root> (has to be a root child, not script/style)
360360
let tops = this.getTopLevelNodes(component);
361361
for(let child of tops) {
362-
let tagName = this.getTagName(child);
363-
if(tagName === "script" || tagName === "style") {
364-
continue;
365-
}
366-
367-
if(this.hasAttribute(child, AstSerializer.attrs.ROOT)) {
368-
// ignore if webc:root and webc:keep
369-
if(this.hasAttribute(child, AstSerializer.attrs.KEEP)) {
362+
let rootNodeMode = this.getRootNodeMode(child);
363+
if(rootNodeMode) {
364+
// do not use parent tag if webc:root="override"
365+
if(rootNodeMode === "override") {
370366
return true;
371367
}
372368

373-
// do not ignore if webc:root (but not webc:keep)
369+
// use parent tag if webc:root (and not webc:root="override")
374370
return false;
375371
}
376372
}
377373

378-
// do not ignore if <style> or <script> in component definition (unless <style webc:root> or <script webc:root>)
374+
// use parent tag if <style> or <script> in component definition (unless <style webc:root> or <script webc:root>)
379375
for(let child of tops) {
380376
let tagName = this.getTagName(child);
381-
if(tagName !== "script" && tagName !== "style" || this.hasAttribute(child, AstSerializer.attrs.ROOT)) {
377+
if(tagName !== "script" && tagName !== "style") {
382378
continue;
383379
}
384380

385381
if(this.hasTextContent(child)) {
386-
return false;
382+
return false; // use parent tag if script/style has non-empty values
387383
}
388384

389385
// <script src=""> or <link rel="stylesheet" href="">
390386
if(this.getExternalSource(tagName, child)) {
391-
return false;
387+
return false; // use parent tag if script/link have external file refs
392388
}
393389
}
394390

395-
396-
// Has <template shadowroot> (can be anywhere in the component body)
391+
// Use parent tag if has <template shadowroot> (can be anywhere in the component body)
397392
let shadowroot = this.findElement(component, "template", ["shadowroot"]);
398393
if(shadowroot) {
399394
return false;
400395
}
401396

397+
// Do not use parent tag
402398
return true;
403399
}
404400

@@ -427,16 +423,16 @@ class AstSerializer {
427423
isTagIgnored(node, component, renderingMode, options) {
428424
let tagName = this.getTagName(node);
429425

430-
if(this.shouldKeepNode(node)) {
426+
if(this.shouldKeepNode(node)) { // webc:keep
431427
return false; // do not ignore
432428
}
433429

434-
if(this.hasAttribute(node, AstSerializer.attrs.ROOT)) {
430+
// must come after webc:keep (webc:keep takes precedence)
431+
if(this.hasAttribute(node, AstSerializer.attrs.NOKEEP)) {
435432
return true;
436433
}
437434

438-
// must come after webc:keep (webc:keep takes precedence)
439-
if(this.hasAttribute(node, AstSerializer.attrs.NOKEEP)) {
435+
if(this.getRootNodeMode(node) === "merge") { // has webc:root but is not webc:root="override"
440436
return true;
441437
}
442438

@@ -659,6 +655,19 @@ class AstSerializer {
659655
return str;
660656
}
661657

658+
getRootNodeMode(node) {
659+
// override is when child component definitions override the host component tag
660+
let rootAttributeValue = this.getAttributeValue(node, AstSerializer.attrs.ROOT);
661+
if(rootAttributeValue) {
662+
return rootAttributeValue;
663+
}
664+
// merge is when webc:root attributes flow up to the host component (and the child component tag is ignored)
665+
if(rootAttributeValue === "") {
666+
return "merge";
667+
}
668+
return false;
669+
}
670+
662671
async renderStartTag(node, tagName, component, renderingMode, options) {
663672
let content = "";
664673

@@ -674,8 +683,8 @@ class AstSerializer {
674683
let attrs = this.getAttributes(node, component, options);
675684
let parentComponent = this.components[options.closestParentComponent];
676685

677-
// webc:keep webc:root should use the style hash class name and host attributes since they won’t be added to the host component
678-
if(parentComponent && parentComponent.ignoreRootTag && this.hasAttribute(node, AstSerializer.attrs.ROOT) && this.hasAttribute(node, AstSerializer.attrs.KEEP)) {
686+
// webc:root="override" should use the style hash class name and host attributes since they won’t be added to the host component
687+
if(parentComponent && parentComponent.ignoreRootTag && this.getRootNodeMode(node) === "override") {
679688
if(parentComponent.scopedStyleHash) {
680689
attrs.push({ name: "class", value: parentComponent.scopedStyleHash });
681690
}
@@ -850,7 +859,7 @@ class AstSerializer {
850859

851860
// Processes `<template webc:root>` as WebC (including slot resolution)
852861
// Processes `<template>` in raw mode (for plain template, shadowroots, webc:keep, etc).
853-
if(!this.hasAttribute(node, AstSerializer.attrs.ROOT)) {
862+
if(!this.hasAttribute(node, AstSerializer.attrs.ROOT)) { // TODO is this too much magic?
854863
templateOptions.rawMode = true;
855864
}
856865

@@ -923,7 +932,7 @@ class AstSerializer {
923932
return Array.from(types);
924933
}
925934

926-
addComponentDependency(component, tagName, options) {
935+
addComponentDependency(component, node, tagName, options) {
927936
let componentFilePath = Path.normalizePath(component.filePath);
928937
if(!options.components.hasNode(componentFilePath)) {
929938
options.components.addNode(componentFilePath);
@@ -1135,12 +1144,29 @@ class AstSerializer {
11351144
return rawContent;
11361145
}
11371146

1147+
addImpliedWebCAttributes(node) {
1148+
if(this.getAttributeValue(node, AstSerializer.attrs.TYPE) === AstSerializer.transformTypes.JS) {
1149+
// this check is perhaps unnecessary since KEEP has a higher precedence than NOKEEP
1150+
if(!this.hasAttribute(node, AstSerializer.attrs.KEEP)) {
1151+
node.attrs.push({ name: AstSerializer.attrs.NOKEEP, value: "" });
1152+
}
1153+
1154+
if(!this.hasAttribute(node, AstSerializer.attrs.IS)) {
1155+
node.attrs.push({ name: AstSerializer.attrs.IS, value: "template" });
1156+
}
1157+
}
1158+
}
1159+
11381160
async compileNode(node, slots = {}, options = {}, streamEnabled = true) {
11391161
options = Object.assign({}, options);
11401162

1163+
this.addImpliedWebCAttributes(node);
1164+
11411165
let tagName = this.getTagName(node);
11421166
let content = "";
11431167

1168+
// webc:type="js" has an implied webc:is="template" webc:nokeep
1169+
11441170
let transformTypes = this.getTransformTypes(node);
11451171
if(transformTypes.length) {
11461172
options.currentTransformTypes = transformTypes;
@@ -1185,11 +1211,16 @@ class AstSerializer {
11851211
}
11861212

11871213
let component;
1188-
let importSource = this.getAttributeValue(node, AstSerializer.attrs.IMPORT);
1189-
if(importSource) {
1190-
component = await this.importComponent(importSource, options.closestParentComponent, tagName);
1191-
} else {
1192-
component = this.getComponent(tagName);
1214+
1215+
// This allows use of <img webc:root> inside of an <img> component definition without circular reference errors
1216+
let rootNodeMode = this.getRootNodeMode(node);
1217+
if(!rootNodeMode) {
1218+
let importSource = this.getAttributeValue(node, AstSerializer.attrs.IMPORT);
1219+
if(importSource) {
1220+
component = await this.importComponent(importSource, options.closestParentComponent, tagName);
1221+
} else {
1222+
component = this.getComponent(tagName);
1223+
}
11931224
}
11941225

11951226
if(component) {
@@ -1259,10 +1290,12 @@ class AstSerializer {
12591290

12601291
// Component content (foreshadow dom)
12611292
if(!options.rawMode && component) {
1262-
this.addComponentDependency(component, tagName, options);
1293+
this.addComponentDependency(component, node, tagName, options);
12631294

1295+
// for attribute sharing
12641296
options.hostComponentNode = node;
12651297

1298+
12661299
let evaluatedAttributes = await AttributeSerializer.evaluateAttributesArray(node.attrs, nodeData);
12671300
options.hostComponentData = AttributeSerializer.mergeAttributes(evaluatedAttributes);
12681301

src/attributeSerializer.js

+13-8
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,7 @@ class AttributeSerializer {
4444
for(let {name, value, privacy} of attrs) {
4545
// Used merged values
4646
if(merged[name]) {
47-
let set = merged[name].value;
48-
if(merged[name].deduplicate) {
49-
set = Array.from(new Set(set));
50-
}
51-
let mergedValue = set.join(merged[name].joinDelimiter);
52-
if(mergedValue) {
53-
attrObject[name] = mergedValue;
54-
}
47+
continue;
5548
} else {
5649
attrObject[name] = value;
5750

@@ -61,6 +54,18 @@ class AttributeSerializer {
6154
}
6255
}
6356

57+
for(let name in merged) {
58+
let set = merged[name].value;
59+
if(merged[name].deduplicate) {
60+
set = Array.from(new Set(set));
61+
}
62+
63+
let mergedValue = set.join(merged[name].joinDelimiter);
64+
if(mergedValue) {
65+
attrObject[name] = mergedValue;
66+
}
67+
}
68+
6469
return attrObject;
6570
}
6671

test/issue-78-test.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import test from "ava";
22
import { WebC } from "../webc.js";
33

4-
test("webc:import in Components should be relative to component file #79", async t => {
4+
test("webc:import in Components should be relative to component file #78", async t => {
55
let component = new WebC();
66

77
component.setInputPath("./test/stubs/issue-78/page.webc");
@@ -19,7 +19,7 @@ woah i am component. i do things.
1919
woah i am component. i do things.`);
2020
});
2121

22-
test("stock webc:type=js #79", async t => {
22+
test("stock webc:type=js #78", async t => {
2323
let component = new WebC();
2424

2525
component.setContent(`<script webc:type="js">
@@ -31,7 +31,7 @@ test("stock webc:type=js #79", async t => {
3131
t.is(html.trim(), `hello`);
3232
});
3333

34-
test("stock webc:type=js with template #79", async t => {
34+
test("stock webc:type=js with template #78", async t => {
3535
let component = new WebC();
3636

3737
component.setContent(`<script webc:type="js" webc:is="template">
@@ -43,7 +43,7 @@ test("stock webc:type=js with template #79", async t => {
4343
t.is(html.trim(), `hello`);
4444
});
4545

46-
test("stock webc:type=js with template/keep #79", async t => {
46+
test("stock webc:type=js with template/keep #78", async t => {
4747
let component = new WebC();
4848

4949
component.setContent(`<script webc:type="js" webc:is="template" webc:keep>
@@ -55,7 +55,7 @@ test("stock webc:type=js with template/keep #79", async t => {
5555
t.is(html.trim(), `<template>hello</template>`);
5656
});
5757

58-
test("stock webc:type=js with webc:is #79", async t => {
58+
test("stock webc:type=js with webc:is #78", async t => {
5959
let component = new WebC();
6060

6161
component.setContent(`<script webc:type="js" webc:is="ul" webc:keep>
@@ -67,7 +67,7 @@ test("stock webc:type=js with webc:is #79", async t => {
6767
t.is(html.trim(), `<ul><li>test</li></ul>`);
6868
});
6969

70-
test("Docs image example #79", async t => {
70+
test("Docs image example #78", async t => {
7171
let component = new WebC();
7272

7373
component.setContent(`<img src="my-image.jpeg" alt="An excited Zach is trying to finish this documentation">`);
@@ -78,7 +78,7 @@ test("Docs image example #79", async t => {
7878
t.is(html.trim(), `<img src="my-image.jpeg" alt="An excited Zach is trying to finish this documentation" extra-attribute>`);
7979
});
8080

81-
test("Docs css example #79", async t => {
81+
test("Docs css example #78", async t => {
8282
let component = new WebC();
8383

8484
component.setContent(`<add-banner-to-css @license="MIT licensed">
@@ -94,7 +94,7 @@ p { color: rebeccapurple; }
9494
</style>`);
9595
});
9696

97-
test("Docs css example using wrapper element #79", async t => {
97+
test("Docs css example using wrapper element #78", async t => {
9898
let component = new WebC();
9999

100100
component.setContent(`<style webc:is="add-banner-to-css-root" @license="MIT licensed">
@@ -112,7 +112,7 @@ p { color: rebeccapurple; }
112112
</style>`);
113113
});
114114

115-
test("Docs css example using render #79", async t => {
115+
test("Docs css example using render #78", async t => {
116116
let component = new WebC();
117117

118118
component.setContent(`<style webc:is="add-banner-to-css-render" @license="MIT licensed">

test/issue-91-test.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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, `<img src="my-image.jpeg" alt="An excited Zach is trying to finish this documentation" this-should-be-included extra-attribute class="one two">`);
14+
});

test/parserTest.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,7 @@ test("Using a web component to override the parent component tag with scoped CSS
929929
"./test/stubs/components/override-parent-scoped.webc",
930930
]);
931931
t.is(html, `Before
932-
<button type="submit" data-attr="1" class="wb_lq-fmb" attr="1" other="2">SSR content</button>
932+
<button type="submit" data-attr="1" attr="1" other="2" class="wb_lq-fmb">SSR content</button>
933933
934934
After`);
935935
});
@@ -1660,6 +1660,19 @@ test("Using scripted render function to generate CSS (webc:root)", async t => {
16601660
"./test/stubs/components/render-css-root.webc",
16611661
]);
16621662

1663+
t.is(html, `<some-css class="wzlbemqff"></some-css>`);
1664+
});
1665+
1666+
test("Using scripted render function to generate CSS (webc:root=override)", async t => {
1667+
let { html, css, js, components } = await testGetResultFor("./test/stubs/using-css-root-override.webc");
1668+
1669+
t.deepEqual(js, []);
1670+
t.deepEqual(css, [`.waltwzk-v .selector{}`]);
1671+
t.deepEqual(components, [
1672+
"./test/stubs/using-css-root-override.webc",
1673+
"./test/stubs/components/render-css-root-override.webc",
1674+
]);
1675+
16631676
t.is(html, ``);
16641677
});
16651678

@@ -1712,7 +1725,7 @@ test("Using img as root mapped to img", async t => {
17121725
"./test/stubs/components/img-as-root.webc",
17131726
]);
17141727

1715-
t.is(html, `<img src="my-src.png" class="class1" child-attr>`);
1728+
t.is(html, `<img src="my-src.png" child-attr class="class1">`);
17161729
});
17171730

17181731
test("Using props", async t => {

test/renderFunctionJsTest.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ test("Using webc:type=js and @html", async t => {
7777

7878
t.is(html, `<!doctype html>
7979
<html>
80-
<head></head><body><script>1/2/3/4</script>
80+
<head></head><body>1/2/3/4
8181
</body>
8282
</html>`);
8383
});

test/streamTest.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ test("Using scripted render function to generate CSS (webc:root)", async t => {
10201020
t.deepEqual(js, []);
10211021
t.deepEqual(css, [`.wzlbemqff .selector{}`]);
10221022

1023-
t.is(html, ``);
1023+
t.is(html, `<some-css class="wzlbemqff"></some-css>`);
10241024
});
10251025

10261026
test("Using scripted render function to generate CSS", async t => {
@@ -1056,7 +1056,7 @@ test("Using img as root mapped to img", async t => {
10561056
t.deepEqual(js, []);
10571057
t.deepEqual(css, []);
10581058

1059-
t.is(html, `<img src="my-src.png" class="class1" child-attr>`);
1059+
t.is(html, `<img src="my-src.png" child-attr class="class1">`);
10601060
});
10611061

10621062
test("Using props", async t => {
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<button webc:root webc:keep type="submit" data-attr="1">SSR content</button>
1+
<button webc:root="override" type="submit" data-attr="1">SSR content</button>
22
<style webc:scoped>:host { font-weight: bold; }</style>
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<button webc:root webc:keep type="submit" data-attr="1">SSR content</button>
1+
<button webc:root="override" type="submit" data-attr="1">SSR content</button>
22
<style>/* Hi */</style>

0 commit comments

Comments
 (0)