Skip to content

Commit

Permalink
support (module)preloading with Vite
Browse files Browse the repository at this point in the history
  • Loading branch information
timacdonald committed Sep 14, 2022
1 parent 964c2b4 commit 73ada6b
Show file tree
Hide file tree
Showing 5 changed files with 883 additions and 18 deletions.
125 changes: 124 additions & 1 deletion src/Illuminate/Foundation/Vite.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,37 @@ class Vite implements Htmlable
*/
protected $styleTagAttributesResolvers = [];

/**
* The preload tag attributes resolvers.
*
* @var array
*/
protected $preloadTagAttributesResolvers = [];

/**
* The preloaded assets.
*
* @var array
*/
protected $preloadedAssets = [];

/**
* The cached manifest files.
*
* @var array
*/
protected static $manifests = [];

/**
* Get the preloaded assets.
*
* @var array
*/
public function preloadedAssets()
{
return $this->preloadedAssets;
}

/**
* Get the Content Security Policy nonce applied to all generated tags.
*
Expand Down Expand Up @@ -183,6 +207,23 @@ public function useStyleTagAttributes($attributes)
return $this;
}

/**
* Use the given callback to resolve attributes for preload tags.
*
* @param (callable(string, string, ?array, ?array): array)|array $attributes
* @return $this
*/
public function usePreloadTagAttributes($attributes)
{
if (! is_callable($attributes)) {
$attributes = fn () => $attributes;
}

$this->preloadTagAttributesResolvers[] = $attributes;

return $this;
}

/**
* Generate Vite tags for an entrypoint.
*
Expand All @@ -209,14 +250,36 @@ public function __invoke($entrypoints, $buildDirectory = null)
$manifest = $this->manifest($buildDirectory);

$tags = collect();
$preloads = collect();

foreach ($entrypoints as $entrypoint) {
$chunk = $this->chunk($manifest, $entrypoint);

$preloads->push($this->makePreloadTagForChunk(
$chunk['src'],
$this->assetPath("{$buildDirectory}/{$chunk['file']}"),
$chunk,
$manifest
));

foreach ($chunk['imports'] ?? [] as $import) {
$preloads->push($this->makePreloadTagForChunk(
$import,
$this->assetPath("{$buildDirectory}/{$manifest[$import]['file']}"),
$manifest[$import],
$manifest
));

foreach ($manifest[$import]['css'] ?? [] as $css) {
$partialManifest = Collection::make($manifest)->where('file', $css);

$preloads->push($this->makePreloadTagForChunk(
$partialManifest->keys()->first(),
$this->assetPath("{$buildDirectory}/{$css}"),
$partialManifest->first(),
$manifest
));

$tags->push($this->makeTagForChunk(
$partialManifest->keys()->first(),
$this->assetPath("{$buildDirectory}/{$css}"),
Expand All @@ -236,6 +299,13 @@ public function __invoke($entrypoints, $buildDirectory = null)
foreach ($chunk['css'] ?? [] as $css) {
$partialManifest = Collection::make($manifest)->where('file', $css);

$preloads->push($this->makePreloadTagForChunk(
$partialManifest->keys()->first(),
$this->assetPath("{$buildDirectory}/{$css}"),
$partialManifest->first(),
$manifest
));

$tags->push($this->makeTagForChunk(
$partialManifest->keys()->first(),
$this->assetPath("{$buildDirectory}/{$css}"),
Expand All @@ -247,7 +317,9 @@ public function __invoke($entrypoints, $buildDirectory = null)

[$stylesheets, $scripts] = $tags->partition(fn ($tag) => str_starts_with($tag, '<link'));

return new HtmlString($stylesheets->join('').$scripts->join(''));
$preloads = $preloads->sortByDesc(fn ($link) => str_contains($link, 'rel="preload"'));

return new HtmlString($preloads->join('').$stylesheets->join('').$scripts->join(''));
}

/**
Expand Down Expand Up @@ -283,6 +355,26 @@ protected function makeTagForChunk($src, $url, $chunk, $manifest)
);
}

/**
* Make preload tag for the given chunk.
*
* @param string $src
* @param string $url
* @param array $chunk
* @param array $manifest
* @return string|null
*/
protected function makePreloadTagForChunk($src, $url, $chunk, $manifest)
{
$attributes = $this->resolvePreloadTagAttributes($src, $url, $chunk, $manifest);

$this->preloadedAssets[$url] = $this->parseAttributes(
Collection::make($attributes)->forget('href')->all()
);

return '<link '.implode(' ', $this->parseAttributes($attributes)).' />';
}

/**
* Resolve the attributes for the chunks generated script tag.
*
Expand Down Expand Up @@ -327,6 +419,37 @@ protected function resolveStylesheetTagAttributes($src, $url, $chunk, $manifest)
return $attributes;
}

/**
* Resolve the attributes for the chunks generated preload tag.
*
* @param string $src
* @param string $url
* @param array $chunk
* @param array $manifest
* @return array
*/
protected function resolvePreloadTagAttributes($src, $url, $chunk, $manifest)
{
$attributes = $this->isCssPath($url) ? [
'rel' => 'preload',
'as' => 'style',
'href' => $url,
] : [
'rel' => 'modulepreload',
'href' => $url,
];

$attributes = $this->integrityKey !== false
? array_merge($attributes, ['integrity' => $chunk[$this->integrityKey] ?? false])
: $attributes;

foreach ($this->preloadTagAttributesResolvers as $resolver) {
$attributes = array_merge($attributes, $resolver($src, $url, $chunk, $manifest));
}

return $attributes;
}

/**
* Generate an appropriate tag for the given URL in HMR mode.
*
Expand Down
28 changes: 28 additions & 0 deletions src/Illuminate/Http/Middleware/VitePreloading.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Illuminate\Http\Middleware;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Vite;

class VitePreloading
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
return tap($next($request), function ($response) {
if (Vite::preloadedAssets() !== []) {
$response->header('Link', Collection::make(Vite::preloadedAssets())
->map(fn ($attributes, $url) => "<{$url}>; ".implode('; ', $attributes))
->sortByDesc(fn ($link) => str_contains($link, 'rel="preload"'))
->join(', '));
}
});
}
}
Loading

0 comments on commit 73ada6b

Please sign in to comment.