styleInjection.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. const { attrsToQuery, genMatchResource } = require('./utils')
  2. const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))
  3. const nonWhitespaceRE = /\S+/
  4. module.exports = function genStyleInjectionCode(
  5. loaderContext,
  6. styles,
  7. id,
  8. resourcePath,
  9. stringifyRequest,
  10. needsHotReload,
  11. needsExplicitInjection,
  12. isProduction,
  13. enableInlineMatchResource
  14. ) {
  15. let styleImportsCode = ``
  16. let styleInjectionCode = ``
  17. let cssModulesHotReloadCode = ``
  18. let hasCSSModules = false
  19. const cssModuleNames = new Map()
  20. function genStyleRequest(style, i) {
  21. const src = style.src || resourcePath
  22. const attrsQuery = attrsToQuery(style.attrs, 'css')
  23. const lang = String(style.attrs.lang || 'css')
  24. const inheritQuery = loaderContext.resourceQuery.slice(1)
  25. ? `&${loaderContext.resourceQuery.slice(1)}`
  26. : ''
  27. // make sure to only pass id not src importing so that we don't inject
  28. // duplicate tags when multiple components import the same css file
  29. const idQuery = !style.src || style.scoped ? `&id=${id}` : ``
  30. const prodQuery = isProduction ? `&prod` : ``
  31. const externalQuery = style.src ? `&external` : ``
  32. const query = `?vue&type=style&index=${i}${idQuery}${prodQuery}${attrsQuery}${inheritQuery}${externalQuery}`
  33. let styleRequest
  34. if (enableInlineMatchResource) {
  35. styleRequest = stringifyRequest(genMatchResource(loaderContext, src, query, lang))
  36. } else {
  37. styleRequest = stringifyRequest(src + query)
  38. }
  39. return styleRequest
  40. }
  41. function genCSSModulesCode(style, request, i) {
  42. hasCSSModules = true
  43. const moduleName = style.module === true ? '$style' : style.module
  44. if (cssModuleNames.has(moduleName)) {
  45. loaderContext.emitError(`CSS module name ${moduleName} is not unique!`)
  46. }
  47. cssModuleNames.set(moduleName, true)
  48. // `(vue-)style-loader` exports the name-to-hash map directly
  49. // `css-loader` exports it in `.locals`
  50. const locals = `(style${i}.locals || style${i})`
  51. const name = JSON.stringify(moduleName)
  52. if (!needsHotReload) {
  53. styleInjectionCode += `this[${name}] = ${locals}\n`
  54. } else {
  55. styleInjectionCode += `
  56. cssModules[${name}] = ${locals}
  57. Object.defineProperty(this, ${name}, {
  58. configurable: true,
  59. get: function () {
  60. return cssModules[${name}]
  61. }
  62. })
  63. `
  64. cssModulesHotReloadCode += `
  65. module.hot && module.hot.accept([${request}], function () {
  66. var oldLocals = cssModules[${name}]
  67. if (oldLocals) {
  68. var newLocals = require(${request})
  69. if (JSON.stringify(newLocals) !== JSON.stringify(oldLocals)) {
  70. cssModules[${name}] = newLocals
  71. require(${hotReloadAPIPath}).rerender("${id}")
  72. }
  73. }
  74. })
  75. `
  76. }
  77. }
  78. // empty styles: with no `src` specified or only contains whitespaces
  79. const isNotEmptyStyle = (style) =>
  80. style.src || nonWhitespaceRE.test(style.content)
  81. // explicit injection is needed in SSR (for critical CSS collection)
  82. // or in Shadow Mode (for injection into shadow root)
  83. // In these modes, vue-style-loader exports objects with the __inject__
  84. // method; otherwise we simply import the styles.
  85. if (!needsExplicitInjection) {
  86. styles.forEach((style, i) => {
  87. // do not generate requests for empty styles
  88. if (isNotEmptyStyle(style)) {
  89. const request = genStyleRequest(style, i)
  90. styleImportsCode += `import style${i} from ${request}\n`
  91. if (style.module) genCSSModulesCode(style, request, i)
  92. }
  93. })
  94. } else {
  95. styles.forEach((style, i) => {
  96. if (isNotEmptyStyle(style)) {
  97. const request = genStyleRequest(style, i)
  98. styleInjectionCode +=
  99. `var style${i} = require(${request})\n` +
  100. `if (style${i}.__inject__) style${i}.__inject__(context)\n`
  101. if (style.module) genCSSModulesCode(style, request, i)
  102. }
  103. })
  104. }
  105. if (!needsExplicitInjection && !hasCSSModules) {
  106. return styleImportsCode
  107. }
  108. return `
  109. ${styleImportsCode}
  110. ${hasCSSModules && needsHotReload ? `var cssModules = {}` : ``}
  111. ${needsHotReload ? `var disposed = false` : ``}
  112. function injectStyles (context) {
  113. ${needsHotReload ? `if (disposed) return` : ``}
  114. ${styleInjectionCode}
  115. }
  116. ${
  117. needsHotReload
  118. ? `
  119. module.hot && module.hot.dispose(function (data) {
  120. disposed = true
  121. })
  122. `
  123. : ``
  124. }
  125. ${cssModulesHotReloadCode}
  126. `.trim()
  127. }