Skip to content

Commit f3fc8ce

Browse files
committed
preload modules, do not preload defer and async scripts
1 parent 1fc4c11 commit f3fc8ce

File tree

9 files changed

+159
-21
lines changed

9 files changed

+159
-21
lines changed

.github/workflows/analyse.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: PHPStan
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
name: analyse
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v2
17+
18+
- name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: 8.2
22+
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
23+
coverage: none
24+
25+
- name: Install dependencies
26+
run: composer install --no-interaction
27+
28+
- name: Analyse
29+
run: composer run analyse

.github/workflows/pint.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Pint
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
name: pint
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v2
17+
18+
- name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: 8.2
22+
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
23+
coverage: none
24+
25+
- name: Install dependencies
26+
run: composer install --no-interaction
27+
28+
- name: Analyse
29+
run: composer run style

.github/workflows/test.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
name: tests
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v2
17+
18+
- name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: 8.2
22+
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
23+
coverage: none
24+
25+
- name: Install dependencies
26+
run: composer install --no-interaction
27+
28+
- name: Analyse
29+
run: composer run test

README.md

+51-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ $ composer require justbetter/laravel-http3earlyhints
1515
```
1616

1717
Next you must add the `\JustBetter\Http3EarlyHints\Middleware\AddHttp3EarlyHints`-middleware to the kernel. Adding it to the web group is recommeneded as API's do not have assets to push.
18+
### Laravel <11
1819
```php
1920
// app/Http/Kernel.php
2021

