goldhand/sw-precache-webpack-plugin

Updates on server

kslstn opened this issue · 1 comments

  • I'm submitting a bug report
  • I'm submitting a feature request
  • I'm submitting a support request

webpack version:
3.12

sw-precache-webpack-plugin version:
0.11.5

Please tell us about your environment:
MacOS/Linux

Browser: iOS Safari

Current behavior:
After an update to my application (can be as small as editing text), iOS shows a blank screen after refreshing the page/launching the add-to-homescreen web app. After clearing Safari's cache, the application works again.

Note that I have set up sw-precache to make the application work offline completely. The app runs completely in the client, so all assets can be cached.

Expected/desired behavior:
I expect the updated version to function. I am aware that would require the service worker to check for updates on the server and use the cached version when the server's not available. I don't know how to configure that though.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem along with your:

  • Webpack configuration:
    'use strict'
    const path = require('path')
    const utils = require('./utils')
    const webpack = require('webpack')
    const config = require('../config')
    const merge = require('webpack-merge')
    const baseWebpackConfig = require('./webpack.base.conf')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const ExtractTextPlugin = require('extract-text-webpack-plugin')
    const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
    const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
    var SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin')

const PUBLIC_PATH = 'https://www.bigtimer.net/'; // For SW Precache. webpack needs the trailing slash for output.publicPath

const env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
publicPath: PUBLIC_PATH, // For SW Precache.
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to false will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to true because we are seeing that sourcemaps are included in the codesplit bundle as well when it's false,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),

// copy custom static assets
new CopyWebpackPlugin([
  {
    from: path.resolve(__dirname, '../static'),
    to: config.build.assetsSubDirectory,
    ignore: ['.*']
  }
]),

// service worker caching
new SWPrecacheWebpackPlugin({
  cacheId: 'big-timer',
  filename: 'service-worker.js',
  staticFileGlobs: ['dist/**/*.{js,html,css}'],
  minify: true,
  navigateFallback: PUBLIC_PATH,
  stripPrefix: 'dist/',
  staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
})

]
})

if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')

webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}

if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

  • Generated service worker (not minified):
    'use strict'
    var precacheConfig = [
    ['index.html', '277d4fc013034f1ce06a422f6feba69e'],
    ['service-worker.js', 'dd58fb74f7d4ad37db7d0ed0ade9858d'],
    ['static/404.html', '1b8119d030279687e0fae58aad5456b5'],
    [
    'static/css/app.9f44cebdd85d64e5f7283ddd7d7020fa.css',
    'e3c073dabe268a54b64eea30dd70cefd'
    ],
    [
    'static/js/app.f2959bcb7abc904fbf43.js',
    '96240dc4b7ddc3955b983d1e68d59b8d'
    ],
    [
    'static/js/manifest.28eddcffd035f5f85795.js',
    '479b2f524fbd82c9ec2f28925b4e0521'
    ],
    [
    'static/js/vendor.25551477f25f30579d9c.js',
    '69af8d0745788253d601840a8caad28b'
    ]
    ],
    cacheName =
    'sw-precache-v3-big-timer-' +
    (self.registration ? self.registration.scope : ''),
    ignoreUrlParametersMatching = [/^utm_/],
    addDirectoryIndex = function(e, t) {
    var n = new URL(e)
    return '/' === n.pathname.slice(-1) && (n.pathname += t), n.toString()
    },
    cleanResponse = function(e) {
    return e.redirected
    ? ('body' in e ? Promise.resolve(e.body) : e.blob()).then(function(t) {
    return new Response(t, {
    headers: e.headers,
    status: e.status,
    statusText: e.statusText
    })
    })
    : Promise.resolve(e)
    },
    createCacheKey = function(e, t, n, r) {
    var a = new URL(e)
    return (
    (r && a.pathname.match(r)) ||
    (a.search +=
    (a.search ? '&' : '') +
    encodeURIComponent(t) +
    '=' +
    encodeURIComponent(n)),
    a.toString()
    )
    },
    isPathWhitelisted = function(e, t) {
    if (0 === e.length) return !0
    var n = new URL(t).pathname
    return e.some(function(e) {
    return n.match(e)
    })
    },
    stripIgnoredUrlParameters = function(e, t) {
    var n = new URL(e)
    return (
    (n.hash = ''),
    (n.search = n.search
    .slice(1)
    .split('&')
    .map(function(e) {
    return e.split('=')
    })
    .filter(function(e) {
    return t.every(function(t) {
    return !t.test(e[0])
    })
    })
    .map(function(e) {
    return e.join('=')
    })
    .join('&')),
    n.toString()
    )
    },
    hashParamName = '_sw-precache',
    urlsToCacheKeys = new Map(
    precacheConfig.map(function(e) {
    var t = e[0],
    n = e[1],
    r = new URL(t, self.location),
    a = createCacheKey(r, hashParamName, n, !1)
    return [r.toString(), a]
    })
    )
    function setOfCachedUrls(e) {
    return e
    .keys()
    .then(function(e) {
    return e.map(function(e) {
    return e.url
    })
    })
    .then(function(e) {
    return new Set(e)
    })
    }
    self.addEventListener('install', function(e) {
    e.waitUntil(
    caches
    .open(cacheName)
    .then(function(e) {
    return setOfCachedUrls(e).then(function(t) {
    return Promise.all(
    Array.from(urlsToCacheKeys.values()).map(function(n) {
    if (!t.has(n)) {
    var r = new Request(n, {credentials: 'same-origin'})
    return fetch(r).then(function(t) {
    if (!t.ok)
    throw new Error(
    'Request for ' +
    n +
    ' returned a response with status ' +
    t.status
    )
    return cleanResponse(t).then(function(t) {
    return e.put(n, t)
    })
    })
    }
    })
    )
    })
    })
    .then(function() {
    return self.skipWaiting()
    })
    )
    }),
    self.addEventListener('activate', function(e) {
    var t = new Set(urlsToCacheKeys.values())
    e.waitUntil(
    caches
    .open(cacheName)
    .then(function(e) {
    return e.keys().then(function(n) {
    return Promise.all(
    n.map(function(n) {
    if (!t.has(n.url)) return e.delete(n)
    })
    )
    })
    })
    .then(function() {
    return self.clients.claim()
    })
    )
    }),
    self.addEventListener('fetch', function(e) {
    if ('GET' === e.request.method) {
    var t,
    n = stripIgnoredUrlParameters(
    e.request.url,
    ignoreUrlParametersMatching
    )
    ;(t = urlsToCacheKeys.has(n)) ||
    ((n = addDirectoryIndex(n, 'index.html')), (t = urlsToCacheKeys.has(n)))
    !t &&
    'navigate' === e.request.mode &&
    isPathWhitelisted([], e.request.url) &&
    ((n = new URL('https://www.bigtimer.net/', self.location).toString()),
    (t = urlsToCacheKeys.has(n))),
    t &&
    e.respondWith(
    caches
    .open(cacheName)
    .then(function(e) {
    return e.match(urlsToCacheKeys.get(n)).then(function(e) {
    if (e) return e
    throw Error(
    'The cached response that was expected is missing.'
    )
    })
    })
    .catch(function(t) {
    return (
    console.warn(
    'Couldn't serve response for "%s" from cache: %O',
    e.request.url,
    t
    ),
    fetch(e.request)
    )
    })
    )
    }
    })

Mm I meant the title to be “Updates on server break application on iOS”