Skip to content

Commit 343a22a

Browse files
Support single-file component inputs
If the input contains a single root <template> and the other root elements are all <script> or <style>, then pick the <template> contents as the actual root element and only render its contents. This allows us to use SFC *.vue files as the input. The getRootNode() method is now obnoxiously long and should certainly be split up, I’m just not yet sure how. Maybe both parseHtml() and getRootNode() should be in a separate class, and Component’s responsibility starts at a DOMNode (with the current methods handleNode() + saveHTML()). (But keep in mind that v-html handling depends on parseHtml(), so if that moves to a separate class, Component still needs to be able to access it…) Bug: T395802
1 parent c6b3f38 commit 343a22a

File tree

2 files changed

+88
-3
lines changed

2 files changed

+88
-3
lines changed

src/Component.php

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,65 @@ private function getRootNode( DOMDocument $document ) {
9999
throw new Exception( 'Empty document' );
100100
}
101101

102+
// documentElement is the <html>, its child is the <body>, its children are the actual root nodes
102103
$rootNodes = $document->documentElement->childNodes[0]->childNodes;
103104

104-
if ( $rootNodes->length > 1 ) {
105-
throw new Exception( 'Template should have only one root node' );
105+
// potentially pick just the <template> contents of a single-file component (SFC)
106+
$onlyTemplateElement = null;
107+
foreach ( $rootNodes as $node ) {
108+
if ( $node->nodeType === XML_COMMENT_NODE ) {
109+
// comment node, ignore
110+
continue;
111+
} elseif ( $node->nodeType === XML_TEXT_NODE ) {
112+
if ( trim( $node->textContent ) === '' ) {
113+
// whitespace-only text node, ignore
114+
continue;
115+
} else {
116+
// not SFC
117+
$onlyTemplateElement = null;
118+
break;
119+
}
120+
}
121+
if ( $node->tagName === 'template' ) {
122+
if ( $onlyTemplateElement === null ) {
123+
$onlyTemplateElement = $node;
124+
} else {
125+
// more than one <template>, handle as non-SFC and throw error below
126+
$onlyTemplateElement = null;
127+
break;
128+
}
129+
} elseif ( $node->tagName !== 'script' && $node->tagName !== 'style' ) {
130+
// top-level tag other than <template>, <script> or <style> => not SFC
131+
$onlyTemplateElement = null;
132+
break;
133+
}
134+
}
135+
if ( $onlyTemplateElement !== null ) {
136+
$rootNodes = $onlyTemplateElement->childNodes;
137+
}
138+
139+
// pick the only “substantial” child element as the root (ignore whitespace, comments)
140+
$onlyRootElement = null;
141+
foreach ( $rootNodes as $node ) {
142+
if ( $node->nodeType === XML_COMMENT_NODE ) {
143+
// comment node, ignore
144+
continue;
145+
} elseif ( $node->nodeType === XML_TEXT_NODE && trim( $node->textContent ) === '' ) {
146+
// whitespace-only text node, ignore
147+
continue;
148+
}
149+
if ( $onlyRootElement === null ) {
150+
$onlyRootElement = $node;
151+
} else {
152+
throw new Exception( 'Template should only have one root node' );
153+
}
106154
}
107155

108-
return $rootNodes[0];
156+
if ( $onlyRootElement !== null ) {
157+
return $onlyRootElement;
158+
} else {
159+
throw new Exception( 'Template contained no root node' );
160+
}
109161
}
110162

111163
/**

tests/php/TemplatingTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,39 @@ public function testJustASingleEmptyHtmlElement() {
1717
$this->assertSame( '<div></div>', $result );
1818
}
1919

20+
public function testSingleFileComponent_OnlyTemplate(): void {
21+
$result = $this->createAndRender( '<template><div></div></template>', [] );
22+
23+
$this->assertSame( '<div></div>', $result );
24+
}
25+
26+
public function testSingleFileComponent_TemplateAndScriptAndStyle(): void {
27+
$template = <<< 'EOF'
28+
<template>
29+
<!-- eslint-disable-next-line something -->
30+
<div></div>
31+
</template>
32+
<script setup>
33+
const something = 'something';
34+
</script>
35+
<style scoped>
36+
.some-class {
37+
font-weight: bold;
38+
}
39+
</style>
40+
EOF;
41+
$result = $this->createAndRender( $template, [] );
42+
43+
$this->assertSame( '<div></div>', $result );
44+
}
45+
46+
public function testSingleFileComponent_ScriptAndTemplateAndStyle(): void {
47+
$template = '<script></script><template><div></div></template>';
48+
$result = $this->createAndRender( $template, [] );
49+
50+
$this->assertSame( '<div></div>', $result );
51+
}
52+
2053
public function testTemplateHasNoRootNodes_ThrowsAnException(): void {
2154
$this->expectException( Exception::class );
2255
$this->createAndRender( '', [] );

0 commit comments

Comments
 (0)