PWA splash screen and icon generator

PWA splash screen and icon generator

Motivation and usage examples of pwa-asset-generator - a library that automates icon and splash screen image generation.

Long story short; while experimenting with ideas on Puppeteer for my next talk, I found myself building an open source CLI tool — pwa-asset-generator! :)

It automatically generates splash screen and icon images for your Progressive Web App in order to provide native-like user experiences on multiple platforms. It also updates your index.html and manifest.json files to declare generated assets to your PWA.

I’d like to get into details of my motivation for building such a library along with some advanced usage examples in this article.

A basic usage demo of pwa-asset-generator:

A basic usage demo of [**pwa-asset-generator**]

I’ll explain why such a library is needed and how it’s compared to some other existing options of generating assets for your PWA on the following chapters.

Feel free to jump to the final chapter of the article ⏬ to see features and advanced usage examples of the library.

Why do we need such a library anyway?

When you build a PWA with the goal of providing native-like user experiences on multiple platforms and stores, you need to meet the criteria of those platforms and stores with your PWA assets; icons and splash screens. Such criteria are;

Once you use icons with required sizes as part of Web App Manifest API, you don’t need to provide additional splash screen images for above platforms. They are automatically generated for you.

Criteria for iOS

Complicated. Currently, iOS doesn’t support Web App Manifest API specs, although the spec is in development— track the progress here. It’s also not clear yet if Apple will change its approach for displaying splash screens during standards implementation, as splash screens are not part of Web App Manifest specs. So far, the only way to setup icons and splash screens for your PWA on iOS is, adding special HTML tags.

A special HTML link tag with rel apple-touch-icon is required to provide icons for your PWA when it's added to the user’s home screen. Read more about it on Apple's Icon Guidelines and Safari Web Content Guide.

Example icon specs from Apple’s Icon Guidelines:

Example icon specs from Apple’s Icon Guidelines

Another special HTML link tag with rel apple-touch-startup-image is required if you also would like to provide splash screens for your PWA. iOS will display those screens when your PWA is being opened as well as when it's in the background. So far so good!

But, there’s a catch here: you need to create a splash screen image for each and every resolution on Apple’s Launch Screen Guidelines and add an HTML tag with media attribute for each device resolution and orientation 🙀! Unfortunately, this requirement is not documented on Safari Web Content Guide sufficiently.

Example link tag for just one resolution & orientation pair;

<link rel="apple-touch-startup-image" href="temp/apple-splash-2048-2732.png" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">

Example splash screen specs from Apple’s Launch Screen Guidelines

Example splash screen specs from Apple’s Launch Screen Guidelines

💡 Creating icon and splash screen images for all the platforms, maintaining sizes and quality for all and adding HTML tags for each image can be overwhelming. So, why not automate it? 🤖

Drawbacks of existing solutions

When I decided to build such a CLI library, there were already a couple of options out there to generate assets for PWAs and meta associated with them. They might as well be a good fit for your needs but I think it’s wise to get to know the drawbacks of them when deciding what to use.

Some of the drawbacks that I noticed when I used them are;

Runtime dependency

One of the options to generate and use PWA assets is using PWACompat. I think it’s a good option to maintain a standard splash screen look and feel across platforms. However, it uses an approach to generate PWA images on runtime (iOS) and store them in the session storage of your browser. Keep in mind that, this approach not only affects the initialization performance of your PWA, it also takes over the ownership of your manifest.json file and your assets.

Brings maintenance costs

Some other options to generate and use PWA assets are online tools like AppScope splash screen generator or PWABuilder. These tools individually generate either icons OR splash screens and there isn’t any online tool that generates both asset types. They also come with maintenance costs such as;

  • Unzipping content and re-locating the assets
  • Updating manifest.json or index.html files manually
  • Keeping an eye on the standards and guidelines to keep your PWA compatible with all device types and platforms

And, doing this manual work again and again.

Difficult to automate

