styleInjection.js 4.0 KB

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