Asset Preloading and Resource Hints with HTTP/2 and WebLink
Symfony 原生支援(透過 WebLink 元件)管理 Link
HTTP 標頭,這是使用 HTTP/2 和現代網路瀏覽器的預載功能來改善應用程式效能的關鍵。
Link
標頭用於 HTTP/2 伺服器推送 和 W3C 的 Resource Hints,以便在用戶端甚至不知道它們需要資源(例如 CSS 和 JavaScript 檔案)之前,將資源推送給它們。WebLink 也啟用了其他適用於 HTTP 1.x 的最佳化。
- 要求瀏覽器在背景中提取或呈現另一個網頁;
- 進行早期的 DNS 查詢、TCP 握手或 TLS 協商。
需要考慮的重要事項是,所有這些 HTTP/2 功能都需要安全的 HTTPS 連線,即使在您的本機電腦上工作也是如此。主要的網路伺服器(Apache、nginx、Caddy 等)都支援這一點,但您也可以使用 Symfony 社群的 Kévin Dunglas 建立的 Symfony 的 Docker 安裝程式和執行時環境。
Installation
在使用 Symfony Flex 的應用程式中,執行以下命令以安裝 WebLink 功能,然後再使用它
1
$ composer require symfony/web-link
Preloading Assets
假設您的應用程式包含如下所示的網頁
1 2 3 4 5 6 7 8 9 10 11 12 13
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Application</title>
<link rel="stylesheet" href="/app.css">
</head>
<body>
<main role="main" class="container">
<!-- ... -->
</main>
</body>
</html>
在傳統的 HTTP 工作流程中,當載入此頁面時,瀏覽器會針對 HTML 文件發出一個請求,並針對連結的 CSS 檔案發出另一個請求。但是,使用 HTTP/2,您的應用程式可以在瀏覽器請求 CSS 檔案的內容之前,將其傳送給瀏覽器。
為了實現這一點,請更新您的範本以使用 WebLink 提供的 preload()
Twig 函數。請注意,「as」屬性是必要的,因為瀏覽器使用它來正確地優先處理資源並遵守內容安全策略
1 2 3 4 5 6 7
<head>
<!-- ... -->
{# note that you must add two <link> tags per asset:
one to link to it and the other one to tell the browser to preload it #}
<link rel="preload" href="{{ preload('/app.css', {as: 'style'}) }}" as="style">
<link rel="stylesheet" href="/app.css">
</head>
如果您重新載入頁面,則感知效能將會提高,因為當瀏覽器僅請求 HTML 頁面時,伺服器會同時回應 HTML 頁面和 CSS 檔案。
提示
當使用 AssetMapper 元件 連結到資源(例如 importmap('app')
)時,無需新增 <link rel="preload">
標籤。當 WebLink 元件可用時,importmap()
Twig 函數會自動為您新增 Link
HTTP 標頭。
注意
您可以透過使用 preload()
函數包裝資源來預載資源
1 2 3 4 5
<head>
<!-- ... -->
<link rel="preload" href="{{ preload(asset('build/app.css')) }}" as="style">
<!-- ... -->
</head>
此外,根據 Priority Hints 規範,您可以使用 importance
屬性來指示要下載的資源的優先順序
1 2 3 4 5
<head>
<!-- ... -->
<link rel="preload" href="{{ preload('/app.css', {as: 'style', importance: 'low'}) }}" as="style">
<!-- ... -->
</head>
How does it work?
WebLink 元件管理新增至回應的 Link
HTTP 標頭。當在先前的範例中使用 preload()
函數時,以下標頭已新增至回應:Link </app.css>; rel="preload"; as="style"
根據 Preload 規範,當 HTTP/2 伺服器偵測到原始 (HTTP 1.x) 回應包含此 HTTP 標頭時,它將在相同的 HTTP/2 連線中自動觸發相關檔案的推送。
熱門的代理服務和 CDN,包括 Cloudflare、Fastly 和 Akamai 也利用了此功能。這表示您可以將資源推送給用戶端,並立即在生產環境中改善應用程式的效能。
如果您想要防止推送,但讓瀏覽器透過發出早期的個別 HTTP 請求來預載資源,請使用 nopush
選項
1 2 3 4 5
<head>
<!-- ... -->
<link rel="preload" href="{{ preload('/app.css', {as: 'style', nopush: true}) }}" as="style">
<!-- ... -->
</head>
Resource Hints
Resource Hints 供應用程式使用,以協助瀏覽器決定應先下載、預先處理或連線哪些資源。
WebLink 元件提供以下 Twig 函數來傳送這些提示
dns_prefetch()
:「指示將用於提取所需資源的來源(例如https://foo.cloudfront.net
),以及使用者代理程式應盡可能早地解析」。preconnect()
:「指示將用於提取所需資源的來源(例如https://127.0.0.1
)。啟動早期連線(包括 DNS 查詢、TCP 握手和選用的 TLS 協商)可讓使用者代理程式掩蓋建立連線的高延遲成本」。prefetch()
:「識別下一次導覽可能需要的資源,以及使用者代理程式應提取的資源,以便使用者代理程式在未來請求資源時能夠提供更快速的回應」。prerender()
:「識別下一次導覽可能需要的資源,以及使用者代理程式應提取和執行的資源,以便使用者代理程式稍後在請求資源時能夠提供更快速的回應」。
該元件也支援傳送與效能無關的 HTTP 連結,以及任何實作 PSR-13 標準的連結。例如,HTML 規範中定義的任何連結
1 2 3 4 5 6
<head>
<!-- ... -->
<link rel="alternate" href="{{ link('/index.jsonld', 'alternate') }}">
<link rel="preload" href="{{ preload('/app.css', {as: 'style', nopush: true}) }}" as="style">
<!-- ... -->
</head>
先前的程式碼片段將導致此 HTTP 標頭傳送至用戶端:Link: </index.jsonld>; rel="alternate",</app.css>; rel="preload"; nopush
您也可以直接從控制器和服務將連結新增至 HTTP 回應
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Component\WebLink\Link;
class BlogController extends AbstractController
{
public function index(Request $request): Response
{
// using the addLink() shortcut provided by AbstractController
$this->addLink($request, (new Link('preload', '/app.css'))->withAttribute('as', 'style'));
// alternative if you don't want to use the addLink() shortcut
$linkProvider = $request->attributes->get('_links', new GenericLinkProvider());
$request->attributes->set('_links', $linkProvider->withLink(
(new Link('preload', '/app.css'))->withAttribute('as', 'style')
));
return $this->render('...');
}
}