Skip to content

Commit f2fd8b9

Browse files
authored
feat: adds stylus & less preprocessor (#5)
* test: style preprocess * fix: postcss is undefined issue by using import syntax * feat: adds less preprocessor * feat: adds stylus preprocessor * chore: remove unnecessary dir in test script * chore: add circleci config
1 parent 9204f16 commit f2fd8b9

File tree

9 files changed

+671
-54
lines changed

9 files changed

+671
-54
lines changed

.circleci/config.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Javascript Node CircleCI 2.0 configuration file
2+
#
3+
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
4+
#
5+
version: 2
6+
jobs:
7+
build:
8+
docker:
9+
- image: circleci/node:6
10+
11+
working_directory: ~/repo
12+
13+
steps:
14+
- checkout
15+
16+
# Download and cache dependencies
17+
- restore_cache:
18+
keys:
19+
- v1-dependencies-{{ checksum "package.json" }}
20+
# fallback to using the latest cache if no exact match is found
21+
- v1-dependencies-
22+
23+
- run: yarn install
24+
25+
- save_cache:
26+
paths:
27+
- node_modules
28+
key: v1-dependencies-{{ checksum "package.json" }}
29+
30+
# run tests!
31+
- run: yarn test

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ module.exports = {
33
transform: {
44
'^.+\\.jsx?$': 'babel-jest',
55
'^.+\\.tsx?$': '<rootDir>/node_modules/ts-jest/preprocessor.js'
6-
}
6+
},
7+
testMatch: ['**/?(*.)(spec|test).ts']
78
}

lib/compileStyle.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ProcessOptions, LazyResult } from 'postcss'
2-
import postcss = require('postcss')
1+
import postcss, { ProcessOptions, LazyResult } from 'postcss'
32
import trimPlugin from './stylePlugins/trim'
43
import scopedPlugin from './stylePlugins/scoped'
54
import { processors, StylePreprocessor, StylePreprocessorResults } from './styleProcessors'
@@ -59,6 +58,9 @@ export function compileStyle (
5958

6059
let result, code, outMap
6160
const errors = []
61+
if (preProcessedSource && preProcessedSource.errors.length) {
62+
errors.push(...preProcessedSource.errors)
63+
}
6264
try {
6365
result = postcss(plugins).process(source, postCSSOptions)
6466
// force synchronous transform (we know we only have sync plugins)

lib/stylePlugins/scoped.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Root } from 'postcss'
2-
import postcss = require('postcss')
2+
import * as postcss from 'postcss'
33
// postcss-selector-parser does have typings but it's problematic to work with.
44
const selectorParser = require('postcss-selector-parser')
55

lib/stylePlugins/trim.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Root } from 'postcss'
2-
import postcss = require('postcss')
2+
import * as postcss from 'postcss'
33

