Skip to content

Commit 04eb4b5

Browse files
committed
feat: introduce basic global style support
1 parent c9d8da0 commit 04eb4b5

File tree

8 files changed

+1585
-643
lines changed

8 files changed

+1585
-643
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ h1 {
183183
</style>
184184
```
185185

186-
What a trick... It uses [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) for dynamic styles, that's why this feature is not supported in IE.
186+
Under the hood, it uses [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) for dynamic styles, that's why this feature is not supported in IE.
187187

188188
### CSS Preprocessors
189189

@@ -197,7 +197,19 @@ The CSS will be passed to `vue-loader` and parsed by PostCSS if a `postcss.confi
197197

198198
### Global Styles
199199

200-
I think you should not do this, but I'm open for other thoughts, let's discuss it in issue tracker if you want this feature.
200+
Use `globalStyle` instead of `style` on your component:
201+
202+
```js
203+
import { css } from 'styled-vue'
204+
205+
export default {
206+
globalStyle: css`
207+
body {
208+
color: ${vm => vm.bodyColor};
209+
}
210+
`
211+
}
212+
```
201213

202214
### TypeScript
203215

example/App.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@
1212
import { css } from 'styled-vue'
1313
import { modularScale } from 'polished'
1414
15+
const border = `10px solid pink`
16+
1517
export default {
18+
globalStyle: css`
19+
#app {
20+
border: ${border};
21+
}
22+
`,
1623
style: css`
1724
#app {
1825
background-color: ${vm => (vm.theme === 'dark' ? '#000' : '#f0f0f0')};

lib/parseComponent.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ module.exports = (content, opts) => {
66
const sfc = parseComponent(content, opts)
77

88
if (sfc.script) {
9-
const { style, styleLang, hasVars, scriptContent } = parseScript(sfc.script)
9+
const {
10+
style,
11+
styleLang,
12+
hasVars,
13+
scriptContent,
14+
globalStyle,
15+
globalStyleLang
16+
} = parseScript(sfc.script)
1017

1118
sfc.script.content = scriptContent
1219

@@ -18,7 +25,9 @@ module.exports = (content, opts) => {
1825
node.attrs = node.attrs || {}
1926
const existing =
2027
node.attrs[':style'] || node.attrs['v-bind:style']
21-
node.attrs[':style'] = `$options.style(this, ${existing})`
28+
node.attrs[
29+
':style'
30+
] = `$options._getCssVariables(this, $options, ${existing})`
2231
}
2332
}
2433
return tree
@@ -29,17 +38,35 @@ module.exports = (content, opts) => {
2938
}).html
3039
}
3140

32-
if (style) {
33-
sfc.styles.push({
41+
let contentLength = content.length
42+
43+
const addStyleTag = (styleContent, styleLang, isGlobal) => {
44+
const style = {
3445
type: 'style',
35-
content: style,
36-
attrs: { scoped: true, lang: styleLang },
37-
scoped: true,
38-
lang: styleLang,
46+
content: styleContent,
47+
attrs: {},
3948
// TODO: this might be wrong
40-
start: content.length,
41-
end: content.length + style.length
42-
})
49+
start: contentLength,
50+
end: contentLength + styleContent.length
51+
}
52+
if (styleLang) {
53+
style.lang = styleLang
54+
style.attrs.lang = styleLang
55+
}
56+
if (!isGlobal) {
57+
style.scoped = true
58+
style.attrs.scoped = true
59+
}
60+
sfc.styles.push(style)
61+
contentLength += styleContent.length
62+
}
63+
64+
if (globalStyle) {
65+
addStyleTag(globalStyle, globalStyleLang, true)
66+
}
67+
68+
if (style) {
69+
addStyleTag(style, styleLang, false)
4370
}
4471
}
4572

lib/parseScript.js

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module.exports = script => {
1010
let style
1111
let styleLang
1212
let hasVars = false
13+
let globalStyle
14+
let globalStyleLang
1315

1416
const ast = parser.parse(script.content, {
1517
sourceType: 'module',
@@ -23,16 +25,17 @@ module.exports = script => {
2325
]
2426
})
2527

26-
const parseTaggedTemplate = ref => {
28+
const parseTaggedTemplate = (ref, isGlobal) => {
2729
const { quasi } = ref.node
2830
const quasis = []
2931
const vars = []
3032
const varDeclarations = []
33+
const variablePrefix = isGlobal ? 'gv' : `v`
3134

3235
for (const [i, q] of quasi.quasis.entries()) {
3336
quasis.push(q)
3437
if (quasi.expressions[i]) {
35-
const value = `var(--v${i})`
38+
const value = `var(--${variablePrefix}${i})`
3639
quasis.push({
3740
type: 'TemplateElement',
3841
value: { raw: value, cooked: value }
@@ -55,13 +58,16 @@ module.exports = script => {
5558
// var v0 = vm => vm.color
5659
varDeclarations.push(
5760
t.variableDeclaration('var', [
58-
t.variableDeclarator(t.identifier(`v${i}`), quasi.expressions[i])
61+
t.variableDeclarator(
62+
t.identifier(`${variablePrefix}${i}`),
63+
quasi.expressions[i]
64+
)
5965
])
6066
)
6167

62-
// { '--v0': v0(vm, existing) }
63-
// { '--v0': v0(vm, existing) + unit }
64-
const id = t.identifier(`v${i}`)
68+
// { '--v0': v0(vm) }
69+
// { '--v0': v0(vm) + unit }
70+
const id = t.identifier(`${variablePrefix}${i}`)
6571
const expType = quasi.expressions[i].type
6672
const mustBeFunction = [
6773
'FunctionExpression',
@@ -73,10 +79,7 @@ module.exports = script => {
7379
const getValue = isFunction => {
7480
let leftExp
7581
if (isFunction) {
76-
leftExp = t.callExpression(id, [
77-
t.identifier('vm'),
78-
t.identifier('existing')
79-
])
82+
leftExp = t.callExpression(id, [t.identifier('vm')])
8083
} else {
8184
leftExp = id
8285
}
@@ -86,7 +89,7 @@ module.exports = script => {
8689
}
8790
vars.push(
8891
t.objectProperty(
89-
t.stringLiteral(`--v${i}`),
92+
t.stringLiteral(`--${variablePrefix}${i}`),
9093
mustBeFunction
9194
? getValue(true)
9295
: mustNotBeFunction
@@ -144,37 +147,92 @@ module.exports = script => {
144147
}
145148

146149
const binding = path.scope.getBinding(specifier.local.name)
150+
let objectExpressionPath
147151
for (let i = 0; i < binding.referencePaths.length; i++) {
152+
// The tagged template path
148153
const ref = binding.referencePaths[i].parentPath
154+
// The object property path
155+
const propertyPath = ref.parentPath
156+
157+
if (
158+
!propertyPath ||
159+
propertyPath.node.type !== 'ObjectProperty' ||
160+
!['style', 'globalStyle'].includes(propertyPath.node.key.name)
161+
) {
162+
throw new Error(
163+
`css tag must be assigned to component property "style" or "globalStyle"`
164+
)
165+
}
149166

167+
// The object expression path
168+
objectExpressionPath = propertyPath.parentPath
169+
170+
const isGlobal = propertyPath.node.key.name === 'globalStyle'
150171
const { vars, varDeclarations, extractedStyle } = parseTaggedTemplate(
151-
ref
172+
ref,
173+
isGlobal
152174
)
153-
style = extractedStyle
175+
if (isGlobal) {
176+
globalStyle = extractedStyle
177+
} else {
178+
style = extractedStyle
179+
}
154180

155-
if (hasVars) {
181+
if (vars.length > 0) {
156182
ref.replaceWith(
157183
t.functionExpression(
158184
null,
159185
[t.identifier('vm'), t.identifier('existing')],
160186
t.blockStatement([
161187
...varDeclarations,
162-
t.returnStatement(
163-
t.callExpression(
164-
t.memberExpression(
165-
t.identifier('Object'),
166-
t.identifier('assign')
167-
),
168-
[t.objectExpression(vars), t.identifier('existing')]
169-
)
170-
)
188+
t.returnStatement(t.objectExpression(vars))
171189
])
172190
)
173191
)
174192
} else {
175-
ref.replaceWith(t.identifier('undefined'))
193+
const NoVarsFound = t.identifier('undefined')
194+
NoVarsFound.trailingComments = [
195+
{ type: 'CommentLine', value: ' No CSS variables' }
196+
]
197+
ref.replaceWith(NoVarsFound)
176198
}
177199
}
200+
201+
if (hasVars && objectExpressionPath) {
202+
const createObjectCall = name => {
203+
return t.logicalExpression(
204+
'&&',
205+
t.memberExpression(t.identifier('options'), t.identifier(name)),
206+
t.callExpression(
207+
t.memberExpression(t.identifier('options'), t.identifier(name)),
208+
[t.identifier('vm')]
209+
)
210+
)
211+
}
212+
213+
objectExpressionPath.node.properties.push(
214+
t.objectProperty(
215+
t.identifier('_getCssVariables'),
216+
t.functionExpression(
217+
null,
218+
[
219+
t.identifier('vm'),
220+
t.identifier('options'),
221+
t.identifier('existing')
222+
],
223+
t.blockStatement([
224+
t.returnStatement(
225+
t.arrayExpression([
226+
t.identifier('existing'),
227+
createObjectCall('globalStyle'),
228+
createObjectCall('style')
229+
])
230+
)
231+
])
232+
)
233+
)
234+
)
235+
}
178236
}
179237

180238
// Remove the import
@@ -185,6 +243,8 @@ module.exports = script => {
185243
return {
186244
style,
187245
styleLang,
246+
globalStyle,
247+
globalStyleLang,
188248
hasVars,
189249
scriptContent: generator.default(ast).code
190250
}

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"test": "npm run lint && jest",
1414
"lint": "xo",
1515
"toc": "markdown-toc README.md -i",
16-
"example": "poi --config example/poi.config.js -so"
16+
"example": "poi --config example/poi.config.js -s"
1717
},
1818
"repository": {
1919
"url": "egoist/styled-vue",
@@ -27,7 +27,7 @@
2727
"@babel/traverse": "^7.2.3",
2828
"@babel/types": "^7.2.2",
2929
"posthtml": "^0.11.3",
30-
"vue-template-compiler": "^2.5.21"
30+
"vue-template-compiler": "^2.6.11"
3131
},
3232
"devDependencies": {
3333
"commitizen": "^3.0.5",
@@ -39,11 +39,11 @@
3939
"jest": "^23.6.0",
4040
"lint-staged": "^7.2.0",
4141
"markdown-toc": "^1.2.0",
42-
"poi": "^12.2.14",
42+
"poi": "^12.7.5",
4343
"polished": "^2.3.1",
4444
"prettier": "^1.15.2",
4545
"semantic-release": "^15.13.3",
46-
"vue": "^2.5.21",
46+
"vue": "^2.6.11",
4747
"xo": "^0.23.0"
4848
},
4949
"xo": {

0 commit comments

Comments
 (0)