Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ To disable the inspector:
bin/magento mageforge:theme:inspector disable
```

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



---
Expand Down
80 changes: 80 additions & 0 deletions src/Model/TemplateEngine/Decorator/InspectorHints.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ class InspectorHints implements TemplateEngineInterface
* @param Random $random
* @param BlockCacheCollector $cacheCollector
* @param File $fileDriver
* @param string[] $excludedClassPrefixes Block class prefixes to skip inspector wrapping for
* @param string[] $excludedTemplatePaths Template path substrings to skip inspector wrapping for
*/
public function __construct(
private readonly TemplateEngineInterface $subject,
private readonly bool $showBlockHints,
private readonly Random $random,
private readonly BlockCacheCollector $cacheCollector,
private readonly File $fileDriver,
private readonly array $excludedClassPrefixes = [],
private readonly array $excludedTemplatePaths = [],
) {
$this->magentoRoot = $this->resolveMagentoRoot();
}
Expand All @@ -58,6 +62,55 @@ private function resolveMagentoRoot(): string
return $path;
}

/**
* Check if a block class should be excluded from inspector wrapping
*
* @param string $blockClass
* @return bool
*/
private function isExcluded(string $blockClass): bool
{
foreach ($this->excludedClassPrefixes as $prefix) {
if (str_starts_with($blockClass, $prefix)) {
return true;
}
}

return false;
}

/**
* Check if a template path should be excluded from inspector wrapping
*
* @param string $templateFile
* @return bool
*/
private function isExcludedTemplate(string $templateFile): bool
{
$normalized = str_replace('\\', '/', strtolower($templateFile));
foreach ($this->excludedTemplatePaths as $path) {
if (str_contains($normalized, str_replace('\\', '/', strtolower(trim($path))))) {
return true;
}
}

return false;
}

/**
* Check if rendered HTML contains wire attributes (Magewire/Livewire components)
*
* Wrapping these in HTML comments breaks wire:id injection which relies on
* finding the first root element via regex.
*
* @param string $html
* @return bool
*/
private function containsWireAttributes(string $html): bool
{
return str_contains($html, 'wire:id=') || str_contains($html, 'wire:initial-data=');
}

/**
* Insert inspector data attributes into the rendered block contents
*
Expand All @@ -78,6 +131,33 @@ public function render(BlockInterface $block, $templateFile, array $dictionary =
return $result;
}

// Skip inspector wrapping for excluded block classes (e.g. Magewire components)
if ($this->isExcluded(get_class($block))) {
return $result;
}

// Skip inspector wrapping for templates in excluded paths (e.g. /magewire/ directories).
// Magewire injects wire:id AFTER the template engine returns via regex on the root element.
// Wrapping the output in HTML comments before that element breaks the injection.
if ($this->isExcludedTemplate($templateFile)) {
return $result;
}

// Skip inspector wrapping for Magewire component blocks.
// Magewire sets a 'magewire' data key on the block before rendering and injects wire:id
// via regex AFTER the template engine returns. Wrapping the output in HTML comments
// shifts the offset used by insertAttributesIntoHtmlRoot(), causing broken components.
// Soft dependency: hasData() is a Magento DataObject method, not a Magewire class.
if (method_exists($block, 'hasData') && $block->hasData('magewire')) {
return $result;
}

// Skip inspector wrapping if the rendered HTML contains wire attributes (Magewire/Livewire).
// This catches container blocks whose children have already been rendered with wire attributes.
if ($this->containsWireAttributes($result)) {
return $result;
}

// Only inject attributes if there's actual HTML content
if (empty(trim($result))) {
return $result;
Expand Down
8 changes: 8 additions & 0 deletions src/etc/frontend/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
<type name="OpenForgeProject\MageForge\Model\TemplateEngine\Decorator\InspectorHints">
<arguments>
<argument name="cacheCollector" xsi:type="object">OpenForgeProject\MageForge\Service\Inspector\Cache\BlockCacheCollector</argument>
<!-- Block class prefixes excluded from inspector wrapping (e.g. Magewire injects wire:id after render) -->
<argument name="excludedClassPrefixes" xsi:type="array">
<item name="magewire" xsi:type="string">Magewirephp\Magewire\</item>
</argument>
<!-- Template path substrings excluded from inspector wrapping (case-insensitive) -->
<argument name="excludedTemplatePaths" xsi:type="array">
<item name="magewire" xsi:type="string">magewire/</item>
</argument>
</arguments>
</type>

Expand Down
Loading