styleInjection.js 3.9 KB

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