Skip to main content
Version: 2.0.0-beta.0

πŸ“¦ plugin-pwa

Workboxλ₯Ό μ‚¬μš©ν•΄ PWA 지원을 μΆ”κ°€ν•  수 μžˆλŠ” λ„νμ‚¬μš°λ£¨μŠ€ ν”ŒλŸ¬κ·ΈμΈμž…λ‹ˆλ‹€. μ œν’ˆ λΉŒλ“œμ—λ§Œ μ„œλΉ„μŠ€ μ›Œμ»€λ₯Ό λ§Œλ“€μ–΄μ£ΌλŠ” ν”ŒλŸ¬κ·ΈμΈμž…λ‹ˆλ‹€. 이λ₯Ό 톡해 μ˜€ν”„λΌμΈμ—μ„œ μ‹€ν–‰ν•  수 있고 μ„€μΉ˜λ„ μ§€μ›ν•˜λŠ” μ™„μ „ν•œ PWA ν˜Έν™˜ λ¬Έμ„œλ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ„€μΉ˜#

npm install --save @docusaurus/plugin-pwa

μ„€μ •#

./static/manifest.json νŒŒμΌμ— PWA λ§€λ‹ˆνŽ˜μŠ€νŠΈλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

docusaurus.config.js에 ν•„μš”ν•œ PWA 섀정을 μΆ”κ°€ν•©λ‹ˆλ‹€.

docusaurus.config.js
module.exports = {
plugins: [
[
'@docusaurus/plugin-pwa',
{
debug: true,
offlineModeActivationStrategies: [
'appInstalled',
'standalone',
'queryString',
],
pwaHead: [
{
tagName: 'link',
rel: 'icon',
href: '/img/docusaurus.png',
},
{
tagName: 'link',
rel: 'manifest',
href: '/manifest.json', // your PWA manifest
},
{
tagName: 'meta',
name: 'theme-color',
content: 'rgb(37, 194, 160)',
},
],
},
],
],
};

ν”„λ‘œκ·Έλ ˆμ‹œλΈŒ μ›Ή μ•±#

μ„œλΉ„μŠ€ μ›Œμ»€λ₯Ό μ„€μΉ˜ν•˜λŠ” κ²ƒλ§ŒμœΌλ‘œ μ—¬λŸ¬λΆ„μ˜ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ PWAκ°€ λ˜λŠ” 건 μ•„λ‹™λ‹ˆλ‹€. μ΅œμ†Œν•œ μ›Ή μ•± λ§€λ‹ˆνŽ˜μŠ€νŠΈλ₯Ό 가지고 μžˆμ–΄μ•Ό ν•˜λ©° <head> νƒœκ·Έ μ•ˆμ— μ μ ˆν•œ νƒœκ·Έκ°€ ν¬ν•¨λ˜μ–΄ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€ (Options > pwaHead).

배포 ν›„μ—λŠ” Lighthouseλ₯Ό μ‚¬μš©ν•΄ μ—¬λŸ¬λΆ„μ˜ μ‚¬μ΄νŠΈλ₯Ό 점검할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ—¬λŸ¬λΆ„μ˜ μ‚¬μ΄νŠΈκ°€ PWAκ°€ 되기 μœ„ν•΄ ν•„μš”ν•œ μ’€ 더 μžμ„Έν•œ λ‚΄μš©μ€ PWA 체크리슀트λ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

μ•± μ„€μΉ˜ 지원#

μ—¬λŸ¬λΆ„μ˜ λΈŒλΌμš°μ €κ°€ μ§€μ›ν•œλ‹€λ©΄ λ„νμ‚¬μš°λ£¨μŠ€ μ‚¬μ΄νŠΈλ₯Ό μ•±μ²˜λŸΌ μ„€μΉ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

pwa_install.gif

note

μ•± μ„€μΉ˜λ₯Ό μ§€μ›ν•˜λ €λ©΄ https ν”„λ‘œν† μ½œμ„ μ‚¬μš©ν•΄μ•Ό ν•˜λ©° μœ νš¨ν•œ λ§€λ‹ˆνŽ˜μŠ€νŠΈκ°€ ν•„μš”ν•©λ‹ˆλ‹€.

μ˜€ν”„λΌμΈ λͺ¨λ“œ(precaching)#

μ„œλΉ„μŠ€ μ›Œμ»€ 사전캐싱을 μ‚¬μš©ν•΄ λ„νμ‚¬μš°λ£¨μŠ€ μ‚¬μ΄νŠΈλ₯Ό μ˜€ν”„λΌμΈμ—μ„œ μ‚¬μš©μžκ°€ 탐색할 수 μžˆμŠ΅λ‹ˆλ‹€.

