Skip to content

Commit

Permalink
feat(icon): support for iOS splash screens (#308)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
dvarnai and pi0 authored Aug 13, 2020
1 parent aa4eace commit f4eeda7
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 44 deletions.
79 changes: 55 additions & 24 deletions lib/icon/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const path = require('path')
const { fork } = require('child_process')
const fs = require('fs-extra')
const hasha = require('hasha')
const { joinUrl, getRouteParams } = require('../utils')
const { joinUrl, getRouteParams, sizeName } = require('../utils')

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

_iconHash: null,
_cacheDir: null,
_icons: null,
_assetIcons: null
_assets: null,
_manifestIcons: null,
_iosSplash: null
}

// Merge options
Expand Down Expand Up @@ -91,8 +104,8 @@ async function findIcon (options) {

function addPlugin (options) {
const icons = {}
for (const icon of options._assetIcons) {
icons[icon.sizes] = icon.src
for (const asset of options._assets) {
icons[asset.name] = asset.target
}

if (options.accessibleIcons) {
Expand All @@ -112,37 +125,53 @@ async function generateIcons (options) {
if (!options.iconHash) {
options.iconHash = await hasha.fromFile(options.iconSrc).then(h => h.substring(0, 6))
}

// Resize cache dir
if (!options._cacheDir) {
options._cacheDir = path.join(__dirname, '.cache', options.iconHash)
}

// Generate _icons
options._icons = {}
for (const size of options.sizes) {
options._icons[size] = `${options.targetDir}/icon_${size}.${options.iconHash}.png`
}
// Icons to be emited by webpack
options._assets = []

// Generate _purpose
// Manifest icons
const purpose = options.purpose ? options.purpose.join(' ') : undefined
options._manifestIcons = []
for (const size of options.sizes) {
const name = sizeName(size)
const target = `${options.targetDir}/icon_${name}.${options.iconHash}.png`
options._assets.push({ name, target })
options._manifestIcons.push({
src: joinUrl(options.publicPath, target),
sizes: name,
type: 'image/png',
purpose
})
}

// Generate _assetIcons
options._assetIcons = options.sizes.map(size => ({
src: joinUrl(options.publicPath, options._icons[size]),
sizes: `${size}x${size}`,
type: 'image/png',
purpose
}))
// Generate _iosSplash
options._iosSplash = {}
for (const size of options.iosSizes) {
const name = sizeName(size)
const target = `${options.targetDir}/splash_${name}.${options.iconHash}.png`
options._assets.push({ name, target })
options._iosSplash[size[2]] = joinUrl(options.publicPath, target)
}
}

function addManifest (options, pwa) {
if (!pwa.manifest) {
pwa.manifest = {}
}

if (!pwa.manifest.icons) {
pwa.manifest.icons = []
}
pwa.manifest.icons.push(...options._manifestIcons)

pwa.manifest.icons.push(...options._assetIcons)
pwa._iosSplash = {
...options._iosSplash
}
}

function emitAssets (options) {
Expand All @@ -156,11 +185,10 @@ function emitAssets (options) {
apply (compiler) {
compiler.hooks.emit.tapPromise('nuxt-pwa-icon', async (compilation) => {
await resizePromise
await Promise.all(options.sizes.map(async (size) => {
const targetFilename = options._icons[size]
const srcFileName = path.join(options._cacheDir, `${size}.png`)
await Promise.all(options._assets.map(async ({ name, target }) => {
const srcFileName = path.join(options._cacheDir, `${name}.png`)
const src = await fs.readFile(srcFileName)
compilation.assets[targetFilename] = { source: () => src, size: () => src.length }
compilation.assets[target] = { source: () => src, size: () => src.length }
}))
})
}
Expand All @@ -181,7 +209,10 @@ async function resizeIcons (options) {
JSON.stringify({
input: options.iconSrc,
distDir: options._cacheDir,
sizes: options.sizes
sizes: [
...options.sizes,
...options.iosSizes
]
})
])
child.on('exit', (code) => {
Expand Down
9 changes: 6 additions & 3 deletions lib/icon/resize.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
const path = require('path')
const Jimp = require('jimp-compact')
const { normalizeSize, sizeName } = require('../utils')

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

await Promise.all(sizes.map((size) => {
const distFile = path.join(distDir, `${size}.png`)
// Icons
await Promise.all(sizes.map(normalizeSize).map((size) => {
const name = sizeName(size)
const distFile = path.join(distDir, `${name}.png`)
return new Promise((resolve) => {
inputFile.clone().contain(size, size).write(distFile, () => resolve())
inputFile.clone().contain(size[0], size[1]).write(distFile, () => resolve())
})
}))
}
Expand Down
1 change: 1 addition & 0 deletions lib/manifest/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function addManifest (pwa) {
description: process.env.npm_package_description,
publicPath,
icons: [],
iosSplash: [],
start_url: routerBase + '?standalone=true',
display: 'standalone',
background_color: '#ffffff',
Expand Down
11 changes: 10 additions & 1 deletion lib/meta/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,16 @@ function generateMeta (pwa) {

// Launch Screen Image (IOS)
if (options.mobileAppIOS && !find(this.options.head.link, 'rel', 'apple-touch-startup-image')) {
this.options.head.link.push({ rel: 'apple-touch-startup-image', href: iconBig.src })
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' })
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' })
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' })
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' })
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' })
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' })
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' })
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' })
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' })
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' })
}
}

Expand Down
22 changes: 21 additions & 1 deletion lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ function joinUrl (...args) {
return path.join(...args).replace(':/', '://')
}

function normalizeSize (size) {
if (!Array.isArray(size)) {
size = [size, size]
}
if (size.length === 1) {
size = [size, size]
} else if (size.length === 0) {
size = 64
}
return size
}

function sizeName (size) {
size = normalizeSize(size)
const prefix = size[2] ? (size[2] + '_') : ''
return prefix + size[0] + 'x' + size[1]
}

function getRouteParams (options) {
// routerBase
const routerBase = options.router.base
Expand Down Expand Up @@ -39,5 +57,7 @@ module.exports = {
isUrl,
joinUrl,
getRouteParams,
startCase
startCase,
normalizeSize,
sizeName
}
48 changes: 34 additions & 14 deletions test/__snapshots__/pwa.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,23 @@ Array [
"fixture/.nuxt/dist/client",
"fixture/.nuxt/dist/client/LICENSES",
"fixture/.nuxt/dist/client/icons",
"fixture/.nuxt/dist/client/icons/icon_120.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_144.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_152.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_192.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_384.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_512.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_64.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_120x120.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_144x144.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_152x152.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_192x192.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_384x384.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_512x512.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/icon_64x64.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_ipad_1536x2048.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_ipadpro10_1668x2224.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_ipadpro12_2048x2732.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_ipadpro9_1536x2048.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_iphone6_50x1334.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_iphoneplus_1080x1920.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_iphonese_640x1136.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_iphonex_1125x2436.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_iphonexr_828x1792.b8f3a1.png",
"fixture/.nuxt/dist/client/icons/splash_iphonexsmax_1242x2688.b8f3a1.png",
"fixture/.nuxt/dist/client/manifest_test.webmanifest",
"fixture/.nuxt/dist/client/node_modules",
"fixture/.nuxt/dist/client/pages",
Expand All @@ -39,13 +49,23 @@ Array [
"fixture/dist/_nuxt",
"fixture/dist/_nuxt/LICENSES",
"fixture/dist/_nuxt/icons",
"fixture/dist/_nuxt/icons/icon_120.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_144.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_152.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_192.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_384.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_512.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_64.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_120x120.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_144x144.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_152x152.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_192x192.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_384x384.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_512x512.b8f3a1.png",
"fixture/dist/_nuxt/icons/icon_64x64.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_ipad_1536x2048.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_ipadpro10_1668x2224.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_ipadpro12_2048x2732.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_ipadpro9_1536x2048.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_iphone6_50x1334.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_iphoneplus_1080x1920.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_iphonese_640x1136.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_iphonex_1125x2436.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_iphonexr_828x1792.b8f3a1.png",
"fixture/dist/_nuxt/icons/splash_iphonexsmax_1242x2688.b8f3a1.png",
"fixture/dist/_nuxt/manifest_test.webmanifest",
"fixture/dist/_nuxt/node_modules",
"fixture/dist/_nuxt/pages",
Expand Down
4 changes: 4 additions & 0 deletions test/fixture/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ module.exports = {
fileName: 'manifest_test.[ext]?[hash]'
},

meta: {
nativeUI: true
},

workbox: {
offlineAnalytics: true,
dev: true,
Expand Down
2 changes: 1 addition & 1 deletion test/pwa.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('pwa', () => {

test('accessible icons', async () => {
const { html } = await nuxt.renderRoute('/')
expect(html).toContain('/_nuxt/icons/icon_512.b8f3a1.png')
expect(html).toContain('/_nuxt/icons/icon_512x512.b8f3a1.png')
})

test('icons purpose', () => {
Expand Down

0 comments on commit f4eeda7

Please sign in to comment.