ModernModePlugin.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
  4. const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();`
  5. class ModernModePlugin {
  6. constructor ({ targetDir, isModernBuild, unsafeInline, jsDirectory }) {
  7. this.targetDir = targetDir
  8. this.isModernBuild = isModernBuild
  9. this.unsafeInline = unsafeInline
  10. this.jsDirectory = jsDirectory
  11. }
  12. apply (compiler) {
  13. if (!this.isModernBuild) {
  14. this.applyLegacy(compiler)
  15. } else {
  16. this.applyModern(compiler)
  17. }
  18. }
  19. applyLegacy (compiler) {
  20. const ID = `vue-cli-legacy-bundle`
  21. compiler.hooks.compilation.tap(ID, compilation => {
  22. compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
  23. // get stats, write to disk
  24. await fs.ensureDir(this.targetDir)
  25. const htmlName = path.basename(data.plugin.options.filename)
  26. // Watch out for output files in sub directories
  27. const htmlPath = path.dirname(data.plugin.options.filename)
  28. const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
  29. await fs.mkdirp(path.dirname(tempFilename))
  30. await fs.writeFile(tempFilename, JSON.stringify(data.body))
  31. cb()
  32. })
  33. })
  34. }
  35. applyModern (compiler) {
  36. const ID = `vue-cli-modern-bundle`
  37. compiler.hooks.compilation.tap(ID, compilation => {
  38. compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
  39. // use <script type="module"> for modern assets
  40. data.body.forEach(tag => {
  41. if (tag.tagName === 'script' && tag.attributes) {
  42. tag.attributes.type = 'module'
  43. }
  44. })
  45. // use <link rel="modulepreload"> instead of <link rel="preload">
  46. // for modern assets
  47. data.head.forEach(tag => {
  48. if (tag.tagName === 'link' &&
  49. tag.attributes.rel === 'preload' &&
  50. tag.attributes.as === 'script') {
  51. tag.attributes.rel = 'modulepreload'
  52. }
  53. })
  54. // inject links for legacy assets as <script nomodule>
  55. const htmlName = path.basename(data.plugin.options.filename)
  56. // Watch out for output files in sub directories
  57. const htmlPath = path.dirname(data.plugin.options.filename)
  58. const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
  59. const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8'))
  60. .filter(a => a.tagName === 'script' && a.attributes)
  61. legacyAssets.forEach(a => { a.attributes.nomodule = '' })
  62. if (this.unsafeInline) {
  63. // inject inline Safari 10 nomodule fix
  64. data.body.push({
  65. tagName: 'script',
  66. closeTag: true,
  67. innerHTML: safariFix
  68. })
  69. } else {
  70. // inject the fix as an external script
  71. const safariFixPath = path.join(this.jsDirectory, 'safari-nomodule-fix.js')
  72. const fullSafariFixPath = path.join(compilation.options.output.publicPath, safariFixPath)
  73. compilation.assets[safariFixPath] = {
  74. source: function () {
  75. return new Buffer(safariFix)
  76. },
  77. size: function () {
  78. return Buffer.byteLength(safariFix)
  79. }
  80. }
  81. data.body.push({
  82. tagName: 'script',
  83. closeTag: true,
  84. attributes: {
  85. src: fullSafariFixPath
  86. }
  87. })
  88. }
  89. data.body.push(...legacyAssets)
  90. await fs.remove(tempFilename)
  91. cb()
  92. })
  93. compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(ID, data => {
  94. data.html = data.html.replace(/\snomodule="">/g, ' nomodule>')
  95. })
  96. })
  97. }
  98. }
  99. ModernModePlugin.safariFix = safariFix
  100. module.exports = ModernModePlugin