diff --git a/framework/core/src/Extend/Frontend.php b/framework/core/src/Extend/Frontend.php
index 9484a1213..ff7de2021 100644
--- a/framework/core/src/Extend/Frontend.php
+++ b/framework/core/src/Extend/Frontend.php
@@ -16,6 +16,7 @@ use Flarum\Foundation\ContainerUtil;
use Flarum\Foundation\Event\ClearingCache;
use Flarum\Frontend\Assets;
use Flarum\Frontend\Compiler\Source\SourceCollector;
+use Flarum\Frontend\Document;
use Flarum\Frontend\Frontend as ActualFrontend;
use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\RouteCollection;
@@ -33,6 +34,7 @@ class Frontend implements ExtenderInterface
private $routes = [];
private $removedRoutes = [];
private $content = [];
+ private $preloadArrs = [];
/**
* @param string $frontend: The name of the frontend.
@@ -124,11 +126,45 @@ class Frontend implements ExtenderInterface
return $this;
}
+ /**
+ * Adds multiple asset preloads.
+ *
+ * The parameter should be an array of preload arrays, or a callable that returns this.
+ *
+ * A preload array must contain keys that pertain to the `` tag.
+ *
+ * For example, the following will add preload tags for a script and font file:
+ * ```
+ * $frontend->preloads([
+ * [
+ * 'href' => '/assets/my-script.js',
+ * 'as' => 'script',
+ * ],
+ * [
+ * 'href' => '/assets/fonts/my-font.woff2',
+ * 'as' => 'font',
+ * 'type' => 'font/woff2',
+ * 'crossorigin' => ''
+ * ]
+ * ]);
+ * ```
+ *
+ * @param callable|array $preloads
+ * @return self
+ */
+ public function preloads($preloads): self
+ {
+ $this->preloadArrs[] = $preloads;
+
+ return $this;
+ }
+
public function extend(Container $container, Extension $extension = null)
{
$this->registerAssets($container, $this->getModuleName($extension));
$this->registerRoutes($container);
$this->registerContent($container);
+ $this->registerPreloads($container);
}
private function registerAssets(Container $container, string $moduleName): void
@@ -236,6 +272,25 @@ class Frontend implements ExtenderInterface
);
}
+ private function registerPreloads(Container $container): void
+ {
+ if (empty($this->preloadArrs)) {
+ return;
+ }
+
+ $container->resolving(
+ "flarum.frontend.$this->frontend",
+ function (ActualFrontend $frontend, Container $container) {
+ $frontend->content(function (Document $document) use ($container) {
+ foreach ($this->preloadArrs as $preloadArr) {
+ $preloads = is_callable($preloadArr) ? ContainerUtil::wrapCallback($preloadArr, $container)($document) : $preloadArr;
+ $document->preloads = array_merge($document->preloads, $preloads);
+ }
+ });
+ }
+ );
+ }
+
private function getModuleName(?Extension $extension): string
{
return $extension ? $extension->getId() : 'site-custom';
diff --git a/framework/core/src/Frontend/Document.php b/framework/core/src/Frontend/Document.php
index f938041c5..d08ab658b 100644
--- a/framework/core/src/Frontend/Document.php
+++ b/framework/core/src/Frontend/Document.php
@@ -122,6 +122,28 @@ class Document implements Renderable
*/
public $css = [];
+ /**
+ * An array of preloaded assets.
+ *
+ * Each array item should be an array containing keys that pertain to the
+ * `` tag.
+ *
+ * For example, the following will add a preload tag for a FontAwesome font file:
+ * ```
+ * $this->preloads[] = [
+ * 'href' => '/assets/fonts/fa-solid-900.woff2',
+ * 'as' => 'font',
+ * 'type' => 'font/woff2',
+ * 'crossorigin' => ''
+ * ];
+ * ```
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload
+ *
+ * @var array
+ */
+ public $preloads = [];
+
/**
* @var Factory
*/
@@ -203,6 +225,19 @@ class Document implements Renderable
return $this->view->make($this->contentView)->with('content', $this->content);
}
+ protected function makePreloads(): array
+ {
+ return array_map(function ($preload) {
+ $attributes = '';
+
+ foreach ($preload as $key => $value) {
+ $attributes .= " $key=\"".e($value).'"';
+ }
+
+ return "";
+ }, $this->preloads);
+ }
+
/**
* @return string
*/
@@ -216,6 +251,8 @@ class Document implements Renderable
$head[] = '';
}
+ $head = array_merge($head, $this->makePreloads());
+
$head = array_merge($head, array_map(function ($content, $name) {
return '';
}, $this->meta, array_keys($this->meta)));
diff --git a/framework/core/src/Frontend/FrontendServiceProvider.php b/framework/core/src/Frontend/FrontendServiceProvider.php
index cb91a5076..f8cde1689 100644
--- a/framework/core/src/Frontend/FrontendServiceProvider.php
+++ b/framework/core/src/Frontend/FrontendServiceProvider.php
@@ -54,9 +54,58 @@ class FrontendServiceProvider extends AbstractServiceProvider
$frontend->content($container->make(Content\CorePayload::class));
$frontend->content($container->make(Content\Meta::class));
+ $frontend->content(function (Document $document) use ($container) {
+ $default_preloads = $container->make('flarum.frontend.default_preloads');
+
+ // Add preloads for base CSS and JS assets. Extensions should add their own via the extender.
+ $js_preloads = [];
+ $css_preloads = [];
+
+ foreach ($document->css as $url) {
+ $css_preloads[] = [
+ 'href' => $url,
+ 'as' => 'style'
+ ];
+ }
+ foreach ($document->js as $url) {
+ $css_preloads[] = [
+ 'href' => $url,
+ 'as' => 'script'
+ ];
+ }
+
+ $document->preloads = array_merge(
+ $css_preloads,
+ $js_preloads,
+ $default_preloads,
+ $document->preloads,
+ );
+ });
+
return $frontend;
};
});
+
+ $this->container->singleton(
+ 'flarum.frontend.default_preloads',
+ function (Container $container) {
+ $filesystem = $container->make('filesystem')->disk('flarum-assets');
+
+ return [
+ [
+ 'href' => $filesystem->url('fonts/fa-solid-900.woff2'),
+ 'as' => 'font',
+ 'type' => 'font/woff2',
+ 'crossorigin' => ''
+ ], [
+ 'href' => $filesystem->url('fonts/fa-regular-400.woff2'),
+ 'as' => 'font',
+ 'type' => 'font/woff2',
+ 'crossorigin' => ''
+ ]
+ ];
+ }
+ );
}
/**
diff --git a/framework/core/tests/integration/extenders/FrontendPreloadTest.php b/framework/core/tests/integration/extenders/FrontendPreloadTest.php
new file mode 100644
index 000000000..050cd393f
--- /dev/null
+++ b/framework/core/tests/integration/extenders/FrontendPreloadTest.php
@@ -0,0 +1,93 @@
+send(
+ $this->request('GET', '/')
+ );
+
+ $filesystem = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets');
+
+ $urls = [
+ $filesystem->url('fonts/fa-solid-900.woff2'),
+ $filesystem->url('fonts/fa-regular-400.woff2'),
+ ];
+
+ $body = $response->getBody()->getContents();
+
+ foreach ($urls as $url) {
+ $this->assertStringContainsString("", $body);
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function preloads_can_be_added()
+ {
+ $urls = $this->customPreloadUrls;
+
+ $this->extend(
+ (new Extend\Frontend('forum'))
+ ->preloads(
+ array_map(function ($url) {
+ return ['href' => $url];
+ }, $urls)
+ )
+ );
+
+ $response = $this->send(
+ $this->request('GET', '/')
+ );
+ $body = $response->getBody()->getContents();
+
+ foreach ($urls as $url) {
+ $this->assertStringContainsString("", $body);
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function preloads_can_be_added_via_callable()
+ {
+ $urls = $this->customPreloadUrls;
+
+ $this->extend(
+ (new Extend\Frontend('forum'))
+ ->preloads(function () use ($urls) {
+ return array_map(function ($url) {
+ return ['href' => $url];
+ }, $urls);
+ })
+ );
+
+ $response = $this->send(
+ $this->request('GET', '/')
+ );
+ $body = $response->getBody()->getContents();
+
+ foreach ($urls as $url) {
+ $this->assertStringContainsString("", $body);
+ }
+ }
+}