Giter VIP home page Giter VIP logo

gridsome-plugin-pwa's Introduction

@allanchain/gridsome-plugin-pwa

npm (scoped) License Run Tests codecov

⚠️ status: not so stable, contributions welcome

This is the docs for master (targeting upcoming [email protected]). For older releases, checkout v0.3.0, v0.2.5


Overview

This plugin is based on gridsome-plugin-pwa and @vue/cli-plugin-pwa, and it is created to be a better alternative. it serves manifest and no-op service worker in development, use similar config structure, just as vue-cli does

It tries to be more similar to cli-plugin-pwa, but makes use of gridsome's image processing power.

It uses jest, puppeteer and lighthouse for unit and e2e testing, to stabilize the plugin.

Installation

1. Add to Dependencies

You need register-service-worker to register service worker yourself.

npm install @allanchain/gridsome-plugin-pwa register-service-worker
# or
yarn add @allanchain/gridsome-plugin-pwa register-service-worker

2. Register as Gridsome Plugin

This plugin should work with zero config (you still nead step 3) if your favicon source image is at least 512x512

// gridsome.config.js
module.exports = {
  plugins: [
    {
      use: '@allanchain/gridsome-plugin-pwa',
      options: {}
    }
  ]
}

Checkout Options for detailed explanation of all options.

Example Configuration

You can also checkout example gridsome app.

generateSW mode:

{
  manifestOptions: {
    short_name: 'Gridsome',
    description: 'Gridsome is awesome!',
    display: 'standalone',
    gcm_sender_id: undefined,
    start_url: '/',
    categories: ['education'],
    lang: 'en-GB',
    dir: 'auto'
  },
  appleMobileWebAppStatusBarStyle: 'default',
  manifestPath: 'manifest.json',
  icon: 'src/favicon.png',
  msTileColor: '#00a672',
  workboxOptions: {
    cacheId: 'awesome-pwa',
    globPatterns: ['assets/css/*', '*.js', 'index.html'],
    skipWaiting: true
  }
}

injectManifest mode:

{
  workboxPluginMode: 'injectManifest',
  workboxOptions: {
    swSrc: './src/service-worker.js',
    globPatterns: ['assets/css/*', '*.js', 'index.html']
  }
}

3. Register service worker

You need manually register service worker, just as what you do in vue-cli, which gives you more power.

Create registerServiceWorker.js and import it in main.js

A good start point is vue-cli's template. src/registerServiceWorker.js:

/* eslint-disable no-console */

import { register } from 'register-service-worker'

register('/service-worker.js', {
  ready () {
    console.log(
      'App is being served from cache by a service worker.\n' +
      'For more details, visit https://goo.gl/AFskqB'
    )
  },
  registered () {
    console.log('Service worker has been registered.')
  },
  cached () {
    console.log('Content has been cached for offline use.')
  },
  updatefound () {
    console.log('New content is downloading.')
  },
  updated () {
    console.log('New content is available; please refresh.')
  },
  offline () {
    console.log('No internet connection found. App is running in offline mode.')
  },
  error (error) {
    console.error('Error during service worker registration:', error)
  }
})

src/main.js:

export default function (Vue, { router, head, isClient }) {
    if (isClient && process.env.NODE_ENV === 'production') {
      require('./registerServiceWorker')
    }
  // ...
}

Options

workboxPluginMode

Default: 'generateSW'

This allows you to the choose between the two modes supported by the underlying workbox-build.

  • 'generateSW' will lead to a new service worker file being created each time you rebuild your web app.

  • 'injectManifest' allows you to start with an existing service worker file, and creates a copy of that file with a "precache manifest" injected into it.

The "Which Plugin to Use?" guide can help you choose between the two modes.

workboxCompileSrc

Default: true

Only works in injectManifest mode. Compile your service-worker.js with webpack.

Will be applied to compilation if set to an array of webpack plugins.

workboxOptions

Default:

{
  modifyURLPrefix: { '': config.publicPath },
  globDirectory: config.outputDir,
  globPatterns: ['assets/css/*', '*.js'],
  swDest: path.join(config.outputDir, 'service-worker.js')
  sourcemap: false, // if generateSW
  cacheId: config.siteName // if generateSW
}

These options are passed on through to the underlying workbox-build.

For more information on what values are supported, please see the guide for generateSW or for injectManifest.

It is not recommended to precache all files, because your site can be large. Instead, precache important files and consider runtime caching for other files.

appShellPath

Default: null

⚠️ This is a very experimental feature, or rather, a proof of concept.

The relative file path from outputDir to the html file for app shell. Only makes sense when using navigation fallback.

