Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit 22d6793

Browse files
sunnylostznck
authored andcommitted
feat: add support postcss user config (#125)
1 parent 51c561b commit 22d6793

File tree

10 files changed

+281
-90
lines changed

10 files changed

+281
-90
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"merge-options": "0.0.64",
4545
"parse5": "^2.1.0",
4646
"postcss": "^5.2.11",
47+
"postcss-load-config": "^1.2.0",
4748
"postcss-modules": "^0.6.4",
4849
"postcss-selector-parser": "^2.2.3",
4950
"posthtml": "^0.9.2",
@@ -55,6 +56,7 @@
5556
"vue-template-validator": "^1.1.5"
5657
},
5758
"devDependencies": {
59+
"autoprefixer": "^7.1.2",
5860
"babel-eslint": "^7.1.1",
5961
"babel-plugin-transform-runtime": "^6.22.0",
6062
"babel-preset-es2015": "^6.22.0",

src/options.js

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export default {
6666
// Config for stylus.
6767
stylus: {},
6868

69+
// Config for postcss.
70+
postcss: {},
71+
6972
// Config for pug compiler.
7073
pug: {},
7174

src/style/css.js

+65-70
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import postcss from 'postcss'
2+
import postcssLoadConfig from './postcss'
23
import modules from 'postcss-modules'
34
import selectorParser from 'postcss-selector-parser'
45
import camelcase from 'camelcase'
@@ -21,7 +22,7 @@ function isInvalidTag (tag) {
2122
}
2223
}
2324

24-
const addScopeID = postcss.plugin('add-scope-id', options => {
25+
const addScopeID = postcss.plugin('add-scope-id', ({ scopeID }) => {
2526
const selectorTransformer = selectorParser(selectors => {
2627
selectors.each(selector => {
2728
let target = null
@@ -55,7 +56,7 @@ const addScopeID = postcss.plugin('add-scope-id', options => {
5556
/* eslint-enable complexity */
5657

5758
target && selector.insertAfter(target, selectorParser.attribute({
58-
attribute: options.scopeID
59+
attribute: scopeID
5960
}))
6061
})
6162
})
@@ -78,25 +79,9 @@ function compileModule (code, map, source, options) {
7879
},
7980
...options.cssModules
8081
})
81-
]).process(code, { map: { inline: false, prev: map }, from: source.id, to: source.id })
82-
.then(
83-
result => ({ code: result.css, map: result.map.toString(), module: style }),
84-
error => {
85-
throw error
86-
}
87-
)
88-
}
89-
90-
function compileScopedCSS (code, map, source, options) {
91-
debug(`Scoped CSS: ${source.id}`)
92-
93-
return postcss([
94-
addScopeID({
95-
scopeID: genScopeID(source.id)
96-
})
9782
]).process(code, { map: { inline: false, prev: map }, from: source.id, to: source.id })
9883
.then(
99-
result => ({ code: result.css, map: result.map.toString() }),
84+
result => ({ code: result.css, map: result.map.toString(), module: style }),
10085
error => {
10186
throw error
10287
}
@@ -110,62 +95,72 @@ function escapeRegExp (str) {
11095
export default async function (promise, options) {
11196
const style = await promise
11297
debug(`CSS: ${style.id}`)
113-
const { code, map } = ('$compiled' in style) ? style.$compiled : style
114-
115-
if (style.module === true) {
116-
return compileModule(code, map, style, options).then(compiled => {
117-
if (style.$compiled) {
118-
compiled.$prev = style.$compiled
119-
120-
const classes = Object.keys(compiled.module)
121-
const cssModule = {}
122-
123-
if (classes.length) {
124-
// Apply CSS modules to actual source.
125-
// TODO: Update source map.
126-
// const original = style.code
127-
128-
style.code = classes.reduce(
129-
(result, original) => {
130-
const transformed = compiled.module[original]
131-
cssModule[camelcase(original)] = transformed
132-
cssModule[original] = transformed
133-
134-
return result.replace(new RegExp(escapeRegExp(`.${original}`), 'g'), `.${transformed}`)
135-
},
136-
style.code
137-
)
138-
// style.map = (new MagicString(original))
139-
140-
compiled.module = (
141-
typeof (style.module) === 'string' && style.attrs.module.length
142-
) ? { [style.module]: cssModule } : cssModule
143-
}
144-
}
145-
146-
style.$compiled = compiled
147-
148-
return style
149-
}).catch(error => debug(error))
98+
const {code, map} = ('$compiled' in style) ? style.$compiled : style
99+
const initPostcssOptions = {map: {inline: false, prev: map}, from: style.id, to: style.id}
100+
const hasModule = style.module === true
101+
const hasScope = style.scoped === true
102+
const postcssConfig = await postcssLoadConfig(options.postcss)
103+
const plugins = postcssConfig.plugins || []
104+
let processPromise = Promise.resolve()
105+
106+
if (hasScope) {
107+
debug(`Scoped CSS: ${style.id}`)
108+
plugins.push(addScopeID({
109+
scopeID: genScopeID(style.id)
110+
}))
150111
}
151112

152-
if (style.scoped === true) {
153-
return compileScopedCSS(code, map, style, options).then(compiled => {
154-
if (style.$compiled) {
155-
compiled.$prev = style.$compiled
156-
}
157-
158-
style.$compiled = compiled
159-
160-
return style
161-
})
113+
if (hasModule) {
114+
// TODO: I found this plugin makes all postcss plugin run twice.
115+
processPromise = compileModule(code, map, style, options)
162116
}
163117

164-
const output = { code, map, lang: 'css' }
118+
const curOptions = Object.assign({}, postcssConfig.options, initPostcssOptions)
165119

166-
if (style.$compiled) output.$prev = style.$compiled
120+
return processPromise.then(firstResult => {
121+
const moduleNames = firstResult && firstResult.module
122+
return postcss(plugins)
123+
.process(firstResult ? firstResult.code : code, curOptions)
124+
.then(result => {
125+
const compiled = {
126+
code: result.css,
127+
map: result.map.toString()
128+
}
129+
if (style.$compiled) {
130+
compiled.$prev = style.$compiled
131+
}
167132

168-
style.$compiled = output
133+
if (hasModule) {
134+
const classes = Object.keys(moduleNames)
135+
const cssModule = {}
136+
137+
if (classes.length) {
138+
// Apply CSS modules to actual source.
139+
// TODO: Update source map.
140+
// const original = style.code
141+
142+
style.code = classes.reduce(
143+
(result, original) => {
144+
const transformed = moduleNames[original]
145+
cssModule[camelcase(original)] = transformed
146+
cssModule[original] = transformed
147+
148+
return result.replace(new RegExp(escapeRegExp(`.${original}`), 'g'), `.${transformed}`)
149+
},
150+
style.code
151+
)
152+
// style.map = (new MagicString(original))
153+
154+
compiled.module = (
155+
typeof (style.module) === 'string' && style.attrs.module.length
156+
) ? {[style.module]: cssModule} : cssModule
157+
}
158+
}
159+
160+
style.$compiled = compiled
169161

170-
return style
162+
return style
163+
})
164+
.catch(error => debug(error))
165+
})
171166
}

src/style/postcss.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import postcssrc from 'postcss-load-config'
2+
3+
export default async function (postcssOpt) {
4+
let options = {}
5+
let plugins = []
6+
7+
if (typeof postcssOpt === 'function') {
8+
plugins = postcssOpt.call(this)
9+
} else if (Array.isArray(postcssOpt)) {
10+
plugins = plugins.concat(postcssOpt)
11+
} else if (typeof postcssOpt === 'object') {
12+
options = Object.assign({}, options, postcssOpt)
13+
}
14+
15+
return postcssrc().then((config) => {
16+
if (config.plugins) {
17+
plugins = plugins.concat(config.plugins)
18+
}
19+
20+
if (config.options) {
21+
options = Object.assign(options, config.options)
22+
}
23+
return {plugins, options}
24+
}).catch(() => { return {plugins, options} })
25+
}

test/expects/postcss.css

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
body {
3+
display: -webkit-box;
4+
display: -ms-flexbox;
5+
display: flex;
6+
}

test/expects/postcss.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
var postcss = {};
2+
3+
export default postcss;

test/expects/scoped-css-with-deep-tag.css

+15-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
background-color: red;
77
}
88

9-
@keyframes test {
9+
@-webkit-keyframes test {
1010
0% {
1111
color: red;
1212
}
@@ -19,4 +19,17 @@
1919
color: yellow;
2020
}
2121
}
22-
22+
23+
@keyframes test {
24+
0% {
25+
color: red;
26+
}
27+
28+
50% {
29+
color: green;
30+
}
31+
32+
100% {
33+
color: yellow;
34+
}
35+
}

test/fixtures/postcss.vue

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
export default {}
3+
</script>
4+
<style>
5+
body {
6+
display: flex;
7+
}
8+
</style>

test/test.js

+20-18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var assert = require('assert')
55
var fs = require('fs')
66
var rollup = require('rollup')
77
var path = require('path')
8+
var autoprefixer = require('autoprefixer')
89

910
process.chdir(__dirname)
1011

@@ -33,6 +34,7 @@ function test(name) {
3334
modules: {
3435
generateScopedName: '[name]__[local]'
3536
},
37+
postcss: [autoprefixer()],
3638
compileTemplate: [
3739
'compileTemplate',
3840
'compileTemplateLocalComponent',
@@ -49,23 +51,24 @@ function test(name) {
4951

5052
// Check css output
5153
if ([
52-
'css-modules',
53-
'css-modules-static',
54-
'import-scss',
55-
'import-less',
56-
'less',
57-
'pug',
58-
'scoped-css',
59-
'scoped-css-with-no-auto-style',
60-
'scoped-css-with-deep-tag',
61-
'scss',
62-
'sass',
63-
'pug',
64-
'less',
65-
'style',
66-
'stylus',
67-
'external-script'
68-
].indexOf(name) > -1) {
54+
'css-modules',
55+
'css-modules-static',
56+
'import-scss',
57+
'import-less',
58+
'less',
59+
'pug',
60+
'scoped-css',
61+
'scoped-css-with-no-auto-style',
62+
'scoped-css-with-deep-tag',
63+
'scss',
64+
'sass',
65+
'pug',
66+
'less',
67+
'style',
68+
'stylus',
69+
'external-script',
70+
'postcss'
71+
].indexOf(name) > -1) {
6972
var css = read('expects/' + name + '.css')
7073
assert.equal(css.trim(), actualCss.trim(), 'should output style tag content')
7174
} else if (['no-css-extract'].indexOf(name) > -1) {
@@ -114,4 +117,3 @@ describe('styleToImports', function () {
114117
})
115118
})
116119
})
117-

0 commit comments

Comments
 (0)