-import { compileStyle, compileStyleAsync } from '../src/compileStyle'
+import {
+ compileStyle,
+ compileStyleAsync,
+ SFCStyleCompileOptions
+} from '../src/compileStyle'
import { mockWarn } from '@vue/shared'
describe('SFC scoped CSS', () => {
mockWarn()
- function compileScoped(source: string): string {
+ function compileScoped(
+ source: string,
+ options?: Partial<SFCStyleCompileOptions>
+ ): string {
const res = compileStyle({
source,
filename: 'test.css',
id: 'test',
- scoped: true
+ scoped: true,
+ ...options
})
if (res.errors.length) {
res.errors.forEach(err => {
describe('<style vars>', () => {
test('should rewrite CSS vars in scoped mode', () => {
- const code = compileScoped(`.foo {
+ const code = compileScoped(
+ `.foo {
color: var(--color);
font-size: var(--global:font);
- }`)
+ }`,
+ {
+ vars: true
+ }
+ )
expect(code).toMatchInlineSnapshot(`
".foo[test] {
color: var(--test-color);
export interface SFCScriptBlock extends SFCBlock {
type: 'script'
+ setup?: string | boolean
bindings?: BindingMetadata
}
export interface SFCStyleBlock extends SFCBlock {
type: 'style'
scoped?: boolean
+ vars?: string
module?: string | boolean
}
} else if (type === 'style') {
if (p.name === 'scoped') {
;(block as SFCStyleBlock).scoped = true
+ } else if (p.name === 'vars' && typeof attrs.vars === 'string') {
+ ;(block as SFCStyleBlock).vars = attrs.vars
} else if (p.name === 'module') {
;(block as SFCStyleBlock).module = attrs[p.name]
}
} else if (type === 'template' && p.name === 'functional') {
;(block as SFCTemplateBlock).functional = true
+ } else if (type === 'script' && p.name === 'setup') {
+ ;(block as SFCScriptBlock).setup = attrs.setup
}
}
})
const cssVarRE = /\bvar\(--(global:)?([^)]+)\)/g
export default postcss.plugin('vue-scoped', (options: any) => (root: Root) => {
- const id: string = options
+ const { id, vars: hasInjectedVars } = options as { id: string; vars: boolean }
const keyframes = Object.create(null)
root.each(function rewriteSelectors(node) {
})
const hasKeyframes = Object.keys(keyframes).length
- root.walkDecls(decl => {
- // If keyframes are found in this <style>, find and rewrite animation names
- // in declarations.
- // Caveat: this only works for keyframes and animation rules in the same
- // <style> element.
- if (hasKeyframes) {
- // individual animation-name declaration
- if (animationNameRE.test(decl.prop)) {
- decl.value = decl.value
- .split(',')
- .map(v => keyframes[v.trim()] || v.trim())
- .join(',')
- }
- // shorthand
- if (animationRE.test(decl.prop)) {
- decl.value = decl.value
- .split(',')
- .map(v => {
- const vals = v.trim().split(/\s+/)
- const i = vals.findIndex(val => keyframes[val])
- if (i !== -1) {
- vals.splice(i, 1, keyframes[vals[i]])
- return vals.join(' ')
- } else {
- return v
- }
- })
- .join(',')
+ if (hasKeyframes || hasInjectedVars)
+ root.walkDecls(decl => {
+ // If keyframes are found in this <style>, find and rewrite animation names
+ // in declarations.
+ // Caveat: this only works for keyframes and animation rules in the same
+ // <style> element.
+ if (hasKeyframes) {
+ // individual animation-name declaration
+ if (animationNameRE.test(decl.prop)) {
+ decl.value = decl.value
+ .split(',')
+ .map(v => keyframes[v.trim()] || v.trim())
+ .join(',')
+ }
+ // shorthand
+ if (animationRE.test(decl.prop)) {
+ decl.value = decl.value
+ .split(',')
+ .map(v => {
+ const vals = v.trim().split(/\s+/)
+ const i = vals.findIndex(val => keyframes[val])
+ if (i !== -1) {
+ vals.splice(i, 1, keyframes[vals[i]])
+ return vals.join(' ')
+ } else {
+ return v
+ }
+ })
+ .join(',')
+ }
}
- }
- // rewrite CSS variables
- if (cssVarRE.test(decl.value)) {
- decl.value = decl.value.replace(cssVarRE, (_, $1, $2) => {
- return $1 ? `var(--${$2})` : `var(--${id}-${$2})`
- })
- }
- })
+ // rewrite CSS variables
+ if (hasInjectedVars && cssVarRE.test(decl.value)) {
+ decl.value = decl.value.replace(cssVarRE, (_, $1, $2) => {
+ return $1 ? `var(--${$2})` : `var(--${id}-${$2})`
+ })
+ }
+ })
})
function isSpaceCombinator(node: Node) {