Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bmack committed Dec 30, 2018
0 parents commit 238ab55
Show file tree
Hide file tree
Showing 13 changed files with 770 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/Tests export-ignore
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Build
composer.lock
vendor
public
.phpunit.result.cache
90 changes: 90 additions & 0 deletions Classes/PageRendererHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php
declare(strict_types = 1);
namespace B13\Http2;

/*
* This file is part of TYPO3 CMS-based extension "http2" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

/**
* Hooks into PageRenderer before the rendering is taken care of, and remember the files
* that can be sent as resources.
*
* This considers that everything is required, thus is marked as "preload", not via "prefetch".
*/
class PageRendererHook
{
/**
* @var ResourceMatcher
*/
protected $matcher;

/**
* @param ResourceMatcher|null $matcher
*/
public function __construct(ResourceMatcher $matcher = null)
{
$this->matcher = $matcher ?: GeneralUtility::makeInstance(ResourceMatcher::class);
}

/**
* @param array $params
* @param PageRenderer $pageRenderer
* @internal this is just a hook implementation.
*/
public function accumulateResources(array $params, PageRenderer $pageRenderer)
{
// If this is a second run (non-cached cObjects adding more data), then the existing cached data is fetched
if ($this->getTypoScriptFrontendController() instanceof TypoScriptFrontendController) {
$allResources = $this->getTypoScriptFrontendController()->config['b13/http2'] ?? [];
} else {
$allResources = [];
}
foreach ($params['headerData'] as $headerData) {
$allResources = array_merge_recursive($allResources, $this->matcher->match($headerData));
}
foreach ($params['footerData'] as $footerData) {
$allResources = array_merge_recursive($allResources, $this->matcher->match($footerData));
}
$allResources = array_merge_recursive($allResources, $this->matcher->match($params['jsLibs']));
$allResources = array_merge_recursive($allResources, $this->matcher->match($params['jsFiles']));
$allResources = array_merge_recursive($allResources, $this->matcher->match($params['jsFooterLibs']));
$allResources = array_merge_recursive($allResources, $this->matcher->match($params['jsFooterFiles']));
$allResources = array_merge_recursive($allResources, $this->matcher->match($params['cssLibs']));
$allResources = array_merge_recursive($allResources, $this->matcher->match($params['cssFiles']));

$allResources['scripts'] = array_unique($allResources['scripts']);
$allResources['styles'] = array_unique($allResources['styles']);

$this->process($allResources);
}

/**
* In FE the data is stored in TSFE->config.
* In BE it is sent directly to the HTTP response.
*
* @param array $allResources
*/
protected function process(array $allResources)
{
if ($this->getTypoScriptFrontendController() instanceof TypoScriptFrontendController) {
$this->getTypoScriptFrontendController()->config['b13/http2'] = $allResources;
} elseif (GeneralUtility::getIndpEnv('TYPO3_SSL')) {
// Push directly into the TYPO3 Backend, but only if TYPO3 is running in SSL
GeneralUtility::makeInstance(ResourcePusher::class)->pushAll($allResources);
}
}

protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}
49 changes: 49 additions & 0 deletions Classes/ResourceMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
declare(strict_types = 1);
namespace B13\Http2;

/*
* This file is part of TYPO3 CMS-based extension "http2" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

/**
* Parses a string by detecting <script> and <link> tags.
*/
class ResourceMatcher
{
protected $resourcePattern = '[\'"]?([\w\s\/\-:?.]*)["\']?';

/**
* @param string $input
* @return array
*/
public function match(string $input): array
{
if (empty($input)) {
return ['scripts' => [], 'styles' => []];
}
$matches = [];
preg_match_all(
'/<script[\/\s\w-="]*src=' . $this->resourcePattern . '[^>]*>'
. '|' .
'<link[\/\s\w-="]*href=' . $this->resourcePattern . '[^>]*>'
. '/ui',
$input,
$matches
);

// simple check - should be optimized to include further checks
$styles = array_filter($matches[2], function ($resource) {
return strpos($resource, '.css') !== false;
});

return [
'scripts' => array_values(array_filter($matches[1])),
'styles' => array_values($styles)
];
}
}
63 changes: 63 additions & 0 deletions Classes/ResourcePusher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);
namespace B13\Http2;

/*
* This file is part of TYPO3 CMS-based extension "http2" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

/**
* Takes existing accumulated resources and pushes them as HTTP2 <link> headers.
*
* This considers that everything is required, thus is marked as "preload", not via "prefetch".
*
* Also, it only tackles "script", "style", but we should incorporate font as well.
* See https://w3c.github.io/preload/#as-attribute
*/
class ResourcePusher
{
/**
* @param array $resources
*/
public function pushAll(array $resources)
{
foreach ($resources['scripts'] as $resource) {
$this->addPreloadHeader($resource, 'script');
}
foreach ($resources['styles'] as $resource) {
$this->addPreloadHeader($resource, 'style');
}
}

/**
* @param array $params
* @param TypoScriptFrontendController $typoScriptFrontendController
* @internal this is just a hook implementation.
*/
public function pushForFrontend(array $params, TypoScriptFrontendController $typoScriptFrontendController)
{
$allResources = $typoScriptFrontendController->config['b13/http2'];
if (GeneralUtility::getIndpEnv('TYPO3_SSL') && is_array($allResources)) {
$this->pushAll($allResources);
}
}

/**
* as="{style/script/image}"
*
* @param string $uri
* @param string $type
*/
protected function addPreloadHeader(string $uri, string $type)
{
header('Link: <' . htmlspecialchars(PathUtility::getAbsoluteWebPath($uri)) . '>; rel=preload; as=' . $type, false);
}
}
Loading

0 comments on commit 238ab55

Please sign in to comment.