diff --git a/packages/@vue/cli-plugin-pwa/README.md b/packages/@vue/cli-plugin-pwa/README.md index 8a65cfba79..a1cdb7b768 100644 --- a/packages/@vue/cli-plugin-pwa/README.md +++ b/packages/@vue/cli-plugin-pwa/README.md @@ -54,7 +54,7 @@ file, or the `"vue"` field in `package.json`. - Default: `'default'` - **pwa.assetsVersion** - + - Default: `''` This option is used if you need to add a version to your icons and manifest, against browser’s cache. This will append `?v=` to the URLs of the icons and manifest. @@ -65,6 +65,35 @@ file, or the `"vue"` field in `package.json`. The path of app’s manifest. +- **pwa.manifestOptions** + + - Default: `{}` + + The object will be used to generate the `manifest.json` + + If the following attributes are not defined in the object, the options of `pwa` or default options will be used instead. + - name: `pwa.name` + - short_name: `pwa.name` + - icons: + ```js + [ + { + src: './img/icons/android-chrome-192x192.png', + sizes: '192x192', + type: 'image/png' + }, + { + src: './img/icons/android-chrome-512x512.png', + sizes: '512x512', + type: 'image/png' + } + ] + ``` + - start_url: `'./index.html'` + - display: `'standalone'` + - background_color: `'#000000'` + - theme_color: `pwa.themeColor` + - **pwa.iconPaths** - Defaults: diff --git a/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js b/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js index 0f866e8c8a..c856f4c2da 100644 --- a/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js +++ b/packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js @@ -26,6 +26,28 @@ test('pwa', async () => { expect(project.has('dist/manifest.json')).toBe(true) expect(project.has('dist/img/icons/android-chrome-512x512.png')).toBe(true) + const manifest = await project.read('dist/manifest.json') + expect(JSON.parse(manifest)).toEqual({ + name: 'pwa-build', + short_name: 'pwa-build', + theme_color: '#4DBA87', + icons: [ + { + src: './img/icons/android-chrome-192x192.png', + sizes: '192x192', + type: 'image/png' + }, + { + src: './img/icons/android-chrome-512x512.png', + sizes: '512x512', + type: 'image/png' + } + ], + start_url: './index.html', + display: 'standalone', + background_color: '#000000' + }) + // Make sure the base preload/prefetch are not affected const index = await project.read('dist/index.html') diff --git a/packages/@vue/cli-plugin-pwa/generator/template/public/manifest.json b/packages/@vue/cli-plugin-pwa/generator/template/public/manifest.json deleted file mode 100644 index e1f44608f9..0000000000 --- a/packages/@vue/cli-plugin-pwa/generator/template/public/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "<%- rootOptions.projectName %>", - "short_name": "<%- rootOptions.projectName %>", - "icons": [ - { - "src": "./img/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "./img/icons/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": "./index.html", - "display": "standalone", - "background_color": "#000000", - "theme_color": "#4DBA87" -} diff --git a/packages/@vue/cli-plugin-pwa/index.js b/packages/@vue/cli-plugin-pwa/index.js index 91b52f68e2..fe334758f8 100644 --- a/packages/@vue/cli-plugin-pwa/index.js +++ b/packages/@vue/cli-plugin-pwa/index.js @@ -6,6 +6,7 @@ module.exports = (api, options) => { } const name = api.service.pkg.name + const assetsPublic = api.resolve('public') const userOptions = options.pwa || {} // the pwa plugin hooks on to html-webpack-plugin @@ -13,7 +14,8 @@ module.exports = (api, options) => { webpackConfig .plugin('pwa') .use(require('./lib/HtmlPwaPlugin'), [Object.assign({ - name + name, + assetsPublic }, userOptions)]) .after('html') diff --git a/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js b/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js index b5f060054d..0be798d2be 100644 --- a/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js +++ b/packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js @@ -7,7 +7,26 @@ const defaults = { appleMobileWebAppCapable: 'no', appleMobileWebAppStatusBarStyle: 'default', assetsVersion: '', - manifestPath: 'manifest.json' + manifestPath: 'manifest.json', + manifestOptions: {} +} + +const defaultManifest = { + icons: [ + { + "src": "./img/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "./img/icons/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + start_url: './index.html', + display: 'standalone', + background_color: "#000000" } const defaultIconPaths = { @@ -109,6 +128,62 @@ module.exports = class HtmlPwaPlugin { cb(null, data) }) + + + }) + + // generated manifest.json + compiler.hooks.emit.tapAsync(ID, (data, cb) => { + const { + name, + assetsPublic, + themeColor, + manifestPath, + manifestOptions + } = this.options + + const publicOptions = { + name, + short_name: name, + theme_color: themeColor + } + + const manifestFilePath = path.resolve(assetsPublic, manifestPath) + let fileManifest + try { + fileManifest = JSON.parse( + fs.readFileSync(manifestFilePath).toString() + ) + + // Check the generated manifest.json (without file) is identical to the existing one + const nofileManifest = Object.assign(publicOptions, defaultManifest, manifestOptions) + const isIdentical = !Object.keys(fileManifest).find((key) => { + return ( + JSON.stringify(fileManifest[key]) !== + JSON.stringify(nofileManifest[key]) + ) + }) + + // Throw info or warn + setTimeout(() => { + if (isIdentical) { + info(`You can safely delete the manifest.json redundant file.\nFile Path: ${manifestFilePath}`) + } else { + warn(`Recommend: Use pwa.manifestOptions instead of ${manifestFilePath}`) + } + }, 1000) + } catch (err) { + fileManifest = {} + } + + const outputManifest = JSON.stringify( + Object.assign(publicOptions, defaultManifest, fileManifest, manifestOptions) + ) + data.assets[manifestPath] = { + source: () => outputManifest, + size: () => outputManifest.length + } + cb(null, data) }) } }