Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge-up 1.0.x to 1.1.x #88

Merged
merged 10 commits into from
Mar 4, 2024
8 changes: 8 additions & 0 deletions .github/workflows/release-on-milestone-closed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 0

- name: "Release"
uses: "laminas/[email protected]"
Expand All @@ -36,6 +38,8 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 0

- name: "Create Merge-Up Pull Request"
uses: "laminas/[email protected]"
Expand All @@ -57,6 +61,8 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 0

- name: "Create and/or Switch to new Release Branch"
uses: "laminas/[email protected]"
Expand Down Expand Up @@ -101,6 +107,8 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 0

- name: "Create new milestones"
uses: "laminas/[email protected]"
Expand Down
7 changes: 6 additions & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,15 @@ private function setupServiceWorker(ArrayNodeDefinition $node): void
->example('/\.(ico|png|jpe?g|gif|svg|webp|bmp)$/')
->end()
->scalarNode('static_regex')
->defaultValue('/\.(css|js|json|xml|txt|woff2|ttf|eot|otf|map|webmanifest)$/')
->defaultValue('/\.(css|js|json|xml|txt|map|webmanifest)$/')
->info('The regex to match the static files.')
->example('/\.(css|js|json|xml|txt|woff2|ttf|eot|otf|map|webmanifest)$/')
->end()
->scalarNode('font_regex')
->defaultValue('/\.(ttf|eot|otf|woff2)$/')
->info('The regex to match the static files.')
->example('/\.(ttf|eot|otf|woff2)$/')
->end()
->integerNode('max_image_cache_entries')
->defaultValue(60)
->info('The maximum number of entries in the image cache.')
Expand Down
5 changes: 4 additions & 1 deletion src/Dto/Workbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ final class Workbox
public string $imageRegex = '/\.(ico|png|jpe?g|gif|svg|webp|bmp)$/';

#[SerializedName('static_regex')]
public string $staticRegex = '/\.(css|m?jsx?|json|xml|txt|woff2|ttf|eot|otf|map|webmanifest)$/';
public string $staticRegex = '/\.(css|m?jsx?|json|xml|txt|map|webmanifest)$/';

#[SerializedName('font_regex')]
public string $fontRegex = '/\.(ttf|eot|otf|woff2)$/';

#[SerializedName('clear_cache')]
public bool $clearCache = true;
Expand Down
105 changes: 80 additions & 25 deletions src/Service/ServiceWorkerCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,53 +130,108 @@ private function processStandardRules(Workbox $workbox, string $body): string
return $body;
}

$images = [];
$statics = [];
$assets = [];
$fonts = [];
foreach ($this->assetMapper->allAssets() as $asset) {
if (preg_match($workbox->imageRegex, $asset->sourcePath) === 1) {
$images[] = $asset->publicPath;
} elseif (preg_match($workbox->staticRegex, $asset->sourcePath) === 1) {
$statics[] = $asset->publicPath;
if (preg_match($workbox->imageRegex, $asset->sourcePath) === 1 || preg_match(
$workbox->staticRegex,
$asset->sourcePath
) === 1) {
$assets[] = $asset->publicPath;
} elseif (preg_match($workbox->fontRegex, $asset->sourcePath) === 1) {
$fonts[] = $asset->publicPath;
}
}
$jsonOptions = [
JsonEncode::OPTIONS => JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR,
];
$imageUrls = $this->serializer->serialize($images, 'json', $jsonOptions);
$staticUrls = $this->serializer->serialize($statics, 'json', $jsonOptions);
$assetUrls = $this->serializer->serialize($assets, 'json', $jsonOptions);
$fontUrls = $this->serializer->serialize($fonts, 'json', $jsonOptions);
$assetUrlsLength = count($assets) * 2;
$routes = $this->serializer->serialize($workbox->warmCacheUrls, 'json', $jsonOptions);

$declaration = <<<STANDARD_RULE_STRATEGY
// Pages cached during the navigation.
workbox.recipes.pageCache({
cacheName: '{$workbox->pageCacheName}',
networkTimeoutSeconds: {$workbox->networkTimeoutSeconds},
warmCache: {$routes}
});
workbox.recipes.imageCache({
cacheName: '{$workbox->imageCacheName}',
maxEntries: {$workbox->maxImageCacheEntries},
maxImageAge: {$workbox->maxImageAge},
warmCache: {$imageUrls}
});
workbox.recipes.staticResourceCache({
cacheName: '{$workbox->assetCacheName}',
warmCache: {$staticUrls}
});

//Images cache
workbox.routing.registerRoute(
({request}) => request.destination === 'font',
({request, url}) => (request.destination === 'image' && !url.pathname.startsWith('/assets')),
new workbox.strategies.CacheFirst({
cacheName: '{$workbox->fontCacheName}',
cacheName: '{$workbox->imageCacheName}',
plugins: [
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200],
}),
new workbox.cacheableResponse.CacheableResponsePlugin({statuses: [0, 200]}),
new workbox.expiration.ExpirationPlugin({
maxAgeSeconds: {$workbox->maxFontAge},
maxEntries: {$workbox->maxFontCacheEntries},
maxEntries: {$workbox->maxImageCacheEntries},
maxAgeSeconds: {$workbox->maxImageAge},
}),
],
})
);

