HtmlPwaPlugin.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. const ID = 'vue-cli:pwa-html-plugin'
  2. const defaults = {
  3. name: 'PWA app',
  4. themeColor: '#4DBA87', // The Vue color
  5. msTileColor: '#000000',
  6. appleMobileWebAppCapable: 'no',
  7. appleMobileWebAppStatusBarStyle: 'default',
  8. assetsVersion: '',
  9. manifestPath: 'manifest.json',
  10. manifestOptions: {},
  11. manifestCrossorigin: undefined
  12. }
  13. const defaultManifest = {
  14. icons: [
  15. {
  16. 'src': './img/icons/android-chrome-192x192.png',
  17. 'sizes': '192x192',
  18. 'type': 'image/png'
  19. },
  20. {
  21. 'src': './img/icons/android-chrome-512x512.png',
  22. 'sizes': '512x512',
  23. 'type': 'image/png'
  24. },
  25. {
  26. 'src': './img/icons/android-chrome-maskable-192x192.png',
  27. 'sizes': '192x192',
  28. 'type': 'image/png',
  29. 'purpose': 'maskable'
  30. },
  31. {
  32. 'src': './img/icons/android-chrome-maskable-512x512.png',
  33. 'sizes': '512x512',
  34. 'type': 'image/png',
  35. 'purpose': 'maskable'
  36. }
  37. ],
  38. start_url: '.',
  39. display: 'standalone',
  40. background_color: '#000000'
  41. }
  42. const defaultIconPaths = {
  43. favicon32: 'img/icons/favicon-32x32.png',
  44. favicon16: 'img/icons/favicon-16x16.png',
  45. appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png',
  46. maskIcon: 'img/icons/safari-pinned-tab.svg',
  47. msTileImage: 'img/icons/msapplication-icon-144x144.png'
  48. }
  49. module.exports = class HtmlPwaPlugin {
  50. constructor (options = {}) {
  51. const iconPaths = Object.assign({}, defaultIconPaths, options.iconPaths)
  52. delete options.iconPaths
  53. this.options = Object.assign({ iconPaths: iconPaths }, defaults, options)
  54. }
  55. apply (compiler) {
  56. compiler.hooks.compilation.tap(ID, compilation => {
  57. compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync(ID, (data, cb) => {
  58. // wrap favicon in the base template with IE only comment
  59. data.html = data.html.replace(/<link rel="icon"[^>]+>/, '<!--[if IE]>$&<![endif]-->')
  60. cb(null, data)
  61. })
  62. compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, (data, cb) => {
  63. const {
  64. name,
  65. themeColor,
  66. msTileColor,
  67. appleMobileWebAppCapable,
  68. appleMobileWebAppStatusBarStyle,
  69. assetsVersion,
  70. manifestPath,
  71. iconPaths,
  72. manifestCrossorigin
  73. } = this.options
  74. const { publicPath } = compiler.options.output
  75. const assetsVersionStr = assetsVersion ? `?v=${assetsVersion}` : ''
  76. // Favicons
  77. if (iconPaths.favicon32 != null) {
  78. data.head.push(makeTag('link', {
  79. rel: 'icon',
  80. type: 'image/png',
  81. sizes: '32x32',
  82. href: getTagHref(publicPath, iconPaths.favicon32, assetsVersionStr)
  83. }))
  84. }
  85. if (iconPaths.favicon16 != null) {
  86. data.head.push(makeTag('link', {
  87. rel: 'icon',
  88. type: 'image/png',
  89. sizes: '16x16',
  90. href: getTagHref(publicPath, iconPaths.favicon16, assetsVersionStr)
  91. }))
  92. }
  93. // Add to home screen for Android and modern mobile browsers
  94. data.head.push(
  95. makeTag('link', manifestCrossorigin
  96. ? {
  97. rel: 'manifest',
  98. href: getTagHref(publicPath, manifestPath, assetsVersionStr),
  99. crossorigin: manifestCrossorigin
  100. }
  101. : {
  102. rel: 'manifest',
  103. href: getTagHref(publicPath, manifestPath, assetsVersionStr)
  104. }
  105. )
  106. )
  107. if (themeColor != null) {
  108. data.head.push(
  109. makeTag('meta', {
  110. name: 'theme-color',
  111. content: themeColor
  112. })
  113. )
  114. }
  115. // Add to home screen for Safari on iOS
  116. data.head.push(
  117. makeTag('meta', {
  118. name: 'apple-mobile-web-app-capable',
  119. content: appleMobileWebAppCapable
  120. }),
  121. makeTag('meta', {
  122. name: 'apple-mobile-web-app-status-bar-style',
  123. content: appleMobileWebAppStatusBarStyle
  124. }),
  125. makeTag('meta', {
  126. name: 'apple-mobile-web-app-title',
  127. content: name
  128. })
  129. )
  130. if (iconPaths.appleTouchIcon != null) {
  131. data.head.push(makeTag('link', {
  132. rel: 'apple-touch-icon',
  133. href: getTagHref(publicPath, iconPaths.appleTouchIcon, assetsVersionStr)
  134. }))
  135. }
  136. if (iconPaths.maskIcon != null) {
  137. data.head.push(makeTag('link', {
  138. rel: 'mask-icon',
  139. href: getTagHref(publicPath, iconPaths.maskIcon, assetsVersionStr),
  140. color: themeColor
  141. }))
  142. }
  143. // Add to home screen for Windows
  144. if (iconPaths.msTileImage != null) {
  145. data.head.push(makeTag('meta', {
  146. name: 'msapplication-TileImage',
  147. content: getTagHref(publicPath, iconPaths.msTileImage, assetsVersionStr)
  148. }))
  149. }
  150. if (msTileColor != null) {
  151. data.head.push(
  152. makeTag('meta', {
  153. name: 'msapplication-TileColor',
  154. content: msTileColor
  155. })
  156. )
  157. }
  158. cb(null, data)
  159. })
  160. })
  161. if (!isHrefAbsoluteUrl(this.options.manifestPath)) {
  162. compiler.hooks.emit.tapAsync(ID, (data, cb) => {
  163. const {
  164. name,
  165. themeColor,
  166. manifestPath,
  167. manifestOptions
  168. } = this.options
  169. const publicOptions = {
  170. name,
  171. short_name: name,
  172. theme_color: themeColor
  173. }
  174. const outputManifest = JSON.stringify(
  175. Object.assign(publicOptions, defaultManifest, manifestOptions)
  176. )
  177. data.assets[manifestPath] = {
  178. source: () => outputManifest,
  179. size: () => outputManifest.length
  180. }
  181. cb(null, data)
  182. })
  183. }
  184. }
  185. }
  186. function makeTag (tagName, attributes, closeTag = false) {
  187. return {
  188. tagName,
  189. closeTag,
  190. attributes
  191. }
  192. }
  193. function getTagHref (publicPath, href, assetsVersionStr) {
  194. let tagHref = `${href}${assetsVersionStr}`
  195. if (!isHrefAbsoluteUrl(href)) {
  196. tagHref = `${publicPath}${tagHref}`
  197. }
  198. return tagHref
  199. }
  200. function isHrefAbsoluteUrl (href) {
  201. return /(http(s?)):\/\//gi.test(href)
  202. }