사전캐싱은 λ¬΄μ—‡μΈκ°€μš”?#

μ„œλΉ„μŠ€ μ›Œμ»€μ˜ κΈ°λŠ₯ 쀑 ν•˜λ‚˜λ‘œ μ„œλΉ„μŠ€ μ›Œμ»€ μ„€μΉ˜ μ‹œ μΊμ‹œ μ²˜λ¦¬ν•  파일 λ¬ΆμŒμ„ μ €μž₯해놓을 수 μžˆμŠ΅λ‹ˆλ‹€. μ„œλΉ„μŠ€ μ›Œμ»€κ°€ μ‚¬μš©λ˜κΈ° 전에 μ½˜ν…μΈ λ₯Ό μΊμ‹œ μ²˜λ¦¬ν•˜κΈ° λ•Œλ¬Έμ— "사전캐싱"이라고 λΆ€λ¦…λ‹ˆλ‹€.

μ΄λ ‡κ²Œ μ²˜λ¦¬ν•˜λŠ” μ΄μœ λŠ” κ°œλ°œμžμ—κ²Œ μΊμ‹œλ₯Ό μ œμ–΄ν•  수 μžˆλŠ” κΆŒν•œμ„ μ£ΌκΈ° μœ„ν•¨μž…λ‹ˆλ‹€. κ°œλ°œμžλŠ” μΊμ‹œλ₯Ό μ–Έμ œ λ§Œλ“€κ³  μ–Όλ§ˆλ‚˜ 였래 보관할지 κ²°μ •ν•  수 μžˆμ„ 뿐 μ•„λ‹ˆλΌ λ„€νŠΈμ›Œν¬λ₯Ό κ±°μΉ˜μ§€ μ•Šκ³  λΈŒλΌμš°μ €μ—μ„œ λ°”λ‘œ μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 즉 μ˜€ν”„λΌμΈμ—μ„œ λ™μž‘ν•  수 μžˆλŠ” μ›Ή 앱을 λ§Œλ“€ 수 μžˆλ‹€λŠ” κ²λ‹ˆλ‹€.

WorkboxλŠ” APIλ₯Ό λ‹¨μˆœν™”ν•˜κ³  효율적으둜 ν•„μš”ν•œ νŒŒμΌμ„ 내렀받을 수 있게 λ§Œλ“€μ–΄ 사전캐싱 μž‘μ—…μ„ μ‰½κ²Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ μ§€μ›ν•©λ‹ˆλ‹€.

기본적으둜 μ˜€ν”„λΌμΈ λͺ¨λ“œλŠ” μ‚¬μ΄νŠΈκ°€ μ•±μœΌλ‘œ μ„€μΉ˜λ˜λ©΄ ν™œμ„±ν™”λ©λ‹ˆλ‹€. μžμ„Έν•œ λ‚΄μš©μ€ offlineModeActivationStrategies μ˜΅μ…˜ μ„€λͺ…을 μ°Έκ³ ν•˜μ„Έμš”.

μ‚¬μ΄νŠΈ 사전캐싱 이후에 μ„œλΉ„μŠ€ μ›Œμ»€λŠ” λ°©λ¬Έμžμ—κ²Œ μΊμ‹œλœ 응닡을 μ œκ³΅ν•©λ‹ˆλ‹€. μƒˆλ‘œμš΄ λΉŒλ“œκ°€ μƒˆλ‘œμš΄ μ„œλΉ„μŠ€ μ›Œμ»€μ™€ ν•¨κ»˜ 배포되면 μƒˆλ‘œμš΄ 앱이 μ„€μΉ˜λ˜κ³  λŒ€κΈ° μƒνƒœλ‘œ μ΄λ™ν•˜κ²Œ λ©λ‹ˆλ‹€. λŒ€κΈ° μƒνƒœμ— λ“€μ–΄κ°€λ©΄ λ¦¬λ‘œλ“œ νŒμ—…μ΄ ν‘œμ‹œλ˜κ³  μ‚¬μš©μžμ—κ²Œ μƒˆλ‘œμš΄ μ½˜ν…μΈ λ₯Ό μœ„ν•΄ νŽ˜μ΄μ§€λ₯Ό λ¦¬λ‘œλ“œν•  것인지 λ¬Όμ–΄λ΄…λ‹ˆλ‹€. μ‚¬μš©μžκ°€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μΊμ‹œλ₯Ό μ‚­μ œν•˜κ±°λ‚˜ νŒμ—…μ—μ„œ reload λ²„νŠΌμ„ 클릭할 λ•ŒκΉŒμ§€ μ„œλΉ„μŠ€ μ›Œμ»€λŠ” κΈ°μ‘΄ μ½˜ν…μΈ λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

