Skip to content

Commit

Permalink
Broadcast page cache update
Browse files Browse the repository at this point in the history
  • Loading branch information
Spomky committed Mar 9, 2024
1 parent 34a0c42 commit 9f89a46
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 21 deletions.
10 changes: 10 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,16 @@ parameters:
count: 1
path: src/Resources/config/definition/web_client.php

-
message: "#^Parameter \\#6 \\$options of static method SpomkyLabs\\\\PwaBundle\\\\Service\\\\CacheStrategy\\:\\:create\\(\\) expects array\\{maxTimeout\\?\\: int, maxAge\\?\\: int, maxEntries\\?\\: int, warmUrls\\?\\: array\\<string\\>, plugins\\?\\: array\\<string\\>\\}, array\\{maxEntries\\: int\\<0, max\\>, maxAge\\: int, warmUrls\\: mixed\\} given\\.$#"
count: 1
path: src/Service/Rule/AssetCache.php

-
message: "#^Parameter \\#6 \\$options of static method SpomkyLabs\\\\PwaBundle\\\\Service\\\\CacheStrategy\\:\\:create\\(\\) expects array\\{maxTimeout\\?\\: int, maxAge\\?\\: int, maxEntries\\?\\: int, warmUrls\\?\\: array\\<string\\>, plugins\\?\\: array\\<string\\>\\}, array\\{maxTimeout\\: int, plugins\\: array\\{0\\: 'CacheableResponsePl…', 1\\?\\: 'BroadcastUpdatePlug…'\\}, warmUrls\\: mixed\\} given\\.$#"
count: 1
path: src/Service/Rule/PageCache.php

-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\SpomkyLabsPwaBundle\\:\\:loadExtension\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
count: 1
Expand Down
5 changes: 3 additions & 2 deletions src/Command/ListCacheStrategiesCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
use Symfony\Component\Yaml\Yaml;
use function assert;

