Skip to content
This repository was archived by the owner on Feb 16, 2021. It is now read-only.

Commit a59b9cc

Browse files
committed
feat(plugin): Support for postcss-modules
If you are using postcss-modules the plugin has to be able to create a typescript file with the transformed classes close #5
1 parent 6d658bd commit a59b9cc

8 files changed

+125
-33
lines changed

src/buildFile.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ const filename = path.basename(file.cssFileName, '.postcss');
88
return (
99
`export const ${filename}Style = {
1010
${
11-
file.content.map((c: string) => {
12-
return ` ${camelCase(c)}: '${c}'\n`;
13-
})
11+
file.content instanceof Array ? (
12+
file.content.map((c: string) => {
13+
return ` ${camelCase(c)}: '${c}'\n`;
14+
})
15+
) : (
16+
Object.keys(file.content).map((c: string) => {
17+
return ` ${camelCase(c)}: '${(<{[key: string]: string }>file.content)[c]}'\n`;
18+
})
19+
)
1420
},
1521
};
1622
`

src/namespace/PostcssTypescriptCss.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export namespace PostcssTypescriptCss {
22
export interface Options {
33
cssFileName: string;
4-
content: string[];
4+
content: { [key: string]: string } | string[];
55
}
66
}

src/postcss-typescript-css.ts

+21-16
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@ import saveFile from './saveFile';
77
const postcssTypescriptCss = postcss.plugin<PostcssTypescriptCss.Options>('postcss-typescript-css', (opts) => {
88
return (css, result) => {
99
return new Promise((resolve, reject) => {
10-
const classes = new Set();
11-
css.walk((node: postcss.Rule) => {
12-
if (node.type === 'rule') {
13-
parser((selectors: postcss.Rule) => {
14-
selectors.walk((selector: any) => {
15-
if (selector.type === 'class') {
16-
classes.add(selector.value);
17-
}
18-
});
19-
}).process(node.selector).result;
20-
}
21-
});
22-
opts = {
23-
cssFileName: css.source.input.file,
24-
content: [...classes],
25-
};
10+
if (opts) {
11+
if (!opts.cssFileName) reject('The property cssFileName can not be null');
12+
if (!opts.content) reject('The property content can not be null');
13+
} else {
14+
const classes = new Set();
15+
css.walk((node: postcss.Rule) => {
16+
if (node.type === 'rule') {
17+
parser((selectors: postcss.Rule) => {
18+
selectors.walk((selector: any) => {
19+
if (selector.type === 'class') {
20+
classes.add(selector.value);
21+
}
22+
});
23+
}).process(node.selector).result;
24+
}
25+
});
26+
opts = {
27+
cssFileName: css.source.input.file,
28+
content: [...classes],
29+
};
30+
}
2631
saveFile(opts)
2732
.then(() => {
2833
resolve();

src/spec/buildFile.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,18 @@ test('should create a string with the classes as a properties', t => {
1111
t.true(template.includes('fakeComponentDescendentName:'));
1212
t.true(template.includes('fakeComponentModifierName:'));
1313
});
14+
15+
test('should create a string with transformed classes', t => {
16+
const template = build({
17+
cssFileName: 'fakeComponentModules',
18+
content: {
19+
fakeComponentModules: '_FakeComponentModules_h7423_1',
20+
fakeComponentModulesDescendentName: '_FakeComponentModules-descendentName_h7423_1',
21+
fakeComponentModulesModifierName: '_FakeComponentModules--modifierName_h7423_1'
22+
}
23+
});
24+
t.true(template.includes('fakeComponentModulesStyle'));
25+
t.true(template.includes('fakeComponentModules:'));
26+
t.true(template.includes('fakeComponentModulesDescendentName:'));
27+
t.true(template.includes('fakeComponentModulesModifierName:'));
28+
});

src/spec/postcss-typescript-css.spec.ts

+38-9
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,35 @@ import * as postcss from 'postcss';
66
import { PostcssTypescriptCss } from '../namespace/PostcssTypescriptCss';
77
import * as plugin from '../postcss-typescript-css';
88

9-
function run(t: TestContext, input: {css: string, from: string}) {
10-
return postcss([ plugin ]).process(input.css, { from: input.from })
9+
function run(t: TestContext, input: {css: string, from: string}, opts?: PostcssTypescriptCss.Options) {
10+
return postcss([ plugin(opts) ]).process(input.css, { from: input.from })
1111
.then((result) => {
12-
const fakeComponentTS = readFileSync(path.join(__dirname, 'postcss/fakeComponent.ts'), 'utf8');
13-
t.true(fakeComponentTS.includes('fakeComponentStyle'));
14-
t.true(fakeComponentTS.includes('fakeComponent:'));
15-
t.true(fakeComponentTS.includes('fakeComponentDescendentName:'));
16-
t.true(fakeComponentTS.includes('fakeComponentModifierName:'));
12+
let fakeComponentTS;
13+
if (opts) {
14+
fakeComponentTS = readFileSync(path.join(__dirname, 'postcss/fakeComponentModules.ts'), 'utf8');
15+
t.true(fakeComponentTS.includes('fakeComponentModulesStyle'));
16+
t.true(fakeComponentTS.includes('fakeComponentModules:'));
17+
t.true(fakeComponentTS.includes('fakeComponentModulesDescendentName:'));
18+
t.true(fakeComponentTS.includes('fakeComponentModulesModifierName:'));
19+
} else {
20+
fakeComponentTS = readFileSync(path.join(__dirname, 'postcss/fakeComponent.ts'), 'utf8');
21+
t.true(fakeComponentTS.includes('fakeComponentStyle'));
22+
t.true(fakeComponentTS.includes('fakeComponent:'));
23+
t.true(fakeComponentTS.includes('fakeComponentDescendentName:'));
24+
t.true(fakeComponentTS.includes('fakeComponentModifierName:'));
25+
}
1726
})
1827
.catch((err: string) => {
19-
t.true(err.includes('TypeError: Path must be a string'));
28+
if (opts) {
29+
if (!opts.cssFileName) {
30+
t.is(err, 'The property cssFileName can not be null');
31+
}
32+
if (!opts.content) {
33+
t.is(err, 'The property content can not be null');
34+
}
35+
} else {
36+
t.true(err.includes('TypeError: Path must be a string'));
37+
}
2038
});
2139
}
2240

@@ -26,6 +44,17 @@ test('should create a ts file', t => {
2644
return run(t, { css: cssContent, from: cssFile });
2745
});
2846

47+
test('should create a ts file with postcss-modules configuration', t => {
48+
const cssFileName = path.join(__dirname, 'postcss/fakeComponentModules.postcss');
49+
const content = JSON.parse(readFileSync(path.join(__dirname, 'postcss/fakeComponentModules.json'), 'utf8'));
50+
return run(t, { css: '', from: '' }, { cssFileName, content });
51+
});
52+
2953
test('throws if cssFileName is null', t => {
30-
return run(t, { css: '', from: '' });
54+
return run(t, { css: '', from: '' }, { cssFileName: null, content: ['.class'] });
55+
});
56+
57+
test('throws if content is null', t => {
58+
const cssFileName = path.join(__dirname, 'postcss/fakeComponentModules.postcss');
59+
return run(t, { css: '', from: '' }, { cssFileName, content: null });
3160
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"FakeComponentModules":"_FakeComponentModules_h7423_1","FakeComponentModules-descendentName":"_FakeComponentModules-descendentName_h7423_1", "FakeComponentModules--modifierName":"_FakeComponentModules--modifierName_h7423_1"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
._FakeComponentModules_h7423_1 {
2+
color: red;
3+
}
4+
5+
._FakeComponentModules-descendentName_h7423_1 {
6+
color: yellow;
7+
}
8+
9+
._FakeComponentModules--modifierName_h7423_1 {
10+
color: green;
11+
}

src/spec/saveFile.spec.ts

+29-4
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,48 @@ import test from 'ava';
22
import saveFile from './../saveFile';
33
const path = require('path');
44

5-
const cssFileName = path.join(__dirname, 'postcss/fakeComponent2.postcss');
65

76
test('should create a new ts file', async (t) => {
7+
const cssFileName = path.join(__dirname, 'postcss/fakeComponent2.postcss');
88
t.is(await saveFile({
99
cssFileName,
10-
content: ['.FakeComponent', '.FakeComponent2-descendentName', '.FakeComponent2--modifierName']
10+
content: ['.FakeComponent2', '.FakeComponent2-descendentName', '.FakeComponent2--modifierName']
1111
}), true);
1212
const tsFile = path.join(__dirname, 'postcss/fakeComponent2.ts');
1313
const tsFileName = path.basename(tsFile, '.ts');
1414
t.is(tsFileName, 'fakeComponent2');
1515
});
1616

17-
test('throws if there is not a file name or content', async (t) => {
17+
test('should create a new ts file with transformed classes', async (t) => {
18+
const cssFileName = path.join(__dirname, 'postcss/fakeComponentModules.postcss');
19+
t.is(await saveFile({
20+
cssFileName,
21+
content: {
22+
fakeComponentModules: '_FakeComponentModules_h7423_1',
23+
fakeComponentModulesDescendentName: '_FakeComponentModules-descendentName_h7423_1',
24+
fakeComponentModulesModifierName: '_FakeComponentModules--modifierName_h7423_1'
25+
}
26+
}), true);
27+
const tsFile = path.join(__dirname, 'postcss/fakeComponentModules.ts');
28+
const tsFileName = path.basename(tsFile, '.ts');
29+
t.is(tsFileName, 'fakeComponentModules');
30+
});
31+
32+
test('throws if there is not a css file name', async (t) => {
1833
saveFile({
1934
cssFileName: null,
20-
content: null
35+
content: ['.FakeComponent']
2136
}).catch((err: string) => {
2237
t.true(err.includes('TypeError: Path must be a string'));
2338
});
2439
});
40+
41+
test('throws if there is not a content', async (t) => {
42+
const cssFileName = path.join(__dirname, 'postcss/fakeComponent2.postcss');
43+
saveFile({
44+
cssFileName,
45+
content: null
46+
}).catch((err: string) => {
47+
t.true(err.includes('TypeError: Cannot convert undefined or null to object'));
48+
});
49+
});

0 commit comments

Comments
 (0)