// Assets served by Asset Mapper
// - Strategy: CacheFirst
const assetCacheStrategy = new workbox.strategies.CacheFirst({
cacheName: '{$workbox->assetCacheName}',
plugins: [
new workbox.cacheableResponse.CacheableResponsePlugin({statuses: [0, 200]}),
new workbox.expiration.ExpirationPlugin({
maxEntries: {$assetUrlsLength},
maxAgeSeconds: 365 * 24 * 60 * 60,
}),
],
});
// - Strategy: only the Asset Mapper public route
workbox.routing.registerRoute(
({url}) => url.pathname.startsWith('/assets'),
assetCacheStrategy
);
self.addEventListener('install', event => {
const done = {$assetUrls}.map(
path =>
assetCacheStrategy.handleAll({
event,
request: new Request(path),
})[1]
);

event.waitUntil(Promise.all(done));
});


const fontCacheStrategy = new workbox.strategies.CacheFirst({
cacheName: '{$workbox->fontCacheName}',
plugins: [
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200],
}),
new workbox.expiration.ExpirationPlugin({
maxAgeSeconds: {$workbox->maxFontAge},
maxEntries: {$workbox->maxFontCacheEntries},
}),
],
});
workbox.routing.registerRoute(
({request}) => request.destination === 'font',
fontCacheStrategy
);
self.addEventListener('install', event => {
const done = {$fontUrls}.map(
path =>
fontCacheStrategy.handleAll({
event,
request: new Request(path),
})[1]
);

event.waitUntil(Promise.all(done));
});


STANDARD_RULE_STRATEGY;

return str_replace($workbox->standardRulesPlaceholder, trim($declaration), $body);
Expand Down
23 changes: 19 additions & 4 deletions src/Twig/PwaRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
private string $manifestPublicUrl;

public function __construct(
#[Autowire('%spomky_labs_pwa.manifest.enabled%')]
private bool $manifestEnabled,
#[Autowire('%spomky_labs_pwa.sw.enabled%')]
private bool $serviceWorkerEnabled,
#[Autowire('@asset_mapper.importmap.config_reader')]
private ImportMapConfigReader $importMapConfigReader,
private AssetMapperInterface $assetMapper,
Expand All @@ -40,12 +44,23 @@ public function load(
bool $injectSW = true,
array $swAttributes = []
): string {
$url = $this->assetMapper->getPublicPath($this->manifestPublicUrl) ?? $this->manifestPublicUrl;
$output = sprintf('%s<link rel="manifest" href="%s">', PHP_EOL, $url);
$output = '';
if ($this->manifestEnabled === true) {
$output = $this->injectManifestFile($output);
}
if ($this->serviceWorkerEnabled === true) {
$output = $this->injectServiceWorker($output, $injectSW, $swAttributes);
}
$output = $this->injectIcons($output, $injectIcons);
$output = $this->injectThemeColor($output, $injectThemeColor);

return $this->injectServiceWorker($output, $injectSW, $swAttributes);
return $this->injectThemeColor($output, $injectThemeColor);
}

private function injectManifestFile(string $output): string
{
$url = $this->assetMapper->getPublicPath($this->manifestPublicUrl) ?? $this->manifestPublicUrl;

return $output . sprintf('%s<link rel="manifest" href="%s">', PHP_EOL, $url);
}

private function injectThemeColor(string $output, bool $themeColor): string
Expand Down