eb1147c4f422793bd59f92fce448b3c86a5225257877f48015d4c987c91e6df944b0dabcdaef07f2bfc781fe9cfc3a8ab391d89be3cdde8232a089181fee6e 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * @license
  3. * Copyright 2018 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. const defaultOptions = require('./lib/default-options')
  18. const determineAsValue = require('./lib/determine-as-value')
  19. const doesChunkBelongToHTML = require('./lib/does-chunk-belong-to-html')
  20. const extractChunks = require('./lib/extract-chunks')
  21. class PreloadPlugin {
  22. constructor (options) {
  23. this.options = Object.assign({}, defaultOptions, options)
  24. }
  25. generateLinks (compilation, htmlPluginData) {
  26. const options = this.options
  27. const extractedChunks = extractChunks({
  28. compilation,
  29. optionsInclude: options.include
  30. })
  31. const htmlChunks = options.include === 'allAssets'
  32. // Handle all chunks.
  33. ? extractedChunks
  34. // Only handle chunks imported by this HtmlWebpackPlugin.
  35. : extractedChunks.filter((chunk) => doesChunkBelongToHTML({
  36. chunk,
  37. compilation,
  38. htmlAssetsChunks: Object.values(htmlPluginData.assets.chunks)
  39. }))
  40. // Flatten the list of files.
  41. const allFiles = htmlChunks.reduce((accumulated, chunk) => {
  42. return accumulated.concat(chunk.files)
  43. }, [])
  44. const uniqueFiles = new Set(allFiles)
  45. const filteredFiles = [...uniqueFiles].filter(file => {
  46. return (
  47. !this.options.fileWhitelist ||
  48. this.options.fileWhitelist.some(regex => regex.test(file))
  49. )
  50. }).filter(file => {
  51. return (
  52. !this.options.fileBlacklist ||
  53. this.options.fileBlacklist.every(regex => !regex.test(file))
  54. )
  55. })
  56. // Sort to ensure the output is predictable.
  57. const sortedFilteredFiles = filteredFiles.sort()
  58. const links = []
  59. const publicPath = compilation.outputOptions.publicPath || ''
  60. for (const file of sortedFilteredFiles) {
  61. const href = `${publicPath}${file}`
  62. const attributes = {
  63. href,
  64. rel: options.rel
  65. }
  66. // If we're preloading this resource (as opposed to prefetching),
  67. // then we need to set the 'as' attribute correctly.
  68. if (options.rel === 'preload') {
  69. attributes.as = determineAsValue({
  70. href,
  71. file,
  72. optionsAs: options.as
  73. })
  74. // On the off chance that we have a cross-origin 'href' attribute,
  75. // set crossOrigin on the <link> to trigger CORS mode. Non-CORS
  76. // fonts can't be used.
  77. if (attributes.as === 'font') {
  78. attributes.crossorigin = ''
  79. }
  80. }
  81. links.push({
  82. tagName: 'link',
  83. attributes
  84. })
  85. }
  86. this.resourceHints = links
  87. return htmlPluginData
  88. }
  89. apply (compiler) {
  90. const skip = data => {
  91. const htmlFilename = data.plugin.options.filename
  92. const exclude = this.options.excludeHtmlNames
  93. const include = this.options.includeHtmlNames
  94. return (
  95. (include && !(include.includes(htmlFilename))) ||
  96. (exclude && exclude.includes(htmlFilename))
  97. )
  98. }
  99. compiler.hooks.compilation.tap(
  100. this.constructor.name,
  101. compilation => {
  102. compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tap(
  103. this.constructor.name,
  104. (htmlPluginData) => {
  105. if (skip(htmlPluginData)) {
  106. return
  107. }
  108. this.generateLinks(compilation, htmlPluginData)
  109. }
  110. )
  111. compilation.hooks.htmlWebpackPluginAlterAssetTags.tap(
  112. this.constructor.name,
  113. (htmlPluginData) => {
  114. if (skip(htmlPluginData)) {
  115. return
  116. }
  117. if (this.resourceHints) {
  118. htmlPluginData.head = [
  119. ...this.resourceHints,
  120. ...htmlPluginData.head
  121. ]
  122. }
  123. return htmlPluginData
  124. }
  125. )
  126. }
  127. )
  128. }
  129. }
  130. module.exports = PreloadPlugin