Skip to content

Commit

Permalink
Fixed assets preloading via the Link header
Browse files Browse the repository at this point in the history
  • Loading branch information
tg666 committed Nov 29, 2022
1 parent c6acd78 commit aa0f6b2
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 19 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"mockery/mockery": "^1.5",
"nette/application": "^3.1.0",
"nette/bootstrap": "^3.1",
"nette/component-model": "^3.0.2",
"nette/tester": "^2.4.3",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-nette": "^1.1",
Expand Down
33 changes: 32 additions & 1 deletion src/Bridge/Nette/Application/ApplicationResponseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

namespace SixtyEightPublishers\WebpackEncoreBundle\Bridge\Nette\Application;

use Throwable;
use ReflectionClass;
use Nette\Utils\Helpers;
use Nette\Application\Application;
use Nette\Application\UI\Template;
use Nette\Http\IResponse as HttpResponse;
use Nette\Application\Responses\TextResponse;
use Nette\Application\Response as ApplicationResponse;
use SixtyEightPublishers\WebpackEncoreBundle\Asset\TagRenderer;
use SixtyEightPublishers\WebpackEncoreBundle\Asset\EntryPointLookupCollectionInterface;
use function assert;
use function explode;
use function implode;
use function sprintf;
Expand Down Expand Up @@ -44,7 +51,31 @@ public static function register(Application $application, HttpResponse $response
$application->onResponse[] = new self($response, $tagRenderer, $entryPointLookupCollection, $buildNames);
}

public function __invoke(): void
/**
* @throws Throwable
*/
public function __invoke(Application $application, ApplicationResponse $response): void
{
if (!$response instanceof TextResponse || !$response->getSource() instanceof Template) {
$this->processLinks();

return;
}

$source = $response->getSource();
assert($source instanceof Template);
$rendered = Helpers::capture(static fn () => $source->render());

$reflection = new ReflectionClass($response);
$property = $reflection->getProperty('source');

$property->setAccessible(TRUE);
$property->setValue($response, $rendered);

$this->processLinks();
}

private function processLinks(): void
{
$defaultAttributes = $this->tagRenderer->getDefaultAttributes();
$crossOrigin = $defaultAttributes['crossorigin'] ?? NULL;
Expand Down
51 changes: 45 additions & 6 deletions tests/Bridge/Nette/Application/ApplicationResponseHandlerTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use Tester\Assert;
use Tester\TestCase;
use Nette\Application\Application;
use Nette\Http\IResponse as HttpResponse;
use Nette\Bridges\ApplicationLatte\Template;
use Nette\Application\Responses\JsonResponse;
use Nette\Application\Responses\TextResponse;
use Nette\Application\Response as ApplicationResponse;
use SixtyEightPublishers\WebpackEncoreBundle\Asset\TagRenderer;
use SixtyEightPublishers\WebpackEncoreBundle\Asset\EntryPointLookupInterface;
use SixtyEightPublishers\WebpackEncoreBundle\Asset\EntryPointLookupCollectionInterface;
Expand All @@ -31,19 +35,43 @@ final class ApplicationResponseHandlerTest extends TestCase
Assert::type(ApplicationResponseHandler::class, $application->onResponse[0]);
}

public function testInvokingHandlerWithoutPreviousHeader(): void
public function testInvokingHandlerOnTextResponseWithoutPreviousHeader(): void
{
$this->assertInvokingHandler(
NULL,
'<http://localhost:8080/build/file1.js>; rel="preload"; as="script",<http://localhost:8080/build/file2.js>; rel="preload"; as="script",<http://localhost:8080/build/file1.css>; rel="preload"; as="style",<http://localhost:8080/build/file2.css>; rel="preload"; as="style"'
'<http://localhost:8080/build/file1.js>; rel="preload"; as="script",<http://localhost:8080/build/file2.js>; rel="preload"; as="script",<http://localhost:8080/build/file1.css>; rel="preload"; as="style",<http://localhost:8080/build/file2.css>; rel="preload"; as="style"',
NULL,
$this->createTextApplicationResponse()
);
}

public function testInvokingHandlerWithPreviousHeaderAndCrossOrigin(): void
public function testInvokingHandlerOnJsonResponseWithoutPreviousHeader(): void
{
$this->assertInvokingHandler(
NULL,
'<http://localhost:8080/build/file1.js>; rel="preload"; as="script",<http://localhost:8080/build/file2.js>; rel="preload"; as="script",<http://localhost:8080/build/file1.css>; rel="preload"; as="style",<http://localhost:8080/build/file2.css>; rel="preload"; as="style"',
NULL,
new JsonResponse(['status' => 'ok'])
);
}