#[AsCommand(name: 'pwa:cache:list-strategies', description: 'List the available cache strategies',)]
final class ListCacheStrategiesCommand extends Command
{
/**
* @param iterable<HasCacheStrategies> $services
*/
public function __construct(
#[TaggedIterator('spomky_labs_pwa.cache_strategy')]
private readonly iterable $services,
Expand All @@ -32,7 +34,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$table = $io->createTable();
$table->setHeaders(['Name', 'Strategy', 'URL pattern', 'Enabled', 'Workbox?', 'Options']);
foreach ($this->services as $service) {
assert($service instanceof HasCacheStrategies);
$strategies = $service->getCacheStrategies();
foreach ($strategies as $strategy) {
$table->addRow([
Expand Down
3 changes: 3 additions & 0 deletions src/Dto/AssetCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ final class AssetCache
public string $cacheName = 'assets';

public string $regex = '/\.(css|js|json|xml|txt|map|ico|png|jpe?g|gif|svg|webp|bmp)$/';

#[SerializedName('max_age')]
public int $maxAge = 60 * 60 * 24 * 365;
}
10 changes: 10 additions & 0 deletions src/Dto/PageCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ final class PageCache
#[SerializedName('network_timeout')]
public int $networkTimeout = 3;

public string $strategy = 'networkFirst';

public bool $broadcast = false;

/**
* @var array<string>
*/
#[SerializedName('broadcast_headers')]
public array $broadcastHeaders = ['Content-Type', 'ETag', 'Last-Modified'];

/**
* @var array<Url>
*/
Expand Down
25 changes: 25 additions & 0 deletions src/Resources/config/definition/service_worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@
->info('The regex to match the assets.')
->example('/\.(css|js|json|xml|txt|map|ico|png|jpe?g|gif|svg|webp|bmp)$/')
->end()
->scalarNode('max_age')
->defaultValue(60 * 60 * 24 * 365)
->info('The maximum number of seconds before the asset cache is invalidated.')
->example([60 * 60 * 24 * 365, 60 * 60 * 24 * 30, 60 * 60 * 24 * 7])
->end()
->end()
->end()
->arrayNode('font_cache')
Expand Down Expand Up @@ -290,6 +295,26 @@
)
->example([1, 2, 5])
->end()
->scalarNode('strategy')
->defaultValue('networkFirst')
->info(
'The caching strategy. Only "networkFirst" and "staleWhileRevalidate" are supported.'
)
->example(['networkFirst', 'staleWhileRevalidate'])
->end()
->booleanNode('broadcast')
->defaultFalse()
->info(
'Whether to broadcast the cache update events. Only supported with "staleWhileRevalidate" strategy.'
)
->end()
->arrayNode('broadcast_headers')
->treatNullLike(['Content-Length', 'ETag', 'Last-Modified'])
->treatFalseLike(['Content-Length', 'ETag', 'Last-Modified'])
->treatTrueLike(['Content-Length', 'ETag', 'Last-Modified'])
->defaultValue(['Content-Length', 'ETag', 'Last-Modified'])
->scalarPrototype()->end()
->end()
->arrayNode('urls')
->treatNullLike([])
->treatFalseLike([])
Expand Down
6 changes: 3 additions & 3 deletions src/Service/CacheStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@ public function __construct(
public bool $enabled,
public bool $requireWorkbox,
/**
* @var array{maxTimeout?: int, maxAge?: int, maxEntries?: int}
* @var array{maxTimeout?: int, maxAge?: int, maxEntries?: int, warmUrls?: string[], plugins?: string[]}
*/
public array $options = []
) {
}

/**
* @param array{maxTimeout?: int, maxAge?: int, maxEntries?: int} $options
* @param array{maxTimeout?: int, maxAge?: int, maxEntries?: int, warmUrls?: string[], plugins?: string[]} $options
*/
public static function create(
string $name,
string $strategy,
string $urlPattern,
bool $enabled,
bool $requireWorkbox,
array $options = []
array $options = [],
): self {
return new self($name, $strategy, $urlPattern, $enabled, $requireWorkbox, $options);
}
Expand Down
29 changes: 20 additions & 9 deletions src/Service/Rule/AssetCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,7 @@ public function process(string $body): string
if ($this->workbox->assetCache->enabled === false) {
return $body;
}
$assets = [];
foreach ($this->assetMapper->allAssets() as $asset) {
if (preg_match($this->workbox->assetCache->regex, $asset->sourcePath) === 1) {
$assets[] = $asset->publicPath;
}
}
$assets = $this->getAssets();
$assetUrls = $this->serializer->serialize($assets, 'json', $this->jsonOptions);
$assetUrlsLength = count($assets) * 2;

Expand All @@ -78,7 +73,7 @@ public function process(string $body): string
new workbox.cacheableResponse.CacheableResponsePlugin({statuses: [0, 200]}),
new workbox.expiration.ExpirationPlugin({
maxEntries: {$assetUrlsLength},
maxAgeSeconds: 365 * 24 * 60 * 60,
maxAgeSeconds: {$this->workbox->assetCache->maxAge},
}),
],
});
Expand All @@ -104,6 +99,7 @@ public function process(string $body): string

public function getCacheStrategies(): array
{
$urls = json_decode($this->serializer->serialize($this->getAssets(), 'json', $this->jsonOptions), true);
return [
CacheStrategy::create(
$this->workbox->assetCache->cacheName,
Expand All @@ -112,10 +108,25 @@ public function getCacheStrategies(): array
$this->workbox->enabled && $this->workbox->assetCache->enabled,
true,
[
'maxEntries' => -1,
'maxAge' => 365 * 24 * 60 * 60,
'maxEntries' => count($this->getAssets()) * 2,
'maxAge' => $this->workbox->assetCache->maxAge,
'warmUrls' => $urls,
],
),
];
}

/**
* @return array<string>
*/
private function getAssets(): array
{
$assets = [];
foreach ($this->assetMapper->allAssets() as $asset) {
if (preg_match($this->workbox->assetCache->regex, $asset->sourcePath) === 1) {
$assets[] = $asset->publicPath;
}
}
return $assets;
}
}
6 changes: 5 additions & 1 deletion src/Service/Rule/BackgroundSync.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function getCacheStrategies(): array
$strategies = [];
foreach ($this->workbox->backgroundSync as $sync) {
$strategies[] = CacheStrategy::create(
'backgroundSync',
'---',
CacheStrategy::STRATEGY_NETWORK_ONLY,
$sync->regex,
$this->workbox->enabled,
Expand All @@ -78,6 +78,10 @@ public function getCacheStrategies(): array
'maxTimeout' => 0,
'maxAge' => 0,
'maxEntries' => 0,
'plugins' => [
sprintf('backgroundSync: "%s"', $sync->queueName),
sprintf('broadcastChannel: "%s"', $sync->broadcastChannel ?? '---'),
],
]
);
}
Expand Down
55 changes: 49 additions & 6 deletions src/Service/Rule/PageCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,43 @@ public function process(string $body): string
return $body;
}
$routes = $this->serializer->serialize($this->workbox->pageCache->urls, 'json', $this->jsonOptions);
$strategy = match ($this->workbox->pageCache->strategy) {
'staleWhileRevalidate' => 'StaleWhileRevalidate',
default => 'NetworkFirst',
};
$broadcastHeaders = json_encode(
$this->workbox->pageCache->broadcastHeaders === [] ? [
'Content-Type',
'ETag',
'Last-Modified',
] : $this->workbox->pageCache->broadcastHeaders,
JSON_THROW_ON_ERROR,
512
);
$broadcastUpdate = ($strategy === 'StaleWhileRevalidate' && $this->workbox->pageCache->broadcast === true) ? sprintf(
',new workbox.broadcastUpdate.BroadcastUpdatePlugin({headersToCheck: %s})',
$broadcastHeaders
) : '';

