Skip to content

Commit 9abe851

Browse files
authored
Fix/inspector in magewire templates (#168)
* fix: exclude specific block classes from inspector wrapping * docs: add note about Inspector compatibility with Magewire components * fix: skip inspector wrapping for rendered HTML with wire attributes * fix: skip inspector wrapping for Magewire component blocks * fix: add excluded template paths for inspector wrapping * fix: normalize excluded template path comparison in InspectorHints
1 parent bae517d commit 9abe851

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ To disable the inspector:
124124
bin/magento mageforge:theme:inspector disable
125125
```
126126

127+
> **Note:** The Inspector is currently not compatible with **Magewire** components. Magewire blocks are automatically excluded from inspection to prevent rendering errors.
128+
127129

128130

129131
---

src/Model/TemplateEngine/Decorator/InspectorHints.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@ class InspectorHints implements TemplateEngineInterface
2929
* @param Random $random
3030
* @param BlockCacheCollector $cacheCollector
3131
* @param File $fileDriver
32+
* @param string[] $excludedClassPrefixes Block class prefixes to skip inspector wrapping for
33+
* @param string[] $excludedTemplatePaths Template path substrings to skip inspector wrapping for
3234
*/
3335
public function __construct(
3436
private readonly TemplateEngineInterface $subject,
3537
private readonly bool $showBlockHints,
3638
private readonly Random $random,
3739
private readonly BlockCacheCollector $cacheCollector,
3840
private readonly File $fileDriver,
41+
private readonly array $excludedClassPrefixes = [],
42+
private readonly array $excludedTemplatePaths = [],
3943
) {
4044
$this->magentoRoot = $this->resolveMagentoRoot();
4145
}
@@ -58,6 +62,55 @@ private function resolveMagentoRoot(): string
5862
return $path;
5963
}
6064

65+
/**
66+
* Check if a block class should be excluded from inspector wrapping
67+
*
68+
* @param string $blockClass
69+
* @return bool
70+
*/
71+
private function isExcluded(string $blockClass): bool
72+
{
73+
foreach ($this->excludedClassPrefixes as $prefix) {
74+
if (str_starts_with($blockClass, $prefix)) {
75+
return true;
76+
}
77+
}
78+
79+
return false;
80+
}
81+
82+
/**
83+
* Check if a template path should be excluded from inspector wrapping
84+
*
85+
* @param string $templateFile
86+
* @return bool
87+
*/
88+
private function isExcludedTemplate(string $templateFile): bool
89+
{
90+
$normalized = str_replace('\\', '/', strtolower($templateFile));
91+
foreach ($this->excludedTemplatePaths as $path) {
92+
if (str_contains($normalized, str_replace('\\', '/', strtolower(trim($path))))) {
93+
return true;
94+
}
95+
}
96+
97+
return false;
98+
}
99+
100+
/**
101+
* Check if rendered HTML contains wire attributes (Magewire/Livewire components)
102+
*
103+
* Wrapping these in HTML comments breaks wire:id injection which relies on
104+
* finding the first root element via regex.
105+
*
106+
* @param string $html
107+
* @return bool
108+
*/
109+
private function containsWireAttributes(string $html): bool
110+
{
111+
return str_contains($html, 'wire:id=') || str_contains($html, 'wire:initial-data=');
112+
}
113+
61114
/**
62115
* Insert inspector data attributes into the rendered block contents
63116
*
@@ -78,6 +131,33 @@ public function render(BlockInterface $block, $templateFile, array $dictionary =
78131
return $result;
79132
}
80133

134+
// Skip inspector wrapping for excluded block classes (e.g. Magewire components)
135+
if ($this->isExcluded(get_class($block))) {
136+
return $result;
137+
}
138+
139+
// Skip inspector wrapping for templates in excluded paths (e.g. /magewire/ directories).
140+
// Magewire injects wire:id AFTER the template engine returns via regex on the root element.
141+
// Wrapping the output in HTML comments before that element breaks the injection.
142+
if ($this->isExcludedTemplate($templateFile)) {
143+
return $result;
144+
}
145+
146+
// Skip inspector wrapping for Magewire component blocks.
147+
// Magewire sets a 'magewire' data key on the block before rendering and injects wire:id
148+
// via regex AFTER the template engine returns. Wrapping the output in HTML comments
149+
// shifts the offset used by insertAttributesIntoHtmlRoot(), causing broken components.
150+
// Soft dependency: hasData() is a Magento DataObject method, not a Magewire class.
151+
if (method_exists($block, 'hasData') && $block->hasData('magewire')) {
152+
return $result;
153+
}
154+
155+
// Skip inspector wrapping if the rendered HTML contains wire attributes (Magewire/Livewire).
156+
// This catches container blocks whose children have already been rendered with wire attributes.
157+
if ($this->containsWireAttributes($result)) {
158+
return $result;
159+
}
160+
81161
// Only inject attributes if there's actual HTML content
82162
if (empty(trim($result))) {
83163
return $result;

src/etc/frontend/di.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
<type name="OpenForgeProject\MageForge\Model\TemplateEngine\Decorator\InspectorHints">
1313
<arguments>
1414
<argument name="cacheCollector" xsi:type="object">OpenForgeProject\MageForge\Service\Inspector\Cache\BlockCacheCollector</argument>
15+
<!-- Block class prefixes excluded from inspector wrapping (e.g. Magewire injects wire:id after render) -->
16+
<argument name="excludedClassPrefixes" xsi:type="array">
17+
<item name="magewire" xsi:type="string">Magewirephp\Magewire\</item>
18+
</argument>
19+
<!-- Template path substrings excluded from inspector wrapping (case-insensitive) -->
20+
<argument name="excludedTemplatePaths" xsi:type="array">
21+
<item name="magewire" xsi:type="string">magewire/</item>
22+
</argument>
1523
</arguments>
1624
</type>
1725

0 commit comments

Comments
 (0)