diff --git a/build.js b/build.js index 4bf2024..6f07e85 100644 --- a/build.js +++ b/build.js @@ -5,7 +5,9 @@ const shell = require('shelljs') shell.rm('-rf', 'dist') require('webpack')(require('./webpack.config.js'), (err, stats) => { - if (err) throw err + if (err) { + throw err + } process.stdout.write(stats.toString({ colors: true, modules: false, diff --git a/package.json b/package.json index f0781fa..a9274b2 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "vuex-router-sync": "^3.0.0" }, "devDependencies": { + "appcache-webpack-plugin": "^1.3.0", "autoprefixer": "^6.4.0", "babel-core": "^6.16.0", "babel-helper-vue-jsx-merge-props": "^2.0.1", @@ -55,6 +56,7 @@ "postcss-import": "^9.1.0", "postcss-loader": "^0.9.1", "raw-loader": "^0.5.1", + "serviceworker-webpack-plugin": "^0.1.8", "shelljs": "^0.7.6", "style-loader": "^0.13.1", "svg-sprite-loader": "^0.2.0", diff --git a/src/components/DeckFloatButton.vue b/src/components/DeckFloatButton.vue index af6e6e3..202904a 100644 --- a/src/components/DeckFloatButton.vue +++ b/src/components/DeckFloatButton.vue @@ -57,7 +57,7 @@ export default { diff --git a/src/index.js b/src/index.js index 22904ef..e5704d9 100644 --- a/src/index.js +++ b/src/index.js @@ -30,3 +30,8 @@ $get('./CardInfo.json') }) }, 1000) }) + +import runtime from 'serviceworker-webpack-plugin/lib/runtime' +if ('serviceWorker' in navigator) { + runtime.register() +} diff --git a/src/index.tpl b/src/index.tpl index 70bfca5..2eab931 100644 --- a/src/index.tpl +++ b/src/index.tpl @@ -1,5 +1,5 @@ - + diff --git a/src/pages/Deck.vue b/src/pages/Deck.vue index 5cc3efd..c89b0f7 100644 --- a/src/pages/Deck.vue +++ b/src/pages/Deck.vue @@ -3,6 +3,7 @@ import { mapState, mapGetters } from 'vuex' import { AppHeader, HeaderIcon, HeaderMenu } from 'components/AppHeader' import DeckModals from 'components/DeckModals' import DeckFloatButton from 'components/DeckFloatButton' +import UpdateStatusBar from 'components/UpdateStatusBar' import Cell from 'components/Cell' import Block from 'components/Block' import DeckHead from 'components/DeckHead' @@ -20,6 +21,7 @@ export default { HeaderMenu, DeckModals, DeckFloatButton, + UpdateStatusBar, Cell, Block, DeckHead, @@ -203,9 +205,25 @@ export default { - + + +
+
+ +
+
+ +
+
+ + + @@ -225,4 +243,14 @@ export default { .block { width: 20%; } - \ No newline at end of file +.bottom { + position: fixed; + left: 0; + right: 0; + bottom: 0; + + & > div { + position: relative; + } +} + diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000..bedd47e --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,105 @@ +'use strict' + +let hash = self.serviceWorkerOption.hash +let assets = self.serviceWorkerOption.assets +let staticAssets = assets.filter(asset => !asset.endsWith('html')) +let dynamicAssets = assets.filter(asset => asset.endsWith('html')) + .concat(['./', './CardInfo.json']) + +console.group(`Service worker ${hash} starts up.`) + +console.group('staticAssets') +console.log(staticAssets.join('\n')) +console.groupEnd() + +console.group('dynamicAssets') +console.log(dynamicAssets.join('\n')) +console.groupEnd() + +console.groupEnd() + +self.addEventListener('install', event => { + console.log('Installing...') + // Prefetch + event.waitUntil( + Promise.all([ + caches.open('static') + .then(cache => cache.addAll(staticAssets)), + caches.open(`dynamic ${hash}`) + .then(cache => cache.addAll(dynamicAssets)) + ]) + .then(() => { + console.log('Installed.') + }) + .then(() => { + if (self.skipWaiting) { + return self.skipWaiting() + } + }) + ) +}) + +self.addEventListener('activate', event => { + console.log('Activating...') + // Remove old caches + event.waitUntil( + // Remove outdated dynamic assets + caches.keys() + .then(keys => { + console.group('Delete outdated dynamic cache') + return Promise.all(keys.map(key => { + if (key.startsWith('dynamic ') && key !== `dynamic ${hash}`) { + console.log(key) + return caches.delete(key) + } + })) + .then(() => { + console.groupEnd() + }) + }) + .then(() => { + // Remove no longer used static assets + let absoluteUrls = staticAssets.map(relatedUrl => { + return (new URL(relatedUrl, self.location)).href + }) + return caches.open('static').then(cache => { + return cache.keys().then(requests => { + console.group('Delete unused static assets') + return Promise.all(requests.map(request => { + if (!absoluteUrls.includes(request.url)) { + console.log(request.url) + return cache.delete(request) + } + })) + .then(() => { + console.groupEnd() + }) + }) + }) + }) + .then(() => { + if (self.clients.claim) { + return self.clients.claim() + } + }) + .then(() => { + console.log('Activated.') + }) + ) +}) + +self.addEventListener('fetch', event => { + let request = event.request + + return event.respondWith( + caches.match(request) + .then(response => { + if (response) { + console.log(`From cache: ${request.url}`) + return response + } + // console.log(`From network: ${request.url}`) + return fetch(request) + }) + ) +}) diff --git a/webpack.config.js b/webpack.config.js index 73a49f6..60b49fd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -166,11 +166,33 @@ config.base = { new webpack.optimize.CommonsChunkPlugin({ name: 'vue', }), + new webpack.optimize.CommonsChunkPlugin({ + name: 'meta', + chunks: ['vue'], + }), new html({ title: 'test', filename: 'index.html', template: PATHS.template, }), + new (require('appcache-webpack-plugin'))({ + cache: ['CardInfo.json'], + exclude: ['index.html'], + output: 'webxoss.appcache', + }), + new (require('serviceworker-webpack-plugin'))({ + entry: './src/service-worker.js', + filename: 'service-worker.js', + excludes: ['**/*.', '**/*.map', '**/*.appcache', '**/*.hot-update.*'], + template: option => { + // add hash + let hash = require('crypto').createHash('sha256') + hash.update(JSON.stringify(option)) + hash.update(require('fs').readFileSync('./src/service-worker.js')) + option.hash = hash.digest('base64').slice(0, 7) + return Promise.resolve('') + }, + }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false,