@@ -29,6 +30,23 @@ protected $middlewareGroups = [
2930
];
3031
```
3132

33+
### Laravel >=11
34+
```php
35+
// bootstrap/app.php
36+
37+
...
38+
->withMiddleware(function (Middleware $middleware) {
39+
...
40+
$middleware->appendToGroup('web', [
41+
\JustBetter\Http3EarlyHints\Middleware\AddHttp3EarlyHints::class,
42+
]);
43+
// Or
44+
// $middleware->append(\JustBetter\Http3EarlyHints\Middleware\AddHttp3EarlyHints::class);
45+
...
46+
})
47+
...
48+
```
49+
3250
## Publish config
3351

3452
```php
@@ -43,11 +61,42 @@ default behaviour is adding the link headers to the 200 response which e.g. [Clo
4361
When you route a request through the `AddHttp3EarlyHints` middleware, the response is scanned for any `link`, `script` or `img` tags that could benefit from being loaded using Early Hints.
4462
These assets will be added to the `Link` header before sending the response to the client. Easy!
4563

46-
**Note:** To push an image asset, it must have one of the following extensions: `bmp`, `gif`, `jpg`, `jpeg`, `png`, `tiff` or `svg` and not have `loading="lazy"`
64+
**Note:** To push an image asset, it must have one of the following extensions: `bmp`, `gif`, `jpg`, `jpeg`, `png`, `svg`, `tiff` or `webp` and not have `loading="lazy"`
4765

4866
### Advanced usage
4967

50-
If the automatic detection isn't enough for you, you can listen for GenerateEarlyHints events, and manually add new links.
68+
If the automatic detection isn't enough for you, you can listen for the `GenerateEarlyHints` event, and manually add/remove/change new links.
69+
70+
#### Detailed default behaviour
71+
72+
The information on [usage](#usage) is simplified, there are many checks done to make sure we don't preload the wrong things.
73+
74+
Early hints only support rel=preconnect and rel=preload [source](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103#browser_compatibility)
75+
76+
We automatically transform any rel that is not `preconnect` or `preload` into `preload`, so your `<link rel="modulepreload" href="app.js">` will get preloaded with early hints. And get more detailed information once your server starts sending it's response.
77+
78+
##### Link
79+
80+
Any link elements which do **not** have rel=
81+
- icon
82+
- canonical
83+
- manifest
84+
- alternative
85+
86+
##### Script
87+
88+
Script tags will automatically get preloaded **if** it does not have an `async` or `defer` attribute attached to it.
89+
90+
##### Img
91+
92+
Img tags will automatically get preloaded when it does not have `loading="lazy"` and it does not exist within a picture tag.
93+
94+
If it is within a picture tag we may be dealing with mutliple `srcset`s or `type`s, and thus cannot determine which file the browser will need.
95+
So we will not preload these images.
96+
97+
##### Object
98+
99+
If your html object tag contains `data=""` it will preload it.
51100

52101
## Testing
53102

src/Data/LinkHeaders.php

+11-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class LinkHeaders
1515

1616
public function __construct(?EvolvableLinkProviderInterface $linkProvider = null)
1717
{
18-
$this->linkProvider = $linkProvider ?? new GenericLinkProvider();
18+
$this->linkProvider = $linkProvider ?? new GenericLinkProvider;
1919
}
2020

2121
public function getLinkProvider(): EvolvableLinkProviderInterface
@@ -30,18 +30,20 @@ public function setLinkProvider(EvolvableLinkProviderInterface $linkProvider): s
3030
return $this;
3131
}
3232

33-
public function addLink(EvolvableLinkInterface|string|array $uri, string|array|null $rel = null, null|array $attributes = []): static
33+
public function addLink(EvolvableLinkInterface|string|array $uri, string|array|null $rel = null, ?array $attributes = []): static
3434
{
3535
if (is_array($uri)) {
3636
foreach ($uri as $data) {
3737
$data = Arr::Wrap($data);
3838
$this->addLink(...$data);
3939
}
40+
4041
return $this;
4142
}
4243

4344
if ($uri instanceof EvolvableLinkInterface) {
4445
$this->setLinkProvider($this->getLinkProvider()->withLink($uri));
46+
4547
return $this;
4648
}
4749

@@ -67,21 +69,22 @@ public function addLink(EvolvableLinkInterface|string|array $uri, string|array|n
6769
return $this;
6870
}
6971

70-
public function addFromString(string $link) : static
72+
public function addFromString(string $link): static
7173
{
7274
$links = explode(',', trim($link));
7375
foreach ($links as $link) {
7476
$parts = explode('; ', trim($link));
7577
$uri = trim(array_shift($parts), '<>');
7678
$rel = null;
7779
$attributes = [];
78-
foreach($parts as $part) {
80+
foreach ($parts as $part) {
7981
preg_match('/(?<key>[^=]+)(?:="?(?<value>.*)"?)?/', trim($part), $matches);
8082
$key = $matches['key'];
8183
$value = $matches['value'] ?? null;
8284

83-
if($key === 'rel') {
85+
if ($key === 'rel') {
8486
$rel = $value;
87+
8588
continue;
8689
}
8790
$attributes[$key] = $value ?? true;
@@ -99,8 +102,9 @@ public function makeUnique()
99102

100103
foreach ($this->getLinkProvider()->getLinks() as $link) {
101104
$hash = md5(serialize($link));
102-
if (!in_array($hash, $handledHashes)) {
105+
if (! in_array($hash, $handledHashes)) {
103106
$handledHashes[] = $hash;
107+
104108
continue;
105109
}
106110

@@ -135,7 +139,7 @@ public static function linkToString(LinkInterface $link)
135139
continue;
136140
}
137141

138-
if (!\is_bool($value)) {
142+
if (! \is_bool($value)) {
139143
$attributes[] = sprintf('%s="%s"', $key, $value);
140144

141145
continue;

src/Listeners/AddDefaultHeaders.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class AddDefaultHeaders
99
{
1010
public function handle(GenerateEarlyHints $event)
1111
{
12-
foreach(config('http3earlyhints.default_headers', []) as $header) {
12+
foreach (config('http3earlyhints.default_headers', []) as $header) {
1313
$event->linkHeaders->addFromString($header);
1414
}
1515
}

src/Listeners/AddFromBody.php

+3-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ public function handle(GenerateEarlyHints $event)
4545
$event->linkHeaders->addLink($headers->toArray());
4646
}
4747

48-
4948
/**
5049
* Get the DomCrawler instance.
5150
*/
@@ -61,7 +60,7 @@ protected function fetchLinkableNodes(Response $response): Collection
6160
{
6261
$crawler = $this->getCrawler($response);
6362

64-
return collect($crawler->filter('link:not([rel*="icon"]):not([rel="canonical"]):not([rel="manifest"]):not([rel="alternate"]), script[src], *:not(picture)>img[src]:not([loading="lazy"]), object[data]')->extract(['src', 'href', 'data', 'rel', 'type']));
63+
return collect($crawler->filter('link:not([rel*="icon"]):not([rel="canonical"]):not([rel="manifest"]):not([rel="alternate"]), script[src]:not([defer]):not([async]), *:not(picture)>img[src]:not([loading="lazy"]), object[data]')->extract(['src', 'href', 'data', 'rel', 'type']));
6564
}
6665

6766
/**
@@ -84,7 +83,7 @@ private function buildLinkHeader(string $url, ?string $rel = 'preload'): ?Link
8483
'.WOFF2' => 'font',
8584
];
8685

87-
if (!$url) {
86+
if (! $url) {
8887
return null;
8988
}
9089

@@ -97,7 +96,7 @@ private function buildLinkHeader(string $url, ?string $rel = 'preload'): ?Link
9796
$url = rtrim($basePath.ltrim($url, $basePath), '/');
9897
}
9998

100-
if (! in_array($rel, ['preload', 'modulepreload', 'preconnect'])) {
99+
if (! in_array($rel, ['preload', 'preconnect'])) {
101100
$rel = 'preload';
102101
}
103102

src/Middleware/AddHttp3EarlyHints.php

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Illuminate\Http\Response;
88
use Illuminate\Support\Arr;
99
use Illuminate\Support\Facades\Cache;
10-
use Illuminate\Support\Facades\Event;
1110
use Illuminate\Support\Str;
1211
use JustBetter\Http3EarlyHints\Data\LinkHeaders;
1312
use JustBetter\Http3EarlyHints\Events\GenerateEarlyHints;
@@ -48,7 +47,7 @@ public function handle(Request $request, Closure $next, ?int $sizeLimit = null):
4847
}
4948

5049
if (config('http3earlyhints.set_103')) {
51-
$response = new Response();
50+
$response = new Response;
5251
$this->addLinkHeaders($response, $linkHeaders);
5352
$response->sendHeaders(103);
5453

@@ -104,7 +103,7 @@ public function handleGeneratingLinkHeaders(Request $request, SymfonyResponse $r
104103

105104
protected function generateLinkHeaders(Request $request, SymfonyResponse $response, ?int $sizeLimit = null): LinkHeaders
106105
{
107-
$this->linkHeaders = new LinkHeaders();
106+
$this->linkHeaders = new LinkHeaders;
108107
GenerateEarlyHints::dispatch($this->linkHeaders, $request, $response);
109108

110109
$this->linkHeaders->makeUnique();
@@ -114,7 +113,7 @@ protected function generateLinkHeaders(Request $request, SymfonyResponse $respon
114113

115114
while (strlen($headersText) > $sizeLimit) {
116115
$this->linkHeaders->setLinkProvider($this->linkHeaders->getLinkProvider()->withOutLink(Arr::last($this->linkHeaders->getLinkProvider()->getLinks())));
117-
$headersText = $this->linkHeaders->__toString();
116+
$headersText = $this->linkHeaders->__toString();
118117
}
119118

120119
return $this->linkHeaders;
@@ -126,7 +125,7 @@ protected function generateLinkHeaders(Request $request, SymfonyResponse $respon
126125
private function addLinkHeaders(SymfonyResponse $response, LinkHeaders $linkHeaders): SymfonyResponse
127126
{
128127
$link = $linkHeaders->__toString();
129-
if (! $link || !$response instanceof Response) {
128+
if (! $link || ! $response instanceof Response) {
130129
return $response;
131130
}
132131
if ($response->headers->get('Link')) {

tests/AddHttp3EarlyHintsTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class AddHttp3EarlyHintsTest extends TestCase
1212

1313
public function setUp(): void
1414
{
15-
$this->middleware = new AddHttp3EarlyHints();
15+
$this->middleware = new AddHttp3EarlyHints;
1616
parent::setUp();
1717
}
1818

@@ -27,7 +27,7 @@ public function it_will_not_exceed_size_limit()
2727
$request = $this->getNewRequest();
2828

2929
$limit = 75;
30-
$response = $this->middleware->handle($request, $this->getNext('pageWithCssAndJs'), $limit, []);
30+
$response = $this->middleware->handle($request, $this->getNext('pageWithCssAndJs'), $limit);
3131

3232
$this->assertTrue($this->isServerPushResponse($response));
3333
$this->assertTrue(strlen($response->headers->get('link')) <= $limit);

0 commit comments

Comments
 (0)