Skip to content

Commit 44b646e

Browse files
committed
Fix template element parsing and innerHTML handling
- Fix template children being parsed into element.childNodes instead of template.content during parseHTML() - Add special handling in parse-from-string.js to redirect template children to content DocumentFragment - Implement proper template.innerHTML getter/setter to access content instead of direct children - Add template.toString() method following TextElement pattern to include content in serialization - Ensure template element follows HTML spec where innerHTML always references template.content - Add null safety checks in parsing logic to prevent errors during template processing The template element now correctly: - Parses children into content during HTML parsing - Returns content innerHTML when accessing template.innerHTML - Maintains separation between direct children and content - Serializes with content included in toString() --browser result-- const f = document.createDocumentFragment(); const d = document.createElement('div') d.innerHTML='<a>aaa</a>'; f.append(d); const t = document.createElement('template'); t.content.append(f); console.log(t.innerHTML); // '<div><a>aaa</a></div>' console.log(t.innerText); //'' --linkedom-- console.log(t.innerHTML); // '' console.log(t.innerText); //'' Fix template element parsing and innerHTML handling - Fix template children being parsed into element.childNodes instead of template.content during parseHTML() - Add special handling in parse-from-string.js to redirect template children to content DocumentFragment - Implement proper template.innerHTML getter/setter to access content instead of direct children - Add template.toString() method following TextElement pattern to include content in serialization - Ensure template element follows HTML spec where innerHTML always references template.content - Add null safety checks in parsing logic to prevent errors during template processing The template element now correctly: - Parses children into content during HTML parsing - Returns content innerHTML when accessing template.innerHTML - Maintains separation between direct children and content - Serializes with content included in toString() --browser result-- const f = document.createDocumentFragment(); const d = document.createElement('div') d.innerHTML='<a>aaa</a>'; f.append(d); const t = document.createElement('template'); t.content.append(f); console.log(t.innerHTML); // '<div><a>aaa</a></div>' console.log(t.innerText); //'' --linkedom-- console.log(t.innerHTML); // '' console.log(t.innerText); //''
1 parent c7933dc commit 44b646e

File tree

5 files changed

+67
-16
lines changed

5 files changed

+67
-16
lines changed

cjs/html/template-element.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ const {registerHTMLClass} = require('../shared/register-html-class.js');
55

66
const {HTMLElement} = require('./element.js');
77

8-
import {getInnerHtml} from '../mixin/inner-html.js'
8+
const {getInnerHtml} = require('../mixin/inner-html.js');
9+
const {setInnerHtml} = require("../mixin/inner-html");
10+
11+
const {toString} = require('./element.js').HTMLElement.prototype;
912

1013
const tagName = 'template';
1114

@@ -14,22 +17,27 @@ const tagName = 'template';
1417
*/
1518
class HTMLTemplateElement extends HTMLElement {
1619
constructor(ownerDocument) {
17-
super(ownerDocument, tagName);
18-
const content = this.ownerDocument.createDocumentFragment();
19-
(this[CONTENT] = content)[PRIVATE] = this;
20+
super(ownerDocument, tagName);
21+
const content = this.ownerDocument.createDocumentFragment();
22+
(this[CONTENT] = content)[PRIVATE] = this;
2023
}
2124

2225
get innerHTML() {
2326
return getInnerHtml(this.content);
2427
}
2528

29+
set innerHTML(html) {
30+
setInnerHtml(this[CONTENT], html);
31+
}
32+
2633
get content() {
27-
if (this.hasChildNodes() && !this[CONTENT].hasChildNodes()) {
28-
for (const node of this.childNodes)
29-
this[CONTENT].appendChild(node.cloneNode(true));
30-
}
3134
return this[CONTENT];
3235
}
36+
37+
toString() {
38+
const outerHTML = toString.call(this.cloneNode());
39+
return outerHTML.replace('></template>', () => `>${this.innerHTML}</template>`);
40+
}
3341
}
3442

3543
registerHTMLClass(tagName, HTMLTemplateElement);

cjs/shared/parse-from-string.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const HTMLParser2 = require('htmlparser2');
33

44
const {ELEMENT_NODE, SVG_NAMESPACE} = require('./constants.js');
5-
const {CUSTOM_ELEMENTS, PREV, END, VALUE} = require('./symbols.js');
5+
const {CUSTOM_ELEMENTS, PREV, END, VALUE, CONTENT, PRIVATE} = require('./symbols.js');
66
const {keys} = require('./object.js');
77

88
const {knownBoundaries, knownSiblings} = require('./utils.js');
@@ -19,6 +19,10 @@ const {Parser} = HTMLParser2;
1919
let notParsing = true;
2020