For example:

  • Using generateSW mode and setting up navigateFallback:
    {
      appShellPath: 'offline/index.html',
      workboxOptions: {
        globPatterns: ['assets/css/*', '*.js', 'offline/index.html'],
        navigateFallback: '/gridsome/offline/index.html',
        navigateFallbackAllowlist: [/\/$/]
      }
    }
  • Using injectManifest mode and registering a NavigationRoute in service-worker.js.
    registerRoute(
      new NavigationRoute(createHandlerBoundToURL(APP_SHELL), {
        allowlist: [/\/$/]
      })
    )

You may also want to check out examples.

Sourced from gatsby-plugin-offline doc:

The app shell is a minimal amount of user interface that can be cached offline for reliable performance loading on repeat visits.

As for gridsome, it checks window.__INITIAL_STATE__ for data (mostly page query results), falling back to fetch data from json files. All this plugin does are disabling client side hydration in appShellPath html, and deleting window.__INITIAL_STATE__ in appShellPath html to tell gridsome to fetch data from json files. You should define app shell behavior in service-worker.js.

name

Default: config.siteName

Used as the value for the apple-mobile-web-app-title and application-name meta tags in the generated HTML.

themeColor

Default: '#00a672'

appleMobileWebAppCapable

Default: 'no'

This defaults to 'no' because iOS before 11.3 does not have proper PWA support. See this article for more details.

appleMobileWebAppStatusBarStyle

Default: 'default'

manifestPath

Default: 'manifest.json'

The path of app’s manifest. It will be prefixed with publicPath(e.g. '/', '/gridsome/') to generate the final manifest url. Different to vue-cli, currently you can only use the generated manifest.

manifestOptions

Default:

{
  start_url: '.',
  display: 'standalone',
  background_color: '#000000'
}

The object will be used to generate the manifest.json

If the following attributes are not defined in the object, default options will be used instead.

  • name: name
  • short_name: name
  • start_url: '.'
  • display: 'standalone'
  • theme_color: themeColor

icon

Default: your favicon, usually ./src/favicon.png

Note: you need a at least 512x512 image to generate every needed icon.

Or in detail
{
  androidChrome: [{
    src, // your favicon, usually `./src/favicon.png`
    sizes: [512, 384, 192, 144, 96, 72, 48],
    purpose: 'any',
    urls: null
  }],
  msTileImage: {
    src,
    size: 144,
    url: null
  },
  appleMaskIcon: {
    url: null
  }
}

You can use another icon file to generate icons of all sizes:

{
  icon: './src/my-icon.png'
}

It is a relative file path, not a relative URL.

Or you can configure Android Chrome (icons in manifest.json) icon file:

{
  icon: {
    androidChrome: './src/android.png'
  }
}

Also configure output sizes and maskable:

{
  icon: {
    androidChrome: {
      src: './src/maskable-icon.png',
      sizes: [512, 384, 192, 144, 96, 72, 48],
      purpose: 'maskable'
    }
  }
}

The above config will generate android-chrome-512x512.png, android-chrome-384x364.png... from ./src/my-icon.png, and mark them as maskable.

And it is also possible to use different source for 'maskable' and 'any':

{
  icon: {
    androidChrome: [
      {
        src: './src/my-icon.png',
        sizes: [512, 384, 192, 144, 96, 72, 48],
        purpose: 'any'
      },
      {
        src: './src/maskable-icon.png',
        sizes: [512, 384, 192, 144, 96, 72, 48],
        purpose: 'maskable'
      }
    ]
  }
}

Although it is possible to set purpose to 'maskable any', it is not recommended, as explained in Adaptive icon support in PWAs with maskable icons:

While you can specify multiple space-separated purposes like "any maskable", in practice you shouldn't. Using "maskable" icons as "any" icons is suboptimal as the icon is going to be used as-is, resulting in excess padding and making the core icon content smaller. Ideally, icons for the "any" purpose should have transparent regions and no extra padding, like your site's favicons, since the browser isn't going to add that for them.

If you don't want icons to be generated, provide URLs:

{
  icon: {
    androidChrome: {
      sizes: [512, 192],
      urls: ['/icons/512x512.png', '/icons/192x192.png']
    }
  }
}

msTileImage is similar to androidChrome, but only one icon. e.g.:

{
  icon: {
    msTileImage: {
      url: 'assets/icons/android-chrome-144x144.png'
    }
  }
}

appleMaskIcon is a square SVG image, with a transparent (or simply: no) background, and all vectors 100% black. It is not auto generated, and you should provide URL if you want to include it:

{
  icon: {
    appleMaskIcon: {
      url: '/safari-pinned-tab.svg'
    }
  }
}

msTileColor

Default: '#00a672'

appleMaskIconColor

Default: themeColor

Active color of appleMaskIcon

Developing and Testing

