ModernModePlugin.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. const HtmlWebpackPlugin = require('html-webpack-plugin')
  4. class ModernModePlugin {
  5. constructor ({ targetDir, isModuleBuild }) {
  6. this.targetDir = targetDir
  7. this.isModuleBuild = isModuleBuild
  8. }
  9. apply (compiler) {
  10. if (!this.isModuleBuild) {
  11. this.applyLegacy(compiler)
  12. } else {
  13. this.applyModule(compiler)
  14. }
  15. }
  16. applyLegacy (compiler) {
  17. const ID = `vue-cli-legacy-bundle`
  18. compiler.hooks.compilation.tap(ID, compilation => {
  19. HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(ID, async (data, cb) => {
  20. // get stats, write to disk
  21. await fs.ensureDir(this.targetDir)
  22. const htmlName = path.basename(data.plugin.options.filename)
  23. // Watch out for output files in sub directories
  24. const htmlPath = path.dirname(data.plugin.options.filename)
  25. const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
  26. await fs.mkdirp(path.dirname(tempFilename))
  27. let tags = data.bodyTags
  28. if (data.plugin.options.scriptLoading === 'defer') {
  29. tags = data.headTags
  30. }
  31. await fs.writeFile(tempFilename, JSON.stringify(tags))
  32. cb()
  33. })
  34. })
  35. }
  36. applyModule (compiler) {
  37. const ID = `vue-cli-modern-bundle`
  38. compiler.hooks.compilation.tap(ID, compilation => {
  39. HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(ID, async (data, cb) => {
  40. let tags = data.bodyTags
  41. if (data.plugin.options.scriptLoading === 'defer') {
  42. tags = data.headTags
  43. }
  44. // use <script type="module"> for modern assets
  45. tags.forEach(tag => {
  46. if (tag.tagName === 'script' && tag.attributes) {
  47. tag.attributes.type = 'module'
  48. }
  49. })
  50. // use <link rel="modulepreload"> instead of <link rel="preload">
  51. // for modern assets
  52. data.headTags.forEach(tag => {
  53. if (tag.tagName === 'link' &&
  54. tag.attributes.rel === 'preload' &&
  55. tag.attributes.as === 'script') {
  56. tag.attributes.rel = 'modulepreload'
  57. }
  58. })
  59. // inject links for legacy assets as <script nomodule>
  60. const htmlName = path.basename(data.plugin.options.filename)
  61. // Watch out for output files in sub directories
  62. const htmlPath = path.dirname(data.plugin.options.filename)
  63. const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
  64. const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8'))
  65. .filter(a => a.tagName === 'script' && a.attributes)
  66. legacyAssets.forEach(a => { a.attributes.nomodule = '' })
  67. tags.push(...legacyAssets)
  68. await fs.remove(tempFilename)
  69. cb()
  70. })
  71. HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap(ID, data => {
  72. data.html = data.html.replace(/\snomodule="">/g, ' nomodule>')
  73. })
  74. })
  75. }
  76. }
  77. module.exports = ModernModePlugin