caution

μ˜€ν”„λΌμΈ λͺ¨λ“œλ‚˜ 사전캐싱은 μ‚¬μ΄νŠΈμ˜ λͺ¨λ“  애셋을 미리 λ‚΄λ €λ°›μ•„μ•Ό ν•˜λ©° λΆˆν•„μš”ν•œ λŒ€μ—­ν­μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ“  μ’…λ₯˜μ˜ μ‚¬μ΄νŠΈμ—μ„œ 무쑰건 ν•΄λ‹Ή κΈ°λŠ₯을 ν™œμ„±ν™”ν•˜λŠ” 건 쒋은 생각은 μ•„λ‹™λ‹ˆλ‹€.

μ˜΅μ…˜#

debug#

  • Type: boolean
  • Default: false

μ•„λž˜μ™€ 같은 디버그 λͺ¨λ“œλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.

  • Workbox 둜그
  • 좔가적인 λ„νμ‚¬μš°λ£¨μŠ€ 둜그
  • μ΅œμ ν™”λ˜μ§€ λͺ»ν•œ SW 파일 좜λ ₯
  • μ†ŒμŠ€λ§΅

offlineModeActivationStrategies#

  • Type: Array<'appInstalled' | 'mobile' | 'saveData'| 'queryString' | 'always'>
  • Default: ['appInstalled','queryString','standalone']

μ˜€ν”„λΌμΈ λͺ¨λ“œλ₯Ό μ μš©ν•  μƒνƒœμ— λŒ€ν•œ μ˜΅μ…˜μ„ μ„€μ •ν•©λ‹ˆλ‹€.

  • appInstalled: μ‚¬μš©μžκ°€ μ‚¬μ΄νŠΈλ₯Ό μ•±μœΌλ‘œ μ„€μΉ˜ν•œ 경우 ν™œμ„±ν™”(100% μ‹ λ’°ν•  μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€)
  • standalone: μ‚¬μš©μžκ°€ 앱을 λ…λ¦½μ μœΌλ‘œ μ‹€ν–‰ν•œ 경우 ν™œμ„±ν™”(PWAκ°€ μ„€μΉ˜λœ 경우)
  • queryString: queryString에 offlineMode=trueκ°€ ν¬ν•¨λœ 경우 ν™œμ„±ν™”(PWA 디버깅 μ‹œ μœ μš©ν•©λ‹ˆλ‹€)
  • mobile: λͺ¨λ°”일 μ‚¬μš©μžμΈ 경우 ν™œμ„±ν™”(width <= 940px)
  • saveData: navigator.connection.saveData === true둜 μ„€μ •ν•œ 경우 ν™œμ„±ν™”
  • always: λͺ¨λ“  μ‚¬μš©μž ν™œμ„±ν™”
caution

μ£Όμ˜ν•΄μ„œ μ‚¬μš©ν•΄μ£Όμ„Έμš”. 일뢀 μ‚¬μš©μžλŠ” μ˜€ν”„λΌμΈ λͺ¨λ“œλ‘œ μ‚¬μš©ν•˜λ„λ‘ κ°•μ œλ˜λŠ” 것을 μ‹«μ–΄ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

danger

맀우 μ•ˆμ •μ μΈ λ°©μ‹μœΌλ‘œ PWAλ₯Ό κ°μ§€ν•˜λŠ” 것은 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.

appinstalled μ΄λ²€νŠΈκ°€ μŠ€νŽ™μ—μ„œ μ œκ±°λ˜λ©΄μ„œ μ΅œμ‹  크둬 λ²„μ „μ—μ„œλŠ” navigator.getInstalledRelatedApps() API만 μ§€μ›ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μ‚¬μš©ν•˜λ €λ©΄ λ§€λ‹ˆνŽ˜μŠ€νŠΈμ—μ„œ related_applicationsλ₯Ό μ„ μ–Έν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

standalone μ „λž΅μ„ μ‚¬μš©ν•˜λŠ” 것이 μ˜€ν”„λΌμΈ λͺ¨λ“œλ₯Ό ν™œμ„±ν™”ν•˜λŠ” 쒋은 λŒ€μ•ˆμž…λ‹ˆλ‹€(μ„€μΉ˜λœ 앱을 μ‹€ν–‰ν•˜λŠ” 경우).