Yarn 2 is used starting from @allanchain/[email protected], making commands much simpler.

# Install for both root and example
yarn

Now you can make modifications to this plugin and run yarn develop in example project to see the effect.

Or run yarn test in root dir of this project to see test results.

LICENSE

MIT

gridsome-plugin-pwa's People

Contributors

allanchain avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

gridsome-plugin-pwa's Issues

Status of this project

I created this plugin, in another way (different from gridsome/gridsome#167), for convenient and organized PWA config, and the support for serving manifest during development (rishabh3112/gridsome-plugin-pwa#21).

Many things still suck :

  • config logic
  • test coverage (I mean real coverage, not line coverage 😄 )
  • it generates icons on every reload ( update: will be fixed in v0.4.0)

However, this plugin meets my own needs. And all over GitHub, no other repo depends on this plugin. I may fix some of problems, but I will no longer actively develop this plugin like this month.

And yes, feel free to create issues and PRs, and I will respond ASAP. I am glad that you like this plugin.

(Or leave a kind reaction / comment on this issue if you like this plugin)


Edit:

PWA for gridsome is harder than I thought. Plan to read gatsby-plugin-offline's source code and try creating app shell if I have time. Gridsome seems not support app shell because it reads query data from window.__INITIAL_STATE__...

Option to generate separate / unique maskable icon

Ideally maskable icon can be specified separately to a regular icon.

While you can specify multiple space-separated purposes like "any maskable", in practice you shouldn't. Using "maskable" icons as "any" icons is suboptimal as the icon is going to be used as-is, resulting in excess padding and making the core icon content smaller. Ideally, icons for the "any" purpose should have transparent regions and no extra padding, like your site's favicons, since the browser isn't going to add that for them.

Source: Adaptive icon support in PWAs with maskable icons

I recently submitted a pull request to gridsome-plugin-pwa to achieve this, I think it would be great to have this added here too. It looks like it can be handled similarly here with no impact on existing use, whereby the 'maskable' property of defaultIconConfig.androidChrome is expanded to allow for either a boolean or a string:

  • When maskable is false, the icons are generated with purpose 'any' (current behaviour)
  • When maskable is true, the icons are generated with purpose 'maskable any' (current behaviour)
  • When maskable is string, it is treated as a unique icon, to be generated with purpose 'maskable' (new feature)

This gives developers the freedom to have both 'regular' icons and 'maskable' icons generated and identified in the manifest, per recommendations for adaptive icon support in PWAs.

Refactor server API codes for unit testing

#1 has done simple e2e testing. We need more unit testing to make the project more stable.

grisome.client.js is easy to test. just pass options and head = [ link: [], meta: [] ].

We need extract some functions to ease unit testing from server API code.

For example, generateManifest, getWorkboxOptions

Caching external images

Hi @AllanChain
I have been trying to use the CacheFirst strategy for external image source i.e cloudinary. But getting:

The response for 'https://res.cloudinary.com/adapttive/image/upload/v1617880771/Unsolved_Mysteries_in_Magento2_Dark_2629a6feb5.png' is an opaque response. The caching strategy that you're using will not cache opaque responses by default.

Tried to add to service-worker.js , but same results.

import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';

registerRoute(
  ({url}) => url.origin === 'https://third-party.example.com' &&
             url.pathname.startsWith('/images/'),
  new CacheFirst({
    cacheName: 'image-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      })
    ]
  })
);

as suggested in https://developers.google.com/web/tools/workbox/modules/workbox-cacheable-response

Any suggestions? Thanks for your efforts.

Can't get InjectManifest to work

I've tried to use this plugin with the InjectManifest option:

// gridsome.config.js

module.exports = {
  plugins: [
   {
      use: '@allanchain/gridsome-plugin-pwa',
      options: {
        name: 'timee',
        theme: '#424242',
        icon: './src/favicon.png',
        workboxPluginMode: 'InjectManifest',
        workboxOptions: {
          swSrc: './src/service-worker.js',
          /*exclude: [
            '_redirects',
            'netlify.toml',
            /^img\/.+\.svg$/
          ]*/
        },
      }
    }
  ]
}
// service-worker.js

import { registerRoute, NavigationRoute } from 'workbox-routing';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';

precacheAndRoute(self.__WB_MANIFEST);

registerRoute(
  new NavigationRoute(createHandlerBoundToURL('index.html'))
);

The build is successful, but I get this error in the browser console:

Error during service worker registration: TypeError: Failed to register a ServiceWorker for scope ('http://localhost:5000/') with script ('http://localhost:5000/service-worker.js'): ServiceWorker script evaluation failed

And for some reason, index.html is not being precached.

Do you have a working example of how to configure the InjectManifest method?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.