├── .gitattributes
├── .gitignore
├── compiler.js
├── examples
└── nuxt
│ ├── nuxt.config.js
│ ├── package.json
│ └── pages
│ └── index.vue
├── .prettierrc
├── lib
├── compiler.js
├── index.js
├── units.js
├── parseComponent.js
└── parseScript.js
├── nuxt
├── plugin.js
└── index.js
├── example
├── index.js
├── poi.config.js
└── App.vue
├── .editorconfig
├── poi
└── index.js
├── circle.yml
├── .github
└── FUNDING.yml
├── LICENSE
├── package.json
├── test
├── parseComponent.test.js
└── __snapshots__
│ └── parseComponent.test.js.snap
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .nuxt
3 |
--------------------------------------------------------------------------------
/compiler.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/compiler')
2 |
--------------------------------------------------------------------------------
/examples/nuxt/nuxt.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | modules: ['styled-vue/nuxt']
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "bracketSpacing": true
5 | }
6 |
--------------------------------------------------------------------------------
/lib/compiler.js:
--------------------------------------------------------------------------------
1 | module.exports = require('vue-template-compiler')
2 | module.exports.parseComponent = require('./parseComponent')
3 |
--------------------------------------------------------------------------------
/nuxt/plugin.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | // eslint-disable-next-line import/no-unresolved
3 | import { StyledVue } from 'styled-vue'
4 |
5 | Vue.use(StyledVue)
6 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { StyledVue } from 'styled-vue'
3 | import App from './App.vue'
4 |
5 | Vue.use(StyledVue)
6 |
7 | new Vue({
8 | el: '#app',
9 | render: h => h(App)
10 | })
11 |
--------------------------------------------------------------------------------
/examples/nuxt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nuxt",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "nuxt": "^2.11.0",
8 | "styled-vue": "^0.3.3"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/poi/index.js:
--------------------------------------------------------------------------------
1 | exports.name = 'styled-vue'
2 |
3 | exports.apply = api => {
4 | api.hook('createWebpackChain', config => {
5 | config.module
6 | .rule('vue')
7 | .use('vue-loader')
8 | .tap(options => {
9 | return Object.assign({}, options, { compiler: require('../compiler') })
10 | })
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/nuxt/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = function({ registerStyledVue = true } = {}) {
4 | // Lazy load compiler on build only
5 | this.nuxt.hook('build:before', () => {
6 | const { vue } = this.options.build.loaders
7 | vue.compiler = require('../lib/compiler')
8 | })
9 |
10 | if (registerStyledVue) {
11 | this.addPlugin(path.join(__dirname, 'plugin.js'))
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/poi.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: path.join(__dirname, 'index.js'),
5 | chainWebpack(config) {
6 | config.module
7 | .rule('vue')
8 | .use('vue-loader')
9 | .tap(options => {
10 | options.compiler = require('../compiler')
11 | return options
12 | })
13 |
14 | config.resolve.alias.set('styled-vue', path.join(__dirname, '../lib'))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/nuxt/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Hello Nuxt
4 |
And hello styled-vue
5 |
This is awesome! (maybe)
6 |
7 |
8 |
9 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | Object.defineProperty(exports, '__esModule', { value: true })
3 |
4 | function css(_) {
5 | throw new Error(
6 | `You need to replace vue-template-compiler with styled-vue/compiler`
7 | )
8 | }
9 |
10 | function StyledVue(Vue) {
11 | Vue.component('styled-vue-global-css', {
12 | render: function(h) {
13 | var globalStyle = this.$parent.$options.globalStyle(this.$parent)
14 | var css = ''
15 | for (var key in globalStyle) {
16 | css += key + ':' + globalStyle[key] + ';'
17 | }
18 | return h('style', {}, [':root {' + css + '}'])
19 | }
20 | })
21 | }
22 |
23 | exports.css = css
24 |
25 | exports.StyledVue = StyledVue
26 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 |
2 | version: 2
3 | jobs:
4 | build:
5 | docker:
6 | - image: circleci/node:latest
7 | branches:
8 | ignore:
9 | - gh-pages # list of branches to ignore
10 | - /release\/.*/ # or ignore regexes
11 | steps:
12 | - checkout
13 | - restore_cache:
14 | key: dependency-cache-{{ checksum "yarn.lock" }}
15 | - run:
16 | name: install dependences
17 | command: yarn
18 | - save_cache:
19 | key: dependency-cache-{{ checksum "yarn.lock" }}
20 | paths:
21 | - ./node_modules
22 | - run:
23 | name: test
24 | command: yarn test
25 | - run:
26 | name: release
27 | command: yarn semantic-release
28 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: egoist # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/example/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
hello
4 |
5 | - something foo
6 | - something bar
7 |
8 |
9 |
10 |
11 |
45 |
--------------------------------------------------------------------------------
/lib/units.js:
--------------------------------------------------------------------------------
1 | // https://www.w3.org/TR/css-values-4/
2 | const UNITS = [
3 | // font relative lengths
4 | 'em',
5 | 'ex',
6 | 'cap',
7 | 'ch',
8 | 'ic',
9 | 'rem',
10 | 'lh',
11 | 'rlh',
12 |
13 | // viewport percentage lengths
14 | 'vw',
15 | 'vh',
16 | 'vi',
17 | 'vb',
18 | 'vmin',
19 | 'vmax',
20 |
21 | // absolute lengths
22 | 'cm',
23 | 'mm',
24 | 'Q',
25 | 'in',
26 | 'pc',
27 | 'pt',
28 | 'px',
29 |
30 | // angle units
31 | 'deg',
32 | 'grad',
33 | 'rad',
34 | 'turn',
35 |
36 | // duration units
37 | 's',
38 | 'ms',
39 |
40 | // frequency units
41 | 'Hz',
42 | 'kHz',
43 |
44 | // resolution units
45 | 'dpi',
46 | 'dpcm',
47 | 'dppx',
48 | 'x',
49 |
50 | // https://www.w3.org/TR/css-grid-1/#fr-unit
51 | 'fr',
52 |
53 | // percentages
54 | '%'
55 | ]
56 |
57 | const UNITS_RE = new RegExp(`(${UNITS.join('|')});?(\\s|\\n|$)`)
58 |
59 | module.exports = {
60 | UNITS,
61 | UNITS_RE
62 | }
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) EGOIST <0x142857@gmail.com> (https://egoist.sh)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "styled-vue",
3 | "version": "0.1.3",
4 | "description": "Zero-runtime CSS-in-JS for Vue.js.",
5 | "main": "lib/index.js",
6 | "files": [
7 | "lib",
8 | "nuxt",
9 | "poi",
10 | "compiler.js"
11 | ],
12 | "scripts": {
13 | "test": "npm run lint && jest",
14 | "lint": "xo",
15 | "toc": "markdown-toc README.md -i",
16 | "example": "poi --config example/poi.config.js -s"
17 | },
18 | "repository": {
19 | "url": "egoist/styled-vue",
20 | "type": "git"
21 | },
22 | "author": "egoist<0x142857@gmail.com>",
23 | "license": "MIT",
24 | "dependencies": {
25 | "@babel/generator": "^7.2.2",
26 | "@babel/parser": "^7.2.3",
27 | "@babel/traverse": "^7.2.3",
28 | "@babel/types": "^7.2.2",
29 | "hash-sum": "^2.0.0",
30 | "posthtml": "^0.11.3",
31 | "vue-template-compiler": "^2.6.11"
32 | },
33 | "devDependencies": {
34 | "commitizen": "^3.0.5",
35 | "cz-conventional-changelog": "^2.1.0",
36 | "eslint-config-prettier": "^3.3.0",
37 | "eslint-config-rem": "^4.0.0",
38 | "eslint-plugin-prettier": "^3.0.0",
39 | "husky": "^1.0.0-rc.13",
40 | "jest": "^23.6.0",
41 | "lint-staged": "^7.2.0",
42 | "markdown-toc": "^1.2.0",
43 | "poi": "^12.7.5",
44 | "polished": "^2.3.1",
45 | "prettier": "^1.15.2",
46 | "semantic-release": "^15.13.3",
47 | "vue": "^2.6.11",
48 | "xo": "^0.23.0"
49 | },
50 | "xo": {
51 | "extends": [
52 | "rem",
53 | "plugin:prettier/recommended"
54 | ],
55 | "ignores": [
56 | "**/example/**"
57 | ],
58 | "envs": [
59 | "jest"
60 | ],
61 | "rules": {
62 | "unicorn/filename-case": "off",
63 | "unicorn/no-abusive-eslint-disable": "off"
64 | }
65 | },
66 | "husky": {
67 | "hooks": {
68 | "pre-commit": "lint-staged"
69 | }
70 | },
71 | "lint-staged": {
72 | "*.js": [
73 | "xo --fix",
74 | "git add"
75 | ],
76 | "README.md": [
77 | "markdown-toc -i",
78 | "prettier --write",
79 | "git add"
80 | ],
81 | "*.{json,md,vue}": [
82 | "prettier --write",
83 | "git add"
84 | ]
85 | },
86 | "release": {
87 | "branch": "master"
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/parseComponent.js:
--------------------------------------------------------------------------------
1 | const { parseComponent } = require('vue-template-compiler')
2 | const posthtml = require('posthtml')
3 | const parseScript = require('./parseScript')
4 |
5 | module.exports = (content, opts) => {
6 | const sfc = parseComponent(content, opts)
7 |
8 | if (sfc.script) {
9 | const {
10 | style,
11 | styleLang,
12 | hasVars,
13 | scriptContent,
14 | hasGlobalVars,
15 | globalStyle,
16 | globalStyleLang
17 | } = parseScript(sfc.script)
18 |
19 | sfc.script.content = scriptContent
20 |
21 | if (sfc.template && (hasVars || hasGlobalVars)) {
22 | sfc.template.content = posthtml([
23 | tree => {
24 | for (const node of tree) {
25 | if (node.tag) {
26 | if (hasVars) {
27 | node.attrs = node.attrs || {}
28 | const existing =
29 | node.attrs[':style'] || node.attrs['v-bind:style']
30 | node.attrs[':style'] = `$options.style(this, ${existing})`
31 | }
32 | if (hasGlobalVars) {
33 | node.content = node.content || []
34 | node.content.unshift({
35 | tag: 'styled-vue-global-css'
36 | })
37 | }
38 | break
39 | }
40 | }
41 | return tree
42 | }
43 | ]).process(sfc.template.content, {
44 | sync: true,
45 | recognizeSelfClosing: true
46 | }).html
47 | }
48 |
49 | let contentLength = content.length
50 |
51 | const addStyleTag = (styleContent, styleLang, isGlobal) => {
52 | const style = {
53 | type: 'style',
54 | content: styleContent,
55 | attrs: {},
56 | // TODO: this might be wrong
57 | start: contentLength,
58 | end: contentLength + styleContent.length
59 | }
60 | if (styleLang) {
61 | style.lang = styleLang
62 | style.attrs.lang = styleLang
63 | }
64 | if (!isGlobal) {
65 | style.scoped = true
66 | style.attrs.scoped = true
67 | }
68 | sfc.styles.push(style)
69 | contentLength += styleContent.length
70 | }
71 |
72 | if (globalStyle) {
73 | addStyleTag(globalStyle, globalStyleLang, true)
74 | }
75 |
76 | if (style) {
77 | addStyleTag(style, styleLang, false)
78 | }
79 | }
80 |
81 | return sfc
82 | }
83 |
--------------------------------------------------------------------------------
/test/parseComponent.test.js:
--------------------------------------------------------------------------------
1 | const parseComponent = require('../lib/parseComponent')
2 |
3 | function snapshot(title, source) {
4 | test(title, () => {
5 | const sfc = parseComponent(source)
6 | const output = `
7 |
8 | ${sfc.template.content}
9 |
10 |
11 |
14 |
15 | ${sfc.styles
16 | .map(
17 | style => `
18 |
21 | `
22 | )
23 | .join('\n')}
24 | `
25 | expect(`
26 | === source ===
27 | ${source}
28 |
29 | === output ===
30 | ${output}
31 | `).toMatchSnapshot()
32 | })
33 | }
34 |
35 | snapshot(
36 | 'simple',
37 | `
38 |
39 | hello
40 |
41 |
52 | `
53 | )
54 |
55 | snapshot(
56 | 'no css',
57 | `
58 |
59 | hello
60 |
61 |
64 | `
65 | )
66 |
67 | snapshot(
68 | 'no vars',
69 | `
70 |
71 | hello
72 |
73 |
79 | `
80 | )
81 |
82 | snapshot(
83 | 'global style',
84 | `
85 |
86 | hello
87 |
88 |
94 | `
95 | )
96 |
97 | snapshot(
98 | 'global style with scoped style',
99 | `
100 |
101 | hello
102 |
103 |
110 | `
111 | )
112 |
113 | snapshot(
114 | 'global style with scoped style with css variables',
115 | `
116 |
117 | hello
118 |
119 |
126 | `
127 | )
128 |
--------------------------------------------------------------------------------
/test/__snapshots__/parseComponent.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`global style 1`] = `
4 | "
5 | === source ===
6 |
7 |
8 | hello
9 |
10 |
16 |
17 |
18 | === output ===
19 |
20 |
21 |
22 | hello
23 |
24 |
25 |
26 |
32 |
33 |
34 |
37 |
38 |
39 | "
40 | `;
41 |
42 | exports[`global style with scoped style 1`] = `
43 | "
44 | === source ===
45 |
46 |
47 | hello
48 |
49 |
56 |
57 |
58 | === output ===
59 |
60 |
61 |
62 | hello
63 |
64 |
65 |
66 |
74 |
75 |
76 |
79 |
80 |
81 |
84 |
85 |
86 | "
87 | `;
88 |
89 | exports[`global style with scoped style with css variables 1`] = `
90 | "
91 | === source ===
92 |
93 |
94 | hello
95 |
96 |
103 |
104 |
105 | === output ===
106 |
107 |
108 |
109 | hello
110 |
111 |
112 |
113 |
126 |
127 |
128 |
131 |
132 |
133 |
136 |
137 |
138 | "
139 | `;
140 |
141 | exports[`no css 1`] = `
142 | "
143 | === source ===
144 |
145 |
146 | hello
147 |
148 |
151 |
152 |
153 | === output ===
154 |
155 |
156 |
157 | hello
158 |
159 |
160 |
161 |
164 |
165 |
166 |
167 | "
168 | `;
169 |
170 | exports[`no vars 1`] = `
171 | "
172 | === source ===
173 |
174 |
175 | hello
176 |
177 |
183 |
184 |
185 | === output ===
186 |
187 |
188 |
189 | hello
190 |
191 |
192 |
193 |
199 |
200 |
201 |
204 |
205 |
206 | "
207 | `;
208 |
209 | exports[`simple 1`] = `
210 | "
211 | === source ===
212 |
213 |
214 | hello
215 |
216 |
227 |
228 |
229 | === output ===
230 |
231 |
232 |
233 | hello
234 |
235 |
236 |
237 |
250 |
251 |
252 |
260 |
261 |
262 | "
263 | `;
264 |
--------------------------------------------------------------------------------
/lib/parseScript.js:
--------------------------------------------------------------------------------
1 | const parser = require('@babel/parser')
2 | const traverse = require('@babel/traverse')
3 | const generator = require('@babel/generator')
4 | const t = require('@babel/types')
5 | const hashSum = require('hash-sum')
6 | const { UNITS_RE } = require('./units')
7 |
8 | const LANGS = ['css', 'stylus', 'less', 'sass', 'scss']
9 |
10 | module.exports = script => {
11 | let style
12 | let styleLang
13 | let globalStyle
14 | let globalStyleLang
15 | let hasVars = false
16 | let hasGlobalVars = false
17 |
18 | const ast = parser.parse(script.content, {
19 | sourceType: 'module',
20 | plugins: [
21 | 'jsx',
22 | 'typescript',
23 | 'objectRestSpread',
24 | 'classProperties',
25 | 'decorators-legacy',
26 | 'dynamicImport'
27 | ]
28 | })
29 |
30 | const parseTaggedTemplate = ref => {
31 | const { quasi } = ref.node
32 | const quasis = []
33 | const vars = []
34 | const varDeclarations = []
35 |
36 | for (const [i, q] of quasi.quasis.entries()) {
37 | quasis.push(q)
38 | if (quasi.expressions[i]) {
39 | const variableId = `v${hashSum(quasi.expressions[i])}`
40 | const value = `var(--${variableId})`
41 | quasis.push({
42 | type: 'TemplateElement',
43 | value: { raw: value, cooked: value }
44 | })
45 |
46 | // Check unit
47 | let unit
48 | const nextQuasis = quasi.quasis[i + 1]
49 | if (nextQuasis) {
50 | // Test the next 6 chars
51 | const match = UNITS_RE.exec(nextQuasis.value.raw.slice(0, 6))
52 | unit = match && match[1]
53 | if (unit) {
54 | nextQuasis.value.raw = nextQuasis.value.raw.slice(unit.length)
55 | nextQuasis.value.cooked = nextQuasis.value.cooked.slice(unit.length)
56 | }
57 | }
58 |
59 | // var v0 = vm => vm.color
60 | varDeclarations.push(
61 | t.variableDeclaration('var', [
62 | t.variableDeclarator(t.identifier(variableId), quasi.expressions[i])
63 | ])
64 | )
65 |
66 | // { '--v0': v0(vm) }
67 | // { '--v0': v0(vm) + unit }
68 | const id = t.identifier(variableId)
69 | const expType = quasi.expressions[i].type
70 | const mustBeFunction = [
71 | 'FunctionExpression',
72 | 'ArrowFunctionExpression'
73 | ].includes(expType)
74 | // TODO: add more conditions
75 | const mustNotBeFunction =
76 | expType.endsWith('Literal') || ['BinaryExpression'].includes(expType)
77 | const getValue = isFunction => {
78 | let leftExp
79 | if (isFunction) {
80 | leftExp = t.callExpression(id, [t.identifier('vm')])
81 | } else {
82 | leftExp = id
83 | }
84 | return unit
85 | ? t.binaryExpression('+', leftExp, t.stringLiteral(unit))
86 | : leftExp
87 | }
88 | vars.push(
89 | t.objectProperty(
90 | t.stringLiteral(`--${variableId}`),
91 | mustBeFunction
92 | ? getValue(true)
93 | : mustNotBeFunction
94 | ? getValue(false)
95 | : t.conditionalExpression(
96 | t.binaryExpression(
97 | '===',
98 | t.unaryExpression('typeof', id),
99 | t.stringLiteral('function')
100 | ),
101 | getValue(true),
102 | getValue(false)
103 | )
104 | )
105 | )
106 | }
107 | }
108 |
109 | const str = quasis.reduce((res, next) => {
110 | res += next.value.raw
111 | return res
112 | }, '')
113 |
114 | ref.node.quasi.quasis = [
115 | {
116 | type: 'TemplateElement',
117 | value: { raw: str, cooked: str },
118 | tail: true
119 | }
120 | ]
121 | ref.node.quasi.expressions = []
122 |
123 | return {
124 | extractedStyle: ref.get('quasi').evaluate().value,
125 | vars,
126 | varDeclarations
127 | }
128 | }
129 |
130 | traverse.default(ast, {
131 | ImportDeclaration(path) {
132 | if (path.node.source.value !== 'styled-vue') {
133 | return
134 | }
135 |
136 | for (const specifier of path.node.specifiers) {
137 | if (specifier.type !== 'ImportSpecifier') {
138 | continue
139 | }
140 |
141 | const lang = specifier.imported.name
142 |
143 | if (!LANGS.includes(lang)) {
144 | throw new Error(`[styled-vue] "${lang}" is not supported`)
145 | }
146 |
147 | const binding = path.scope.getBinding(specifier.local.name)
148 |
149 | for (let i = 0; i < binding.referencePaths.length; i++) {
150 | // The tagged template path
151 | const ref = binding.referencePaths[i].parentPath
152 | // The object property path
153 | const propertyPath = ref.parentPath
154 |
155 | if (
156 | !propertyPath ||
157 | propertyPath.node.type !== 'ObjectProperty' ||
158 | !['style', 'globalStyle'].includes(propertyPath.node.key.name)
159 | ) {
160 | throw new Error(
161 | `css tag must be assigned to component property "style" or "globalStyle"`
162 | )
163 | }
164 |
165 | const isGlobal = propertyPath.node.key.name === 'globalStyle'
166 | const { vars, varDeclarations, extractedStyle } = parseTaggedTemplate(
167 | ref
168 | )
169 | if (isGlobal) {
170 | globalStyle = extractedStyle
171 | globalStyleLang = lang
172 | if (vars.length > 0) {
173 | hasGlobalVars = true
174 | }
175 | } else {
176 | style = extractedStyle
177 | styleLang = lang
178 | if (vars.length > 0) {
179 | hasVars = true
180 | }
181 | }
182 |
183 | if (vars.length > 0) {
184 | ref.replaceWith(
185 | t.functionExpression(
186 | null,
187 | [
188 | t.identifier('vm'),
189 | !isGlobal && t.identifier('existing') // Global vars are handled differently
190 | ].filter(Boolean),
191 | t.blockStatement([
192 | ...varDeclarations,
193 | t.returnStatement(
194 | isGlobal
195 | ? t.objectExpression(vars)
196 | : t.arrayExpression([
197 | t.identifier('existing'),
198 | t.objectExpression(vars)
199 | ])
200 | )
201 | ])
202 | )
203 | )
204 | } else {
205 | const NoVarsFound = t.identifier('undefined')
206 | NoVarsFound.trailingComments = [
207 | { type: 'CommentLine', value: ' No CSS variables' }
208 | ]
209 | ref.replaceWith(NoVarsFound)
210 | }
211 | }
212 | }
213 |
214 | // Remove the import
215 | path.remove()
216 | }
217 | })
218 |
219 | return {
220 | style,
221 | styleLang,
222 | globalStyle,
223 | globalStyleLang,
224 | hasGlobalVars,
225 | hasVars,
226 | scriptContent: generator.default(ast).code
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ATTENTION: this project is not actively maintained for now (I still push some code once in a while), if you want to see improvements or get support, please consider [sponsoring me](https://github.com/sponsors/egoist).
2 |
3 | 
4 |
5 | ---
6 |
7 | [](https://npmjs.com/package/styled-vue) [](https://npmjs.com/package/styled-vue) [](https://circleci.com/gh/egoist/styled-vue/tree/master) [](https://chat.egoist.sh)
8 |
9 | **Please consider [donating](https://github.com/sponsors/egoist) to this project's author, [EGOIST](#author), to show your ❤️ and support.**
10 |
11 | ## Features
12 |
13 | - Zero-runtime CSS-in-JS for Vue SFC without compromise
14 | - Scoped CSS by default
15 | - CSS preprocessors support
16 | - Dynamic style just works (no IE support)
17 | - Working with SSR out-of-the-box
18 | - Hot module replacement still works
19 | - You get all the features without any config!
20 |
21 | ## Table of Contents
22 |
23 |
24 |
25 | - [Install](#install)
26 | - [Example](#example)
27 | - [How to use](#how-to-use)
28 | - [Using with webpack](#using-with-webpack)
29 | - [Using with Vue CLI](#using-with-vue-cli)
30 | - [Using with Poi](#using-with-poi)
31 | - [Using with Nuxt.js](#using-with-nuxtjs)
32 | - [How does it work](#how-does-it-work)
33 | - [CSS Preprocessors](#css-preprocessors)
34 | - [Global Styles](#global-styles)
35 | - [TypeScript](#typescript)
36 | - [Editor Plugins](#editor-plugins)
37 | - [VSCode](#vscode)
38 | - [Atom](#atom)
39 | - [Inspirations](#inspirations)
40 | - [Contributing](#contributing)
41 | - [Author](#author)
42 |
43 |
44 |
45 | ## Install
46 |
47 | ```bash
48 | yarn add styled-vue --dev
49 | ```
50 |
51 | Then register the Vue plugin (**optional**):
52 |
53 | ```js
54 | import Vue from 'vue'
55 | import { StyledVue } from 'styled-vue'
56 |
57 | Vue.use(StyledVue)
58 | ```
59 |
60 | So far the plugin is only required for [globalStyle](#global-styles), if you only need scoped style, you can safely skip this.
61 |
62 | ## Example
63 |
64 | ```vue
65 |
66 | hello there!
67 |
68 |
69 |
84 | ```
85 |
86 | And that's it, it's like writing `.vue` file's scoped CSS in the `
169 | ```
170 |
171 | Output:
172 |
173 | ```vue
174 |
175 | hello
176 |
177 |
178 |
190 |
191 |
197 | ```
198 |
199 | 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.
200 |
201 | ### CSS Preprocessors
202 |
203 | ```js
204 | import { less, sass, scss, stylus } from 'styled-vue'
205 | ```
206 |
207 | Just use corresponding exports from `styled-vue`.
208 |
209 | The CSS will be passed to `vue-loader` and parsed by PostCSS if a `postcss.config.js` exists in your project, so it really just works like `