injectManifestConfig#

workbox.injectManifest()에 전달할 Workbox μ˜΅μ…˜μ„ μ„€μ •ν•©λ‹ˆλ‹€. 프리캐싱을 μ μš©ν•˜κ³  μ˜€ν”„λΌμΈμ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” 애셋을 직접 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • Type: InjectManifestOptions
  • Default: {}
docusaurus.config.js
module.exports = {
plugins: [
[
'@docusaurus/plugin-pwa',
{
injectManifestConfig: {
manifestTransforms: [
//...
],
modifyURLPrefix: {
//...
},
// 일반적으둜 μ‚¬μš©ν•˜λŠ” 정적 μ• μ…‹(html, 이미지 λ“±)은 μ˜€ν”„λΌμΈμ—μ„œ μ‚¬μš©ν•  수 μžˆλ„λ‘
// 기본적으둜 μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€. μΆ”κ°€λ‘œ ν•„μš”ν•œ 파일만 μ„€μ •ν•΄μ€λ‹ˆλ‹€.
globPatterns: ['**/*.{pdf,docx,xlsx}'],
// ...
},
},
],
],
};

reloadPopup#

  • Type: string | false
  • Default: '@theme/PwaReloadPopup'

νŒμ—… μ»΄ν¬λ„ŒνŠΈλ₯Ό λ¦¬λ‘œλ“œν•  λͺ¨λ“ˆ 경둜λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. μƒˆλ‘œμš΄ μ„œλΉ„μŠ€ μ›Œμ»€κ°€ μ„€μΉ˜λ˜κΈ°λ₯Ό 기닀릴 λ•Œ νŒμ—…μ΄ ν‘œμ‹œλ˜λ©° μ‚¬μš©μžμ—κ²Œ λ¦¬λ‘œλ“œλ₯Ό μ•ˆλ‚΄ν•©λ‹ˆλ‹€.

false둜 μ„€μ •ν•˜λ©΄ νŒμ—…μ΄ ν‘œμ‹œλ˜μ§€ μ•Šμ§€λ§Œ ꢌμž₯ν•˜μ§€λŠ” μ•ŠμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ μ‚¬μš©μžκ°€ μ΅œμ‹  컨텐츠λ₯Ό 확인할 수 μ—†μŠ΅λ‹ˆλ‹€.

μ†μ„±μœΌλ‘œ onReloadλ₯Ό μ„€μ •ν•˜λ©΄ μ‚¬μš©μž 지정 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. onReload μ½œλ°±ν•¨μˆ˜λŠ” reload λ²„νŠΌ 클릭 μ‹œ ν˜ΈμΆœλ©λ‹ˆλ‹€. 그리고 기닀리고 μžˆλŠ” μ„œλΉ„μŠ€ μ›Œμ»€μ—κ²Œ μ„€μΉ˜λ₯Ό μ§„ν–‰ν•˜κ³  νŽ˜μ΄μ§€λ₯Ό λ¦¬λ‘œλ“œν•˜λ„λ‘ μ „λ‹¬ν•©λ‹ˆλ‹€.

interface PwaReloadPopupProps {
onReload: () => void;
}

κΈ°λ³Έ ν…Œλ§ˆλŠ” λ¦¬λ‘œλ“œ νŒμ—…μ΄ κ΅¬ν˜„λ˜μ–΄ 있으며 Infima Alerts을 μ‚¬μš©ν•©λ‹ˆλ‹€.

pwa_reload.gif

pwaHead#

  • Type: Array<{ tagName: string } & Record<string,string>>
  • Default: []

<head> νƒœκ·Έ 내에 μ‚½μž…ν•  tagNameκ³Ό ν‚€, κ°’ μŒμ„ ν¬ν•¨ν•˜λŠ” 였브젝트 배열을 μ„€μ •ν•©λ‹ˆλ‹€. 기술적으둜 μ–΄λ–€ head νƒœκ·Έ λ“  μ‚½μž…ν•  수 μžˆμ§€λ§Œ μ—¬λŸ¬λΆ„μ˜ μ‚¬μ΄νŠΈ PWA에 μ ν•©ν•œ νƒœκ·Έλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ΄μƒμ μž…λ‹ˆλ‹€. μ•„λž˜ λͺ©λ‘μ€ μ—¬λŸ¬λΆ„μ˜ 앱에 μ ν•©ν•œ νƒœκ·Έ λͺ©λ‘μž…λ‹ˆλ‹€.

