Skip to content

Commit f4eeda7

Browse files
dvarnaipi0
andauthored
feat(icon): support for iOS splash screens (#308)
* IOS screen sizes * Emit splash screens for iOS devices * Fix lint error Recreate snapshot * Recreate snapshot * Recreate snapshot * refactor: remove duplicate logic * refactor: unify shared operations * test: update fixture * test: update test Co-authored-by: pooya parsa <pyapar@gmail.com>
1 parent aa4eace commit f4eeda7

File tree

8 files changed

+132
-44
lines changed

8 files changed

+132
-44
lines changed

lib/icon/module.js

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require('path')
22
const { fork } = require('child_process')
33
const fs = require('fs-extra')
44
const hasha = require('hasha')
5-
const { joinUrl, getRouteParams } = require('../utils')
5+
const { joinUrl, getRouteParams, sizeName } = require('../utils')
66

77
module.exports = function (pwa) {
88
this.nuxt.hook('build:before', () => run.call(this, pwa, true))
@@ -19,6 +19,18 @@ async function run (pwa, _emitAssets) {
1919
const defaults = {
2020
iconFileName: 'icon.png',
2121
sizes: [64, 120, 144, 152, 192, 384, 512],
22+
iosSizes: [
23+
[1536, 2048, 'ipad'], // Ipad
24+
[1536, 2048, 'ipadpro9'], // Ipad Pro 9.7"
25+
[1668, 2224, 'ipadpro10'], // Ipad Pro 10.5"
26+
[2048, 2732, 'ipadpro12'], // Ipad Pro 12.9"
27+
[640, 1136, 'iphonese'], // Iphone SE
28+
[50, 1334, 'iphone6'], // Iphone 6
29+
[1080, 1920, 'iphoneplus'], // Iphone Plus
30+
[1125, 2436, 'iphonex'], // Iphone X
31+
[828, 1792, 'iphonexr'], // Iphone XR
32+
[1242, 2688, 'iphonexsmax'] // Iphone XS Max
33+
],
2234
targetDir: 'icons',
2335
accessibleIcons: true,
2436
iconProperty: '$icon',
@@ -28,8 +40,9 @@ async function run (pwa, _emitAssets) {
2840

2941
_iconHash: null,
3042
_cacheDir: null,
31-
_icons: null,
32-
_assetIcons: null
43+
_assets: null,
44+
_manifestIcons: null,
45+
_iosSplash: null
3346
}
3447

3548
// Merge options
@@ -91,8 +104,8 @@ async function findIcon (options) {
91104

92105
function addPlugin (options) {
93106
const icons = {}
94-
for (const icon of options._assetIcons) {
95-
icons[icon.sizes] = icon.src
107+
for (const asset of options._assets) {
108+
icons[asset.name] = asset.target
96109
}
97110

98111
if (options.accessibleIcons) {
@@ -112,37 +125,53 @@ async function generateIcons (options) {
112125
if (!options.iconHash) {
113126
options.iconHash = await hasha.fromFile(options.iconSrc).then(h => h.substring(0, 6))
114127
}
128+
129+
// Resize cache dir
115130
if (!options._cacheDir) {
116131
options._cacheDir = path.join(__dirname, '.cache', options.iconHash)
117132
}
118133

119-
// Generate _icons
120-
options._icons = {}
121-
for (const size of options.sizes) {
122-
options._icons[size] = `${options.targetDir}/icon_${size}.${options.iconHash}.png`
123-
}
134+
// Icons to be emited by webpack
135+
options._assets = []
124136

125-
// Generate _purpose
137+
// Manifest icons
126138
const purpose = options.purpose ? options.purpose.join(' ') : undefined
139+
options._manifestIcons = []
140+
for (const size of options.sizes) {
141+
const name = sizeName(size)
142+
const target = `${options.targetDir}/icon_${name}.${options.iconHash}.png`
143+
options._assets.push({ name, target })
144+
options._manifestIcons.push({
145+
src: joinUrl(options.publicPath, target),
146+
sizes: name,
147+
type: 'image/png',
148+
purpose
149+
})
150+
}
127151

128-
// Generate _assetIcons
129-
options._assetIcons = options.sizes.map(size => ({
130-
src: joinUrl(options.publicPath, options._icons[size]),
131-
sizes: `${size}x${size}`,
132-
type: 'image/png',
133-
purpose
134-
}))
152+
// Generate _iosSplash
153+
options._iosSplash = {}
154+
for (const size of options.iosSizes) {
155+
const name = sizeName(size)
156+
const target = `${options.targetDir}/splash_${name}.${options.iconHash}.png`
157+
options._assets.push({ name, target })
158+
options._iosSplash[size[2]] = joinUrl(options.publicPath, target)
159+
}
135160
}
136161

137162
function addManifest (options, pwa) {
138163
if (!pwa.manifest) {
139164
pwa.manifest = {}
140165
}
166+
141167
if (!pwa.manifest.icons) {
142168
pwa.manifest.icons = []
143169
}
170+
pwa.manifest.icons.push(...options._manifestIcons)
144171

145-
pwa.manifest.icons.push(...options._assetIcons)
172+
pwa._iosSplash = {
173+
...options._iosSplash
174+
}
146175
}
147176

148177
function emitAssets (options) {
@@ -156,11 +185,10 @@ function emitAssets (options) {
156185
apply (compiler) {
157186
compiler.hooks.emit.tapPromise('nuxt-pwa-icon', async (compilation) => {
158187
await resizePromise
159-
await Promise.all(options.sizes.map(async (size) => {
160-
const targetFilename = options._icons[size]
161-
const srcFileName = path.join(options._cacheDir, `${size}.png`)
188+
await Promise.all(options._assets.map(async ({ name, target }) => {
189+
const srcFileName = path.join(options._cacheDir, `${name}.png`)
162190
const src = await fs.readFile(srcFileName)
163-
compilation.assets[targetFilename] = { source: () => src, size: () => src.length }
191+
compilation.assets[target] = { source: () => src, size: () => src.length }
164192
}))
165193
})
166194
}
@@ -181,7 +209,10 @@ async function resizeIcons (options) {
181209
JSON.stringify({
182210
input: options.iconSrc,
183211
distDir: options._cacheDir,
184-
sizes: options.sizes
212+
sizes: [
213+
...options.sizes,
214+
...options.iosSizes
215+
]
185216
})
186217
])
187218
child.on('exit', (code) => {

lib/icon/resize.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
const path = require('path')
22
const Jimp = require('jimp-compact')
3+
const { normalizeSize, sizeName } = require('../utils')
34

45
async function resize ({ input, distDir, sizes }) {
56
const inputFile = await Jimp.read(input)
67

7-
await Promise.all(sizes.map((size) => {
8-
const distFile = path.join(distDir, `${size}.png`)
8+
// Icons
9+
await Promise.all(sizes.map(normalizeSize).map((size) => {
10+
const name = sizeName(size)
11+
const distFile = path.join(distDir, `${name}.png`)
912
return new Promise((resolve) => {
10-
inputFile.clone().contain(size, size).write(distFile, () => resolve())
13+
inputFile.clone().contain(size[0], size[1]).write(distFile, () => resolve())
1114
})
1215
}))
1316
}

lib/manifest/module.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function addManifest (pwa) {
2424
description: process.env.npm_package_description,
2525
publicPath,
2626
icons: [],
27+
iosSplash: [],
2728
start_url: routerBase + '?standalone=true',
2829
display: 'standalone',
2930
background_color: '#ffffff',

lib/meta/module.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,16 @@ function generateMeta (pwa) {
9494

9595
// Launch Screen Image (IOS)
9696
if (options.mobileAppIOS && !find(this.options.head.link, 'rel', 'apple-touch-startup-image')) {
97-
this.options.head.link.push({ rel: 'apple-touch-startup-image', href: iconBig.src })
97+
this.options.head.link.push({ href: pwa._iosSplash.iphonese, media: '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
98+
this.options.head.link.push({ href: pwa._iosSplash.iphone6, media: '(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
99+
this.options.head.link.push({ href: pwa._iosSplash.iphoneplus, media: '(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)', rel: 'apple-touch-startup-image' })
100+
this.options.head.link.push({ href: pwa._iosSplash.iphonex, media: '(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)', rel: 'apple-touch-startup-image' })
101+
this.options.head.link.push({ href: pwa._iosSplash.iphonexr, media: '(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
102+
this.options.head.link.push({ href: pwa._iosSplash.iphonexsmax, media: '(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)', rel: 'apple-touch-startup-image' })
103+
this.options.head.link.push({ href: pwa._iosSplash.ipad, media: '(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
104+
this.options.head.link.push({ href: pwa._iosSplash.ipadpro1, media: '(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
105+
this.options.head.link.push({ href: pwa._iosSplash.ipadpro2, media: '(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
106+
this.options.head.link.push({ href: pwa._iosSplash.ipadpro3, media: '(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)', rel: 'apple-touch-startup-image' })
98107
}
99108
}
100109

lib/utils/index.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ function joinUrl (...args) {
1212
return path.join(...args).replace(':/', '://')
1313
}
1414

15+
function normalizeSize (size) {
16+
if (!Array.isArray(size)) {
17+
size = [size, size]
18+
}
19+
if (size.length === 1) {
20+
size = [size, size]
21+
} else if (size.length === 0) {
22+
size = 64
23+
}
24+
return size
25+
}
26+
27+
function sizeName (size) {
28+
size = normalizeSize(size)
29+
const prefix = size[2] ? (size[2] + '_') : ''
30+
return prefix + size[0] + 'x' + size[1]
31+
}
32+
1533
function getRouteParams (options) {
1634
// routerBase
1735
const routerBase = options.router.base
@@ -39,5 +57,7 @@ module.exports = {
3957
isUrl,
4058
joinUrl,
4159
getRouteParams,
42-
startCase
60+
startCase,
61+
normalizeSize,
62+
sizeName
4363
}

test/__snapshots__/pwa.test.js.snap

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,23 @@ Array [
99
"fixture/.nuxt/dist/client",
1010
"fixture/.nuxt/dist/client/LICENSES",
1111
"fixture/.nuxt/dist/client/icons",
12-
"fixture/.nuxt/dist/client/icons/icon_120.b8f3a1.png",
13-
"fixture/.nuxt/dist/client/icons/icon_144.b8f3a1.png",
14-
"fixture/.nuxt/dist/client/icons/icon_152.b8f3a1.png",
15-
"fixture/.nuxt/dist/client/icons/icon_192.b8f3a1.png",
16-
"fixture/.nuxt/dist/client/icons/icon_384.b8f3a1.png",
17-
"fixture/.nuxt/dist/client/icons/icon_512.b8f3a1.png",
18-
"fixture/.nuxt/dist/client/icons/icon_64.b8f3a1.png",
12+
"fixture/.nuxt/dist/client/icons/icon_120x120.b8f3a1.png",
13+
"fixture/.nuxt/dist/client/icons/icon_144x144.b8f3a1.png",
14+
"fixture/.nuxt/dist/client/icons/icon_152x152.b8f3a1.png",
15+
"fixture/.nuxt/dist/client/icons/icon_192x192.b8f3a1.png",
16+
"fixture/.nuxt/dist/client/icons/icon_384x384.b8f3a1.png",
17+
"fixture/.nuxt/dist/client/icons/icon_512x512.b8f3a1.png",
18+
"fixture/.nuxt/dist/client/icons/icon_64x64.b8f3a1.png",
19+
"fixture/.nuxt/dist/client/icons/splash_ipad_1536x2048.b8f3a1.png",
20+
"fixture/.nuxt/dist/client/icons/splash_ipadpro10_1668x2224.b8f3a1.png",
21+
"fixture/.nuxt/dist/client/icons/splash_ipadpro12_2048x2732.b8f3a1.png",
22+
"fixture/.nuxt/dist/client/icons/splash_ipadpro9_1536x2048.b8f3a1.png",
23+
"fixture/.nuxt/dist/client/icons/splash_iphone6_50x1334.b8f3a1.png",
24+
"fixture/.nuxt/dist/client/icons/splash_iphoneplus_1080x1920.b8f3a1.png",
25+
"fixture/.nuxt/dist/client/icons/splash_iphonese_640x1136.b8f3a1.png",
26+
"fixture/.nuxt/dist/client/icons/splash_iphonex_1125x2436.b8f3a1.png",
27+
"fixture/.nuxt/dist/client/icons/splash_iphonexr_828x1792.b8f3a1.png",
28+
"fixture/.nuxt/dist/client/icons/splash_iphonexsmax_1242x2688.b8f3a1.png",
1929
"fixture/.nuxt/dist/client/manifest_test.webmanifest",
2030
"fixture/.nuxt/dist/client/node_modules",
2131
"fixture/.nuxt/dist/client/pages",
@@ -39,13 +49,23 @@ Array [
3949
"fixture/dist/_nuxt",
4050
"fixture/dist/_nuxt/LICENSES",
4151
"fixture/dist/_nuxt/icons",
42-
"fixture/dist/_nuxt/icons/icon_120.b8f3a1.png",
43-
"fixture/dist/_nuxt/icons/icon_144.b8f3a1.png",
44-
"fixture/dist/_nuxt/icons/icon_152.b8f3a1.png",
45-
"fixture/dist/_nuxt/icons/icon_192.b8f3a1.png",
46-
"fixture/dist/_nuxt/icons/icon_384.b8f3a1.png",
47-
"fixture/dist/_nuxt/icons/icon_512.b8f3a1.png",
48-
"fixture/dist/_nuxt/icons/icon_64.b8f3a1.png",
52+
"fixture/dist/_nuxt/icons/icon_120x120.b8f3a1.png",
53+
"fixture/dist/_nuxt/icons/icon_144x144.b8f3a1.png",
54+
"fixture/dist/_nuxt/icons/icon_152x152.b8f3a1.png",
55+
"fixture/dist/_nuxt/icons/icon_192x192.b8f3a1.png",
56+
"fixture/dist/_nuxt/icons/icon_384x384.b8f3a1.png",
57+
"fixture/dist/_nuxt/icons/icon_512x512.b8f3a1.png",
58+
"fixture/dist/_nuxt/icons/icon_64x64.b8f3a1.png",
59+
"fixture/dist/_nuxt/icons/splash_ipad_1536x2048.b8f3a1.png",
60+
"fixture/dist/_nuxt/icons/splash_ipadpro10_1668x2224.b8f3a1.png",
61+
"fixture/dist/_nuxt/icons/splash_ipadpro12_2048x2732.b8f3a1.png",
62+
"fixture/dist/_nuxt/icons/splash_ipadpro9_1536x2048.b8f3a1.png",
63+
"fixture/dist/_nuxt/icons/splash_iphone6_50x1334.b8f3a1.png",
64+
"fixture/dist/_nuxt/icons/splash_iphoneplus_1080x1920.b8f3a1.png",
65+
"fixture/dist/_nuxt/icons/splash_iphonese_640x1136.b8f3a1.png",
66+
"fixture/dist/_nuxt/icons/splash_iphonex_1125x2436.b8f3a1.png",
67+
"fixture/dist/_nuxt/icons/splash_iphonexr_828x1792.b8f3a1.png",
68+
"fixture/dist/_nuxt/icons/splash_iphonexsmax_1242x2688.b8f3a1.png",
4969
"fixture/dist/_nuxt/manifest_test.webmanifest",
5070
"fixture/dist/_nuxt/node_modules",
5171
"fixture/dist/_nuxt/pages",

test/fixture/nuxt.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ module.exports = {
1717
fileName: 'manifest_test.[ext]?[hash]'
1818
},
1919

20+
meta: {
21+
nativeUI: true
22+
},
23+
2024
workbox: {
2125
offlineAnalytics: true,
2226
dev: true,

test/pwa.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('pwa', () => {
3131

3232
test('accessible icons', async () => {
3333
const { html } = await nuxt.renderRoute('/')
34-
expect(html).toContain('/_nuxt/icons/icon_512.b8f3a1.png')
34+
expect(html).toContain('/_nuxt/icons/icon_512x512.b8f3a1.png')
3535
})
3636

3737
test('icons purpose', () => {

0 commit comments

Comments
 (0)