44
export default postcss.plugin('trim', () => (css: Root) => {
55
css.walk(({ type, raws }) => {

lib/styleProcessors/index.ts

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface StylePreprocessor {
1111
export interface StylePreprocessorResults {
1212
code: string
1313
map?: any
14+
errors: Array<Error>
1415
}
1516

1617
// .scss/.sass processor
@@ -24,19 +25,25 @@ const scss: StylePreprocessor = {
2425
const finalOptions = Object.assign({}, options, {
2526
data: source,
2627
file: options.filename,
28+
outFile: options.filename,
2729
sourceMap: !!map
2830
})
2931

30-
const result = nodeSass.renderSync(finalOptions)
32+
try {
33+
const result = nodeSass.renderSync(finalOptions)
3134

32-
if (map) {
33-
return {
34-
code: result.css.toString(),
35-
map: merge(map, JSON.parse(result.map.toString()))
35+
if (map) {
36+
return {
37+
code: result.css.toString(),
38+
map: merge(map, JSON.parse(result.map.toString())),
39+
errors: []
40+
}
3641
}
37-
}
3842

39-
return { code: result.css.toString() }
43+
return { code: result.css.toString(), errors: [] }
44+
} catch (e) {
45+
return { code: '', errors: [e] }
46+
}
4047
}
4148
}
4249

@@ -54,7 +61,72 @@ const sass = {
5461
}
5562
}
5663

64+
// .less
65+
const less = {
66+
render(
67+
source: string,
68+
map: any | null,
69+
options: any
70+
): StylePreprocessorResults {
71+
const nodeLess = require('less')
72+
73+
let result: any
74+
let error: Error | null = null
75+
nodeLess.render(
76+
source,
77+
{ syncImport: true },
78+
(err: Error | null, output: any) => {
79+
error = err
80+
result = output
81+
}
82+
)
83+
84+
if (error) return { code: '', errors: [error] }
85+
86+
if (map) {
87+
return {
88+
code: result.css.toString(),
89+
map: merge(map, result.map),
90+
errors: []
91+
}
92+
}
93+
94+
return { code: result.css.toString(), errors: [] }
95+
}
96+
}
97+
98+
// .styl
99+
const styl = {
100+
render(
101+
source: string,
102+
map: any | null,
103+
options: any
104+
): StylePreprocessorResults {
105+
const nodeStylus = require('stylus')
106+
try {
107+
const ref = nodeStylus(source)
108+
Object.keys(options).forEach(key => ref.set(key, options[key]))
109+
if (map) ref.set('sourcemap', { inline: false, comment: false })
110+
111+
const result = ref.render()
112+
if (map) {
113+
return {
114+
code: result,
115+
map: merge(map, ref.sourcemap),
116+
errors: []
117+
}
118+
}
119+
120+
return { code: result, errors: [] }
121+
} catch (e) {
122+
return { code: '', errors: [e] }
123+
}
124+
}
125+
}
126+
57127
export const processors: { [key: string]: StylePreprocessor } = {
128+
less,
129+
sass,
58130
scss,
59-
sass
131+
styl
60132
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
"devDependencies": {
2929
"@types/node": "^9.4.7",
3030
"jest": "^22.4.2",
31+
"less": "^3.0.1",
32+
"node-sass": "^4.8.3",
3133
"pug": "^2.0.3",
34+
"stylus": "^0.54.5",
3235
"ts-jest": "^22.4.2",
3336
"typescript": "^2.7.2",
3437
"vue-template-compiler": "^2.5.16"

test/compileStyle.spec.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { parse } from '../lib/parse'
2+
import { compileStyle } from '../lib/compileStyle'
3+
4+
test('preprocess less', () => {
5+
const style = parse({
6+
source:
7+
'<style lang="less">\n' +
8+
'@red: rgb(255, 0, 0);\n' +
9+
'.color { color: @red; }\n' +
10+
'</style>\n',
11+
filename: 'example.vue',
12+
needMap: true
13+
}).styles[0]
14+
const result = compileStyle({
15+
id: 'v-scope-xxx',
16+
filename: 'example.vue',
17+
source: style.content,
18+
map: style.map,
19+
scoped: false,
20+
preprocessLang: style.lang
21+
})
22+
23+
expect(result.errors.length).toBe(0)
24+
expect(result.code).toEqual(expect.stringContaining('color: #ff0000;'))
25+
expect(result.map).toBeTruthy()
26+
})
27+
28+
test('preprocess scss', () => {
29+
const style = parse({
30+
source:
31+
'<style lang="scss">\n' +
32+
'$red: rgb(255, 0, 0);\n' +
33+
'.color { color: $red; }\n' +
34+
'</style>\n',
35+
filename: 'example.vue',
36+
needMap: true
37+
}).styles[0]
38+
const result = compileStyle({
39+
id: 'v-scope-xxx',
40+
filename: 'example.vue',
41+
source: style.content,
42+
map: style.map,
43+
scoped: false,
44+
preprocessLang: style.lang
45+
})
46+
47+
expect(result.errors.length).toBe(0)
48+
expect(result.code).toEqual(expect.stringContaining('color: red;'))
49+
expect(result.map).toBeTruthy()
50+
})
51+
52+
test('preprocess sass', () => {
53+
const style = parse({
54+
source:
55+
'<style lang="sass">\n' +
56+
'$red: rgb(255, 0, 0);\n' +
57+
'.color\n' +
58+
' color: $red\n' +
59+
'</style>\n',
60+
filename: 'example.vue',
61+
needMap: true
62+
}).styles[0]
63+
const result = compileStyle({
64+
id: 'v-scope-xxx',
65+
filename: 'example.vue',
66+
source: style.content,
67+
map: style.map,
68+
scoped: false,
69+
preprocessLang: style.lang
70+
})
71+
72+
expect(result.errors.length).toBe(0)
73+
expect(result.code).toEqual(expect.stringContaining('color: red;'))
74+
expect(result.map).toBeTruthy()
75+
})
76+
77+
test('preprocess stylus', () => {
78+
const style = parse({
79+
source:
80+
'<style lang="styl">\n' +
81+
'red-color = rgb(255, 0, 0);\n' +
82+
'.color\n' +
83+
' color: red-color\n' +
84+
'</style>\n',
85+
filename: 'example.vue',
86+
needMap: true
87+
}).styles[0]
88+
const result = compileStyle({
89+
id: 'v-scope-xxx',
90+
filename: 'example.vue',
91+
source: style.content,
92+
map: style.map,
93+
scoped: false,
94+
preprocessLang: style.lang
95+
})
96+
97+
expect(result.errors.length).toBe(0)
98+
expect(result.code).toEqual(expect.stringContaining('color: #f00;'))
99+
expect(result.map).toBeTruthy()
100+
})

0 commit comments

Comments
 (0)