Another drawback of using online tools is that they are difficult to automate. An ideal scenario of keeping your PWA assets up to date and compatible with all platforms in an automated way would be integrating pwa-asset-generator to your build steps on your favorite CI. It can generate all the assets and update your index.html and manifest.json files with associated meta based on the latest platform specifications.

Lacks flexibility and ownership on the content

You’re not in control of how your assets are generated. None of the existing solutions provide full control of how your assets are generated. PWACompat doesn’t provide any control at all, it automatically generates all splash screens in the same form. And, online tools provide limited customization options when you generate your assets. pwa-asset-generator gives you full control of your assets via HTML inputs. I will describe this in detail in the next chapter.

Features and usage of the lib

PWA Asset Generator automates the image generation in a creative way. Having Puppeteer at its core enables lots of possibilities.

— Generates both icons and splash screens with optional --icon-only --splash-only --landscape-only and --portrait-only flags ✨

— Updates your manifest.json and index.html files automatically for declaring generated image assets 🙌

— Scrapes latest specs from Apple Human Interface guidelines website via Puppeteer to make your PWA ready for all/recent iOS devices out there 🤖

  • Supports offline mode and uses static spec data when things go wrong with scraping 📴
  • Updates static spec data before each release automatically and monitors spec changes everyday 🔄

— Uses the Chrome browser as it’s a canvas of your fav image editor. It uses a shell HTML file as an artboard and centers your logo before taking screenshots for each resolution via Puppeteer 🤖

— You can provide your source in multiple formats; a local image file, a local HTML file, a remote image or HTML file 🙌

  • When it’s an image source, it is centered over the background option you provide 🌅
  • When it’s an HTML source, you can go as creative as you like; position your logo, use SVG filters, use variable fonts, use gradient backgrounds, use typography and etc. Your HTML file is rendered on Chrome before taking screenshots for each resolution 🎨

— It uses puppeteer-core instead of puppeteer and only installs Chromium if it doesn’t exist on the system. Saves waste of ~110–150mb of disk space and many seconds from the world per each user 🌎️️⚡️

— Supports dark mode splash screens on iOS! So, you can provide both light 🌕 and dark 🌚 splash screen images to differentiate your apps look & feel based on user preference 🌙

Examples

  1. Basic usage with local PNG input, skips scraping specs, generating both splash screens and icons;
npx pwa-asset-generator ./img/logo.png --background "#ababab" --scrape false

Basic usage with local PNG input

2. Advanced usage with remote SVG input, using a custom gradient background 🏳️‍🌈, generating icons only;

npx pwa-asset-generator https://animejs.com/documentation/assets/img/icons/icon-github.svg ./temp -b "linear-gradient(180deg, #f00000, #f00000 16.67%, #ff8000 16.67%, #ff8000 33.33%, #ffff00 33.33%, #ffff00 50%, #007940 50%, #007940 66.67%, #4040ff 66.67%, #4040ff 83.33%, #a000c0 83.33%, #a000c0) fixed" --icon-only

Advanced usage with remote SVG input

💡 One can introduce a build step on CI to automate differentiating the branding of a PWA on special occasions. Wouldn’t it be awesome?

3. Advanced usage with custom HTML input, using background tile and custom variable web-font in it, generating splash screens only, outputting JPEG file type with 70% quality, with code output saved to your PWA’s index.html file;

# The local nyan.html can be a remote url as well  
# npx pwa-asset-generator https://ny.an/nyan.html
npx pwa-asset-generator nyan.html ./src/app/temp --splash-only --type=jpeg --quality=70 --index ./src/app/index.html

Advanced usage with custom HTML input

If you’re curious to see how such an HTML is built, here’s the Gist.

💡 When HTML input is used, you gain full control on your assets 👩‍💻 You can use any sort of web technology to render the content of your assets. Some might include but not limited to SVG filters, css media queries, variable fonts, css image filters, gradient backgrounds, and image tiles 👩‍🎨

