diff --git a/packages/workbox/index.js b/packages/workbox/index.js index 0853c5ad..5482c37a 100755 --- a/packages/workbox/index.js +++ b/packages/workbox/index.js @@ -1,76 +1,149 @@ const path = require('path') const swBuild = require('workbox-build') +const { readFileSync, writeFileSync } = require('fs') +const hashSum = require('hash-sum') +const debug = require('debug')('nuxt:pwa:workbox') const fixUrl = url => url.replace(/\/\//g, '/').replace(':/', '://') const isUrl = url => url.indexOf('http') === 0 || url.indexOf('//') === 0 -module.exports = function nuxtWorkbox (options) { +// ============================================= +// workboxModule +// ============================================= + +module.exports = function nuxtWorkbox (moduleOptions) { + const options = Object.assign({}, moduleOptions, this.options.workbox) + const ctx = { options } + if (this.options.dev) { return } - // routerBase and publicPath + this.nuxt.plugin('build', builder => { + debug('Adding workbox') + getRouterBase.call(this, ctx) + workboxInject.call(this, ctx) + emitAssets.call(this, ctx) + addTemplates.call(this, ctx) + }) +} + +// ============================================= +// getRouterBase +// ============================================= + +function getRouterBase (ctx) { const routerBase = this.options.router.base let publicPath = fixUrl(`${routerBase}/${this.options.build.publicPath}`) if (isUrl(this.options.build.publicPath)) { // CDN publicPath = this.options.build.publicPath if (publicPath.indexOf('//') === 0) { - publicPath = '/' + publicPath // escape fixUrl + publicPath = '/' + publicPath // Escape fixUrl } } - const swFileName = 'sw.js' + ctx.routerBase = routerBase + ctx.publicPath = publicPath +} + +// ============================================= +// addTemplates +// ============================================= + +function addTemplates (ctx) { + // Add sw.template.js + this.addTemplate({ + src: path.resolve(__dirname, 'templates/sw.template.js'), + fileName: 'sw.template.js', + options: { + importPath: ctx.wbDst, + wb: ctx.workboxOptions + } + }) + + // Add sw.plugin.js + const swURL = `${ctx.routerBase}/${ctx.options.swURL || 'sw.js'}` + this.addPlugin({ + src: path.resolve(__dirname, 'templates/sw.plugin.js'), + ssr: false, + fileName: 'sw.plugin.js', + options: { + swURL: fixUrl(swURL), + swScope: fixUrl(`${ctx.routerBase}/`) + } + }) +} - // Use swBuild to generate sw file - // We set dest to static dir that is served as / to allow global sw scope +// ============================================= +// emitAssets +// ============================================= + +function emitAssets (ctx) { + const assets = [] + const emitAsset = (path, name, ext = 'js') => { + const source = readFileSync(path) + const hash = hashSum(source) + const dst = `${name}.${hash}.${ext}` + assets.push({source, dst}) + return dst + } + + // Write assets after build + this.nuxt.plugin('build', builder => { + builder.plugin('built', () => { + assets.forEach(({source, dst}) => { + writeFileSync(path.resolve(this.options.buildDir, 'dist', dst), source, 'utf-8') + }) + }) + }) + + // workbox.js + let wbPath = require.resolve('workbox-sw') + if (ctx.options.dev) { + wbPath = wbPath.replace(/prod/g, 'dev') + } + ctx.wbDst = fixUrl(ctx.publicPath + '/' + emitAsset(wbPath, 'workbox' + (ctx.options.dev ? '.dev' : ''))) +} + +// ============================================= +// workboxInject +// ============================================= + +function workboxInject (ctx) { // https://workboxjs.org/reference-docs/latest/module-workbox-build.html#.generateSW - const workboxOptions = Object.assign({ - swDest: path.resolve(this.options.srcDir, 'static', swFileName), + ctx.workboxOptions = Object.assign({ + swSrc: path.resolve(this.options.buildDir, 'sw.template.js'), + swDest: path.resolve(this.options.srcDir, 'static', 'sw.js'), directoryIndex: '/', cacheId: process.env.npm_package_name + '_' + process.env.npm_package_version, clientsClaim: true, globPatterns: ['**/*.{js,css}'], globDirectory: path.resolve(this.options.buildDir, 'dist'), modifyUrlPrefix: { - '': fixUrl(publicPath) + '': fixUrl(ctx.publicPath) }, runtimeCaching: [ // Cache routes if offline { - urlPattern: fixUrl(routerBase + '/**'), + urlPattern: fixUrl(ctx.routerBase + '/**'), handler: 'networkFirst' }, // Cache other _nuxt resources runtime // They are hashed by webpack so are safe to loaded by cacheFirst handler { - urlPattern: fixUrl(publicPath + '/**'), + urlPattern: fixUrl(ctx.publicPath + '/**'), handler: 'cacheFirst' } ] - }, options) + }, ctx.options) - // Use nuxt plugins to prevent race conditions with webpack plugin - // (https://github.com/nuxt-community/modules/issues/110) this.nuxt.plugin('build', builder => { builder.plugin('built', () => { - if (workboxOptions.swSrc) { - return swBuild.injectManifest(workboxOptions) - } else { - return swBuild.generateSW(workboxOptions) - } + const opts = Object.assign({}, ctx.workboxOptions) + delete opts.runtimeCaching + return swBuild.injectManifest(opts) }) }) - - // Register runtime plugin - const swURL = `${routerBase}/${options.swURL || swFileName}` - this.addPlugin({ - src: path.resolve(__dirname, 'plugin.js'), - ssr: false, - options: { - swURL: fixUrl(swURL), - swScope: fixUrl(`${routerBase}/`) - } - }) } module.exports.meta = require('./package.json') diff --git a/packages/workbox/plugin.js b/packages/workbox/templates/sw.plugin.js similarity index 100% rename from packages/workbox/plugin.js rename to packages/workbox/templates/sw.plugin.js diff --git a/packages/workbox/templates/sw.template.js b/packages/workbox/templates/sw.template.js new file mode 100644 index 00000000..7db599e0 --- /dev/null +++ b/packages/workbox/templates/sw.template.js @@ -0,0 +1,13 @@ +importScripts('<%= options.importPath %>') + +const workboxSW = new self.WorkboxSW({ + cacheId: '<%= options.wb.cacheId %>', + clientsClaim: <%= options.wb.clientsClaim %>, + directoryIndex: '<%= options.wb.directoryIndex %>' +}) + +workboxSW.precache([]) + +<% options.wb.runtimeCaching.forEach(r => { %> +workboxSW.router.registerRoute('<%= r.urlPattern %>', workboxSW.strategies.<%= r.handler %>({}), 'GET') +<% }) %>