Skip to content

Commit 756a368

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()). Bug: T395802
1 parent 7cc0a7c commit 756a368

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

src/Component.php

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,67 @@ 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->item( 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+
for ( $i = 0; $i < $rootNodes->length; $i++ ) {
108+
$node = $rootNodes->item( $i );
109+
if ( $node->nodeType === XML_COMMENT_NODE ) {
110+
// comment node, ignore
111+
continue;
112+
} elseif ( $node->nodeType === XML_TEXT_NODE ) {
113+
if ( trim( $node->textContent ) === '' ) {
114+
// whitespace-only text node, ignore
115+
continue;
116+
} else {
117+
// not SFC
118+
$onlyTemplateElement = null;
119+
break;
120+
}
121+
}
122+
if ( $node->tagName === 'template' ) {
123+
if ( $onlyTemplateElement === null ) {
124+
$onlyTemplateElement = $node;
125+
} else {
126+
// more than one <template>, handle as non-SFC and throw error below
127+
$onlyTemplateElement = null;
128+
break;
129+
}
130+
} elseif ( $node->tagName !== 'script' && $node->tagName !== 'style' ) {
131+
// top-level tag other than <template>, <script> or <style> => not SFC
132+
$onlyTemplateElement = null;
133+
break;
134+
}
135+
}
136+
if ( $onlyTemplateElement !== null ) {
137+
$rootNodes = $onlyTemplateElement->childNodes;
138+
}
139+
140+
// pick the only “substantial” child element as the root (ignore whitespace, comments)
141+
$onlyRootElement = null;
142+
for ( $i = 0; $i < $rootNodes->length; $i++ ) {
143+
$node = $rootNodes->item( $i );
144+
if ( $node->nodeType === XML_COMMENT_NODE ) {
145+
// comment node, ignore
146+
continue;
147+
} elseif ( $node->nodeType === XML_TEXT_NODE && trim( $node->textContent ) === '' ) {
148+
// whitespace-only text node, ignore
149+
continue;
150+
}
151+
if ( $onlyRootElement === null ) {
152+
$onlyRootElement = $node;
153+
} else {
154+
throw new Exception( 'Template should only have one root node' );
155+
}
106156
}
107157

108-
return $rootNodes->item( 0 );
158+
if ( $onlyRootElement !== null ) {
159+
return $onlyRootElement;
160+
} else {
161+
throw new Exception( 'Template contained no root node' );
162+
}
109163
}
110164

111165
/**

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)