public function testInvokingHandlerOnTextResponseWithPreviousHeaderAndCrossOrigin(): void
{
$this->assertInvokingHandler(
'<http://localhost:8080/static.js>; rel="preload"; as="script",<http://localhost:8080/static.css>; rel="preload"; as="style"',
'<http://localhost:8080/static.js>; rel="preload"; as="script",<http://localhost:8080/static.css>; rel="preload"; as="style",<http://localhost:8080/build/file1.js>; rel="preload"; as="script",<http://localhost:8080/build/file2.js>; rel="preload"; as="script",<http://localhost:8080/build/file1.css>; rel="preload"; as="style",<http://localhost:8080/build/file2.css>; rel="preload"; as="style"'
'<http://localhost:8080/static.js>; rel="preload"; as="script",<http://localhost:8080/static.css>; rel="preload"; as="style",<http://localhost:8080/build/file1.js>; rel="preload"; as="script"; crossorigin="anonymous",<http://localhost:8080/build/file2.js>; rel="preload"; as="script"; crossorigin="anonymous",<http://localhost:8080/build/file1.css>; rel="preload"; as="style"; crossorigin="anonymous",<http://localhost:8080/build/file2.css>; rel="preload"; as="style"; crossorigin="anonymous"',
'anonymous',
$this->createTextApplicationResponse()
);
}

public function testInvokingHandlerOnJsonResponseWithPreviousHeader(): void
{
$this->assertInvokingHandler(
'<http://localhost:8080/static.js>; rel="preload"; as="script",<http://localhost:8080/static.css>; rel="preload"; as="style"',
'<http://localhost:8080/static.js>; rel="preload"; as="script",<http://localhost:8080/static.css>; rel="preload"; as="style",<http://localhost:8080/build/file1.js>; rel="preload"; as="script",<http://localhost:8080/build/file2.js>; rel="preload"; as="script",<http://localhost:8080/build/file1.css>; rel="preload"; as="style",<http://localhost:8080/build/file2.css>; rel="preload"; as="style"',
NULL,
new JsonResponse(['status' => 'ok'])
);
}

Expand All @@ -52,7 +80,7 @@ final class ApplicationResponseHandlerTest extends TestCase
Mockery::close();
}

private function assertInvokingHandler(?string $previousHeader, string $expectedHeader, ?string $crossOrigin = NULL): void
private function assertInvokingHandler(?string $previousHeader, string $expectedHeader, ?string $crossOrigin, ApplicationResponse $applicationResponse): void
{
$entrypointLookup1 = Mockery::mock(EntryPointLookupInterface::class);
$entrypointLookup2 = Mockery::mock(EntryPointLookupInterface::class);
Expand Down Expand Up @@ -97,7 +125,18 @@ final class ApplicationResponseHandlerTest extends TestCase

$handler = new ApplicationResponseHandler($response, $tagRenderer, $entrypointCollection, ['_default', 'other_build']);

$handler();
$handler(Mockery::mock(Application::class), $applicationResponse);
}

private function createTextApplicationResponse(): ApplicationResponse
{
$template = Mockery::mock(Template::class);

$template->shouldReceive('render')
->once()
->andReturnUndefined();

return new TextResponse($template);
}
}

Expand Down
14 changes: 2 additions & 12 deletions tests/Bridge/Nette/Application/TestPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,12 @@

namespace SixtyEightPublishers\WebpackEncoreBundle\Tests\Bridge\Nette\Application;

use Nette\Application\Request;
use Nette\Application\Response;
use Nette\Application\UI\Presenter;
use Nette\Bridges\ApplicationLatte\Template;
use Nette\Application\Responses\VoidResponse;
use function assert;

final class TestPresenter extends Presenter
{
public function run(Request $request): Response
protected function beforeRender(): void
{
$template = $this->getTemplate()->setFile(__DIR__ . '/testPresenter.latte');
assert($template instanceof Template);

$template->renderToString();

return new VoidResponse();
$this->getTemplate()->setFile(__DIR__ . '/testPresenter.latte');
}
}
6 changes: 6 additions & 0 deletions tests/bootstrap.withoutSymfonyConsole.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
$loader->missingClasses[$className] = TRUE;
}
}

$psr4Dir = $needle . '\\';

if (isset($loader->prefixDirsPsr4[$psr4Dir])) {
unset($loader->prefixDirsPsr4[$psr4Dir]);
}
}, NULL, ClassLoader::class));

return $loader;

0 comments on commit aa0f6b2

Please sign in to comment.