2121
const append = (self, node, active) => {
22+
if (self && self.localName === 'template' && self[CONTENT]) {
23+
self = self[CONTENT];
24+
}
25+
if (!self) return node; // null 체크 추가
2226
const end = self[END];
2327
node.parentNode = self;
2428
knownBoundaries(end[PREV], node, end);
@@ -107,6 +111,10 @@ const parseFromString = (document, isHTML, markupLanguage) => {
107111
onclosetag() {
108112
if (isHTML && node === ownerSVGElement)
109113
ownerSVGElement = null;
114+
// template content인 경우 원래 template로 돌아가기
115+
if (node && node[PRIVATE]) {
116+
node = node[PRIVATE];
117+
}
110118
node = node.parentNode;
111119
}
112120
}, {

esm/html/template-element.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {registerHTMLClass} from '../shared/register-html-class.js';
44

55
import {HTMLElement} from './element.js';
66

7-
import {getInnerHtml} from '../mixin/inner-html.js'
7+
import {getInnerHtml, setInnerHtml} from '../mixin/inner-html.js';
8+
9+
const {toString} = HTMLElement.prototype;
810

911
const tagName = 'template';
1012

@@ -22,13 +24,18 @@ class HTMLTemplateElement extends HTMLElement {
2224
return getInnerHtml(this.content);
2325
}
2426

27+
set innerHTML(html) {
28+
setInnerHtml(this[CONTENT], html);
29+
}
30+
2531
get content() {
26-
if (this.hasChildNodes() && !this[CONTENT].hasChildNodes()) {
27-
for (const node of this.childNodes)
28-
this[CONTENT].appendChild(node.cloneNode(true));
29-
}
3032
return this[CONTENT];
3133
}
34+
35+
toString() {
36+
const outerHTML = toString.call(this.cloneNode());
37+
return outerHTML.replace('></template>', () => `>${this.innerHTML}</template>`);
38+
}
3239
}
3340

3441
registerHTMLClass(tagName, HTMLTemplateElement);

esm/shared/parse-from-string.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as HTMLParser2 from 'htmlparser2';
22

33
import {ELEMENT_NODE, SVG_NAMESPACE} from './constants.js';
4-
import {CUSTOM_ELEMENTS, PREV, END, VALUE} from './symbols.js';
4+
import {CUSTOM_ELEMENTS, PREV, END, VALUE, CONTENT, PRIVATE} from './symbols.js';
55
import {keys} from './object.js';
66

77
import {knownBoundaries, knownSiblings} from './utils.js';
@@ -18,6 +18,10 @@ const {Parser} = HTMLParser2;
1818
let notParsing = true;
1919

2020
const append = (self, node, active) => {
21+
if (self && self.localName === 'template' && self[CONTENT]) {
22+
self = self[CONTENT];
23+
}
24+
if (!self) return node; // null 체크 추가
2125
const end = self[END];
2226
node.parentNode = self;
2327
knownBoundaries(end[PREV], node, end);
@@ -105,6 +109,10 @@ export const parseFromString = (document, isHTML, markupLanguage) => {
105109
onclosetag() {
106110
if (isHTML && node === ownerSVGElement)
107111
ownerSVGElement = null;
112+
// template content인 경우 원래 template로 돌아가기
113+
if (node && node[PRIVATE]) {
114+
node = node[PRIVATE];
115+
}
108116
node = node.parentNode;
109117
}
110118
}, {

test/html/template-element.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ assert(document.querySelector('template > *'), null);
1515
assert(template.content, template.content);
1616

1717
template.replaceChildren();
18-
assert(template.innerHTML, '');
18+
assert(template.innerHTML, '<div>foo</div><div>bar</div>');
1919

2020
template.innerHTML = '<p>ok</p>';
2121
assert(template.innerHTML, '<p>ok</p>');
@@ -47,3 +47,23 @@ const docWithTemplateAttribute = parseHTML(`<div template="anything"><p>not insi
4747

4848
assert(docWithTemplateAttribute.querySelector('*').tagName, 'P');
4949
assert(docWithTemplateAttribute.querySelectorAll('*').length, 1);
50+
51+
template = document.createElement('template');
52+
let fragment = document.createDocumentFragment();
53+
let div = document.createElement('div')
54+
div.innerHTML='<a>child element</a>';
55+
fragment.append(div);
56+
template.content.append(fragment);
57+
assert(template.innerHTML, '<div><a>child element</a></div>', 'template.innerHTML');
58+
assert(template.innerText, '', 'template.innerText');
59+
60+
template = document.createElement('template');
61+
template.innerHTML='<div><a>child element innerHTML</a></div>';
62+
fragment = document.createDocumentFragment();
63+
div = document.createElement('div');
64+
div.innerHTML='<a>new child element</a>';
65+
assert(Array.from(template.children).length, 0, 'template.appendChild zero');
66+
template.appendChild(div);
67+
assert(Array.from(template.children).length, 1, 'template.appendChild one');
68+
assert(template.innerText, 'new child element', 'template.innerText');
69+
assert(template.innerHTML, '<div><a>child element innerHTML</a></div>', 'template.innerHTML The template maintains internal HTML even with additional children');

0 commit comments

Comments
 (0)