module.exports = {
plugins: [
[
'@docusaurus/plugin-pwa',
{
pwaHead: [
{
tagName: 'link',
rel: 'icon',
href: '/img/docusaurus.png',
},
{
tagName: 'link',
rel: 'manifest',
href: '/manifest.json',
},
{
tagName: 'meta',
name: 'theme-color',
content: 'rgb(37, 194, 160)',
},
{
tagName: 'meta',
name: 'apple-mobile-web-app-capable',
content: 'yes',
},
{
tagName: 'meta',
name: 'apple-mobile-web-app-status-bar-style',
content: '#000',
},
{
tagName: 'link',
rel: 'apple-touch-icon',
href: '/img/docusaurus.png',
},
{
tagName: 'link',
rel: 'mask-icon',
href: '/img/docusaurus.svg',
color: 'rgb(37, 194, 160)',
},
{
tagName: 'meta',
name: 'msapplication-TileImage',
content: '/img/docusaurus.png',
},
{
tagName: 'meta',
name: 'msapplication-TileColor',
content: '#000',
},
],
},
],
],
};

swCustom#

  • Type: string | undefined
  • Default: undefined

Workbox에 μ μš©ν•  κ·œμΉ™ μΆ”κ°€ μ‹œ μœ μš©ν•œ μ„€μ •μž…λ‹ˆλ‹€. μ„œλΉ„μŠ€ μ›Œμ»€μ˜ λͺ¨λ“  κΈ°λŠ₯κ³Ό ν•¨κ»˜ Workbox 라이브러리의 κ°•λ ₯ν•œ μΆ”κ°€ κΈ°λŠ₯을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 트랜슀파일된 μ½”λ“œμ΄λ©° μ΅œμ‹  ES6+ ꡬ문을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄ μ™ΈλΆ€ κ²½λ‘œμ— μžˆλŠ” νŒŒμΌμ„ μΊμ‹œν•˜λŠ” κ²½μš°μ—λŠ” μ•„λž˜μ™€ 같이 μ‚¬μš©ν•©λ‹ˆλ‹€.

import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
// default fn export receiving some useful params
export default function swCustom(params) {
const {
debug, // :boolean
offlineMode, // :boolean
} = params;
// Cache responses from external resources
registerRoute((context) => {
return [
/graph\.facebook\.com\/.*\/picture/,
/netlify\.com\/img/,
/avatars1\.githubusercontent/,
].some((regex) => context.url.href.match(regex));
}, new StaleWhileRevalidate());
}

λͺ¨λ“ˆμ€ default 내보내기 ν•¨μˆ˜λ₯Ό 가지고 μžˆμ–΄μ•Ό ν•˜λ©° 일뢀 λ§€κ°œλ³€μˆ˜λ₯Ό λ°›μ•„μ•Ό ν•©λ‹ˆλ‹€.

swRegister#

  • Type: string | false
  • Default: 'docusaurus-plugin-pwa/src/registerSW.js'

앱이 μ‹€ν–‰ν•˜κΈ° 전에 등둝이 μ²˜λ¦¬λ˜λ„λ‘ λ„νμ‚¬μš°λ£¨μŠ€ μ•± μ•žμ— ν•­λͺ©μ„ μΆ”κ°€ν•©λ‹ˆλ‹€. κΈ°λ³Έ registerSW.js νŒŒμΌμ€ κ°„λ‹¨ν•œ 등둝을 μœ„ν•œ μΆ©λΆ„ν•œ 섀정을 ν¬ν•¨ν•©λ‹ˆλ‹€.

false둜 μ„€μ •ν•˜λ©΄ 등둝을 μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

λ§€λ‹ˆνŽ˜μŠ€νŠΈ 예#

λ„νμ‚¬μš°λ£¨μŠ€ μ‚¬μ΄νŠΈ λ§€λ‹ˆνŽ˜μŠ€νŠΈλ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

{
"name": "Docusaurus v2",
"short_name": "Docusaurus",
"theme_color": "#2196f3",
"background_color": "#424242",
"display": "standalone",
"scope": "./",
"start_url": "./index.html",
"related_applications": [
{
"platform": "webapp",
"url": "https://docusaurus.io/manifest.json"
}
],
"icons": [
{
"src": "img/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "img/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "img/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "img/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "img/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "img/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "img/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "img/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}