4. Generating both light and dark mode splash screens for iOS, outputting JPEG file type with 80% quality, with code output saved to your PWA’s index.html file;

npx pwa-asset-generator light-logo.svg ./assets --dark-mode --background dimgrey --splash-only --type jpeg --quality 80 --index ./src/app/index.html
npx pwa-asset-generator dark-logo.svg ./assets --background lightgray --splash-only --type jpeg --quality 80 --index ./src/app/index.html

Video demonstration of dark mode splash screens on iOS:

BONUS: Under the hood

For curious minds reading up to this point, I’d like to share a simple yet powerful concept that library uses under the hood.

As I mentioned earlier, using Puppeteer under the hood opens a door of possibilities. A key feature of Puppeteer that pwa-asset-generator uses, is its screenshot API.

Puppeteer allows saving screenshots in both PNG and JPEG file types with optional compression possibility for JPEG images.

Every time you execute a command with pwa-asset-generator, library scrapes latest iOS specs from 2 sources. Then it opens new tabs rendering the content of shell HTML, based on CLI parameters provided. Here’s the shell HTML content;

<!DOCTYPE html>  
<html>  
<head>  
  <meta name="viewport" content="width=device-width, initial-scale=1">  
  <style>  
    body {  
      margin: 0;  
      background: --background;  
      height: 100vh;  
      padding: --padding;  
      box-sizing: border-box;  
    }  
    img {  
      width: 100%;  
      height: 100%;  
      margin: 0 auto;  
      object-fit: contain;      
    }  
  </style>  
</head>  
<body>  
<img src="--image-input">  
</body>  
</html>

It opens a new browser tab that renders shell HTML file contents via Puppeteer for each icon and splash screen size, and finally saves a screenshot of each.

// You can see the complete source here: https://github.com/onderceylan/pwa-asset-generator/blob/master/src/helpers/puppets.ts

// You can see the complete source here: https://github.com/onderceylan/pwa-asset-generator/blob/master/src/helpers/puppets.ts
const saveImages = async (
  imageList: Image[],
  source: string,
  output: string,
  options: Options,
  browser: Browser,
): Promise<SavedImage[]> => {
  let address: string;
  let shellHtml: string;

  const logger = preLogger(saveImages.name, options);
  logger.log('Initialising puppeteer to take screenshots', '🤖');

  if (canNavigateTo(source)) {
    address = await url.getAddress(source, options);
  } else {
    shellHtml = await url.getShellHtml(source, options);
  }

  return Promise.all(
    imageList.map(async ({ name, width, height, scaleFactor, orientation }) => {
      const { type, quality } = options;
      const path = file.getImageSavePath(name, output, type);

      try {
        const page = await browser.newPage();
        await page.setViewport({ width, height });

        if (address) {
          await page.goto(address);
        } else {
          await page.setContent(shellHtml);
        }

        await page.screenshot({
          path,
          omitBackground: !options.opaque,
          type: options.type,
          ...(type !== 'png' ? { quality } : {}),
        });

        await page.close();

        logger.success(`Saved image ${name}`);

        return { name, width, height, scaleFactor, path, orientation };
      } catch (e) {
        logger.error(e.message);
        throw Error(`Failed to save image ${name}`);
      }
    }),
  );
};

I was inspired by this blog post initially for the idea of saving screenshots of a resized responsive web page, thanks for the inspiration Dominik!

⚡️ Unlike Puppeteer, pwa-asset-generator uses system browser first approach using chrome-launcher with puppeteer-core and it only installs chromium if it’s not installed on execution environment.

Since the library uses Puppeteer in headless mode, you don’t see what’s happening under the hood. To make it visible, here’s a demonstration of the process, by disabling the headless mode in Puppeteer launch options;

Support

If you like the library, I appreciate your star on GitHub and your like/retweet and feedback on Twitter. If you’re a blogger too, it’d be awesome to see a reference to it on your blogs related to PWAs.

If you enjoyed my article, follow me here and on Twitter to subscribe to my future posts and projects! Cheers!