Skip to content

Commit

Permalink
feat: dynamic style injection
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Mar 20, 2018
1 parent 3a16ce5 commit 234d48b
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 19 deletions.
3 changes: 3 additions & 0 deletions lib/customBlocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function genCustomBlocksCode () {

}
34 changes: 19 additions & 15 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const plugin = require('./plugin')
const selectBlock = require('./select')
const loaderUtils = require('loader-utils')
const { genHotReloadCode } = require('./hotReload')
const genStyleInjectionCode = require('./styleInjection')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')

module.exports = function (source) {
Expand All @@ -22,9 +23,12 @@ module.exports = function (source) {
resourceQuery
} = loaderContext

const incomingQuery = qs.parse(resourceQuery.slice(1))
const options = loaderUtils.getOptions(loaderContext) || {}

const isServer = target === 'node'
const isShadow = incomingQuery.shadow != null
const isProduction = minimize || process.env.NODE_ENV === 'production'
const options = loaderUtils.getOptions(loaderContext) || {}
const fileName = path.basename(resourcePath)
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))
Expand All @@ -39,7 +43,6 @@ module.exports = function (source) {
// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
const incomingQuery = qs.parse(resourceQuery.slice(1))
if (incomingQuery.type) {
return selectBlock(descriptor, loaderContext, incomingQuery)
}
Expand Down Expand Up @@ -93,33 +96,34 @@ module.exports = function (source) {
}

// styles
// TODO construct an injection function instead
let styleImports = ``
let styleInjectionCode = ``
if (descriptor.styles.length) {
styleImports = descriptor.styles.map((style, i) => {
const src = style.src || resourcePath
const langQuery = getLangQuery(style, 'css')
const scopedQuery = style.scoped ? `&scoped&id=${id}` : ``
const query = `?vue&type=style&index=${i}${langQuery}${scopedQuery}`
const request = stringifyRequest(src + query)
return `import style${i} from ${request}`
}).join('\n')
styleInjectionCode = genStyleInjectionCode(
loaderContext,
descriptor.styles,
id,
resourcePath,
stringifyRequest,
getLangQuery,
needsHotReload,
isServer || isShadow // needs explicit injection?
)
}

let code = `
${templateImport}
${scriptImport}
${styleImports}
${styleInjectionCode}
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
var component = normalizer(
script,
render,
staticRenderFns,
${hasFunctional ? `true` : `false`},
null, // TODO style injection
${/injectStyles/.test(styleInjectionCode) ? `injectStyles` : `null`},
${hasScoped ? JSON.stringify(id) : `null`},
${isServer ? JSON.stringify(hash(request)) : `null`}
${incomingQuery.shadow ? `,true` : ``}
${isShadow ? `,true` : ``}
)
`.trim()

Expand Down
109 changes: 109 additions & 0 deletions lib/styleInjection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const hotReloadAPIPath = require.resolve('vue-hot-reload-api')

module.exports = function genStyleInjectionCode (
loaderContext,
styles,
id,
resourcePath,
stringifyRequest,
getLangQuery,
needsHotReload,
needsExplicitInjection
) {
let styleImportsCode = ``
let styleInjectionCode = ``
let cssModulesHotReloadCode = ``

const hasCSSModules = false
const cssModuleNames = new Map()

function genStyleRequest (style, i) {
const src = style.src || resourcePath
const langQuery = getLangQuery(style, 'css')
const scopedQuery = style.scoped ? `&scoped&id=${id}` : ``
const query = `?vue&type=style&index=${i}${langQuery}${scopedQuery}`
return stringifyRequest(src + query)
}

function genCSSModulesCode (style, request, i) {
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}`
} else {
styleInjectionCode += `
cssModules[${name}] = ${locals}
Object.defineProperty(this, ${name}, {
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}")
}
}
})
`
}
}

// 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) => {
const request = genStyleRequest(style, i)
styleImportsCode += `import style${i} from ${request}\n`
if (style.module) genCSSModulesCode(style, request, i)
})
} else {
styles.forEach((style, i) => {
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()
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"source-map": "^0.5.6",
"vue-component-compiler": "vuejs/vue-component-compiler#master",
"vue-hot-reload-api": "^2.3.0",
"vue-style-loader": "^4.0.2",
"vue-style-loader": "^4.1.0",
"vue-template-es2015-compiler": "^1.6.0"
}
}
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8026,9 +8026,9 @@ vue-server-renderer@^2.5.16:
serialize-javascript "^1.3.0"
source-map "0.5.6"

vue-style-loader@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.0.2.tgz#e89aa4702a0c6b9630d8de70b1cbddb06b9ad254"
vue-style-loader@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863"
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
Expand Down

0 comments on commit 234d48b

Please sign in to comment.