$declaration = <<<PAGE_CACHE_RULE_STRATEGY
workbox.recipes.pageCache({
cacheName: '{$this->workbox->pageCache->cacheName}',
networkTimeoutSeconds: {$this->workbox->pageCache->networkTimeout},
warmCache: {$routes}
const pageCacheStrategy = new workbox.strategies.{$strategy}({
networkTimeoutSeconds: {$this->workbox->pageCache->networkTimeout},
cacheName: '{$this->workbox->pageCache->cacheName}',
plugins: [new workbox.cacheableResponse.CacheableResponsePlugin({statuses: [0, 200]}){$broadcastUpdate}],
});
workbox.routing.registerRoute(
({request}) => request.mode === 'navigate',
pageCacheStrategy
);
self.addEventListener('install', event => {
const done = {$routes}.map(
path =>
pageCacheStrategy.handleAll({
event,
request: new Request(path),
})[1]
);
event.waitUntil(Promise.all(done));
});
PAGE_CACHE_RULE_STRATEGY;

Expand All @@ -68,15 +99,27 @@ public function process(string $body): string

public function getCacheStrategies(): array
{
$strategy = match ($this->workbox->pageCache->strategy) {
'staleWhileRevalidate' => CacheStrategy::STRATEGY_STALE_WHILE_REVALIDATE,
default => CacheStrategy::STRATEGY_NETWORK_FIRST,
};
$plugins = ['CacheableResponsePlugin'];
if ($this->workbox->pageCache->broadcast === true && $strategy === CacheStrategy::STRATEGY_STALE_WHILE_REVALIDATE) {
$plugins[] = 'BroadcastUpdatePlugin';
}
$routes = $this->serializer->serialize($this->workbox->pageCache->urls, 'json', $this->jsonOptions);
$url = json_decode($routes, true, 512, JSON_THROW_ON_ERROR);
return [
CacheStrategy::create(
$this->workbox->pageCache->cacheName,
CacheStrategy::STRATEGY_STALE_WHILE_REVALIDATE,
"'({request}) => request.mode === 'navigate'",
$strategy,
"({request}) => request.mode === 'navigate'",
$this->workbox->enabled && $this->workbox->pageCache->enabled,
true,
[
'maxTimeout' => $this->workbox->pageCache->networkTimeout,
'plugins' => $plugins,
'warmUrls' => $url,
]
),
];
Expand Down

0 comments on commit 9f89a46

Please sign in to comment.