styleInjection.js 3.5 KB

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