Adding the noopener attribute to CommonMark
Over on Notist I’m using the PHP League CommonMark Markdown parser to convert Markdown to HTML.
One recommendation that Google’s Lighthouse audits recommend is that rel="noopener"
be added to any external links. There’s an entire article explaining why this is a positive move for both security and performance.
I initially couldn’t figure out how best to do this, but it turns out it’s really simple. CommonMark enables you to specify and register custom renderers and they even have an example of one for adding a class to external links. All I needed to do was slightly modify this to add the rel="noopener"
attribute and I was away.
<?php | |
namespace Notist\Extensions; | |
use League\CommonMark\ElementRendererInterface; | |
use League\CommonMark\HtmlElement; | |
use League\CommonMark\Inline\Element\AbstractInline; | |
use League\CommonMark\Inline\Element\Link; | |
use League\CommonMark\Inline\Renderer\InlineRendererInterface; | |
class ExternalLinkRenderer implements InlineRendererInterface | |
{ | |
private $host; | |
public function __construct($host) | |
{ | |
$this->host = $host; | |
} | |
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer) | |
{ | |
if (!($inline instanceof Link)) { | |
throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline)); | |
} | |
$attrs = []; | |
$attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true); | |
if (isset($inline->attributes['title'])) { | |
$attrs['title'] = $htmlRenderer->escape($inline->data['title'], true); | |
} | |
if ($this->isExternalUrl($inline->getUrl())) { | |
$attrs['target'] = '_blank'; | |
$attrs['rel'] = 'noopener'; | |
} | |
return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children())); | |
} | |
private function isExternalUrl($url) | |
{ | |
return parse_url($url, PHP_URL_HOST) !== $this->host; | |
} | |
} |
Posting this here, because when I searched I couldn’t find anything interesting about CommonMark, PHP, and noopener
so I hope it might help someone else.