diff --git a/change/@microsoft-fast-element-e0afb613-ce1a-4f4f-95a8-5518772af979.json b/change/@microsoft-fast-element-e0afb613-ce1a-4f4f-95a8-5518772af979.json new file mode 100644 index 00000000000..b3577836763 --- /dev/null +++ b/change/@microsoft-fast-element-e0afb613-ce1a-4f4f-95a8-5518772af979.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix: update compiler to ensure first and last child references are defined", + "packageName": "@microsoft/fast-element", + "email": "chhol@microsoft.com", + "dependentChangeType": "prerelease" +} diff --git a/packages/web-components/fast-element/src/templating/compiler.spec.ts b/packages/web-components/fast-element/src/templating/compiler.spec.ts index 8cd7f261202..c563b1c106e 100644 --- a/packages/web-components/fast-element/src/templating/compiler.spec.ts +++ b/packages/web-components/fast-element/src/templating/compiler.spec.ts @@ -72,6 +72,13 @@ describe("The template compiler", () => { fragment: ``, childCount: 0, }, + { + type: "an empty template", + html: ``, + directives: () => [], + fragment: ``, + childCount: 0, + }, { type: "a single", html: `${inline(0)}`, @@ -181,6 +188,13 @@ describe("The template compiler", () => { ]; scenarios.forEach(x => { + it(`ensures that first and last child references are not null for ${x.type}`, () => { + const { fragment } = compile(x.html, x.directives()); + + expect(fragment.firstChild).not.to.be.null; + expect(fragment.lastChild).not.to.be.null; + }) + policies.forEach(y => { it(`handles ${x.type} binding expression(s) with ${y.name} policy`, () => { const directives = x.directives(); diff --git a/packages/web-components/fast-element/src/templating/compiler.ts b/packages/web-components/fast-element/src/templating/compiler.ts index 7fef0634c88..93512a1b9ea 100644 --- a/packages/web-components/fast-element/src/templating/compiler.ts +++ b/packages/web-components/fast-element/src/templating/compiler.ts @@ -358,8 +358,13 @@ export const Compiler = { template = html; } + if (!template.content.firstChild && !template.content.lastChild) { + template.content.appendChild(document.createComment("")); + } + // https://bugs.chromium.org/p/chromium/issues/detail?id=1111864 const fragment = document.adoptNode(template.content); + const context = new CompilationContext( fragment, factories, diff --git a/packages/web-components/fast-element/src/templating/view.spec.ts b/packages/web-components/fast-element/src/templating/view.spec.ts index 456b6b0d792..ac1eab4a9b2 100644 --- a/packages/web-components/fast-element/src/templating/view.spec.ts +++ b/packages/web-components/fast-element/src/templating/view.spec.ts @@ -25,6 +25,26 @@ function startCapturingWarnings() { describe(`The HTMLView`, () => { context("when binding hosts", () => { + it("gracefully handles empty template elements", () => { + const template = html` + + `; + + const view = template.create(); + view.bind({}); + + expect(view.firstChild).not.to.be.null; + expect(view.lastChild).not.to.be.null; + }); + it("gracefully handles empty template literals", () => { + const template = html``; + + const view = template.create(); + view.bind({}); + + expect(view.firstChild).not.to.be.null; + expect(view.lastChild).not.to.be.null; + }); it("warns on class bindings when host not present", () => { const template = html`