const { attrsToQuery } = require('./utils') const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api')) const nonWhitespaceRE = /\S+/ module.exports = function genStyleInjectionCode ( loaderContext, styles, id, resourcePath, stringifyRequest, needsHotReload, needsExplicitInjection ) { let styleImportsCode = `` let styleInjectionCode = `` let cssModulesHotReloadCode = `` let hasCSSModules = false const cssModuleNames = new Map() function genStyleRequest (style, i) { const src = style.src || resourcePath const attrsQuery = attrsToQuery(style.attrs, 'css') const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}` // make sure to only pass id when necessary so that we don't inject // duplicate tags when multiple components import the same css file const idQuery = style.scoped ? `&id=${id}` : `` const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${inheritQuery}` return stringifyRequest(src + query) } function genCSSModulesCode (style, request, i) { hasCSSModules = true const moduleName = style.module === true ? '$style' : style.module if (cssModuleNames.has(moduleName)) { loaderContext.emitError(`CSS module name ${moduleName} is not unique!`) } cssModuleNames.set(moduleName, true) // `(vue-)style-loader` exports the name-to-hash map directly // `css-loader` exports it in `.locals` const locals = `(style${i}.locals || style${i})` const name = JSON.stringify(moduleName) if (!needsHotReload) { styleInjectionCode += `this[${name}] = ${locals}\n` } else { styleInjectionCode += ` cssModules[${name}] = ${locals} Object.defineProperty(this, ${name}, { configurable: true, get: function () { return cssModules[${name}] } }) ` cssModulesHotReloadCode += ` module.hot && module.hot.accept([${request}], function () { var oldLocals = cssModules[${name}] if (oldLocals) { var newLocals = require(${request}) if (JSON.stringify(newLocals) !== JSON.stringify(oldLocals)) { cssModules[${name}] = newLocals require(${hotReloadAPIPath}).rerender("${id}") } } }) ` } } // empty styles: with no `src` specified or only contains whitespaces const isNotEmptyStyle = style => style.src || nonWhitespaceRE.test(style.content) // explicit injection is needed in SSR (for critical CSS collection) // or in Shadow Mode (for injection into shadow root) // In these modes, vue-style-loader exports objects with the __inject__ // method; otherwise we simply import the styles. if (!needsExplicitInjection) { styles.forEach((style, i) => { // do not generate requests for empty styles if (isNotEmptyStyle(style)) { const request = genStyleRequest(style, i) styleImportsCode += `import style${i} from ${request}\n` if (style.module) genCSSModulesCode(style, request, i) } }) } else { styles.forEach((style, i) => { if (isNotEmptyStyle(style)) { const request = genStyleRequest(style, i) styleInjectionCode += ( `var style${i} = require(${request})\n` + `if (style${i}.__inject__) style${i}.__inject__(context)\n` ) if (style.module) genCSSModulesCode(style, request, i) } }) } if (!needsExplicitInjection && !hasCSSModules) { return styleImportsCode } return ` ${styleImportsCode} ${hasCSSModules && needsHotReload ? `var cssModules = {}` : ``} ${needsHotReload ? `var disposed = false` : ``} function injectStyles (context) { ${needsHotReload ? `if (disposed) return` : ``} ${styleInjectionCode} } ${needsHotReload ? ` module.hot && module.hot.dispose(function (data) { disposed = true }) ` : ``} ${cssModulesHotReloadCode} `.trim() }