Skip to content

Commit 063ab31

Browse files
committed
feat(export-type): add support for default export types
1 parent 65fed40 commit 063ab31

File tree

9 files changed

+131
-26
lines changed

9 files changed

+131
-26
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,52 @@
1-
import { classNamesToTypeDefinitions } from "../../lib/typescript";
1+
import { classNamesToTypeDefinitions, ExportType } from "../../lib/typescript";
22

33
describe("classNamesToTypeDefinitions", () => {
4-
it("converts an array of class name strings to type definitions", () => {
5-
const definition = classNamesToTypeDefinitions(["myClass", "yourClass"]);
4+
describe("named", () => {
5+
it("converts an array of class name strings to type definitions", () => {
6+
const definition = classNamesToTypeDefinitions(
7+
["myClass", "yourClass"],
8+
"named"
9+
);
610

7-
expect(definition).toEqual(
8-
"export const myClass: string;\nexport const yourClass: string;\n"
9-
);
11+
expect(definition).toEqual(
12+
"export const myClass: string;\nexport const yourClass: string;\n"
13+
);
14+
});
15+
16+
it("returns null if there are no class names", () => {
17+
const definition = classNamesToTypeDefinitions([], "named");
18+
19+
expect(definition).toBeNull;
20+
});
21+
});
22+
23+
describe("default", () => {
24+
it("converts an array of class name strings to type definitions", () => {
25+
const definition = classNamesToTypeDefinitions(
26+
["myClass", "yourClass"],
27+
"default"
28+
);
29+
30+
expect(definition).toEqual(
31+
"interface Styles {\n 'myClass': string;\n 'yourClass': string;\n}\n\ndeclare const styles: Styles;\n\nexport default styles;\n"
32+
);
33+
});
34+
35+
it("returns null if there are no class names", () => {
36+
const definition = classNamesToTypeDefinitions([], "default");
37+
38+
expect(definition).toBeNull;
39+
});
1040
});
1141

12-
it("returns null if there are no class names", () => {
13-
const definition = classNamesToTypeDefinitions([]);
42+
describe("invalid export type", () => {
43+
it("returns null", () => {
44+
const definition = classNamesToTypeDefinitions(
45+
["myClass"],
46+
"invalid" as ExportType
47+
);
1448

15-
expect(definition).toBeNull;
49+
expect(definition).toBeNull;
50+
});
1651
});
1752
});

examples/basic/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# Basic Example
22

3-
This example has several pieces:
3+
This example contains:
44

55
- Core variables (`core/variables.scss`) which contains things like colors, etc. To make the import of these variables easier, it's expected that this directory is included in the search path. This demonstrates the need for `includePaths`.
66
- An alias. This is most common using a [webpack alias](https://webpack.js.org/configuration/resolve/#resolve-alias). This demonstrates the need for `aliases`.
77

8-
The command to generate the proper type files could look like this (_in the root of this repository_):
8+
The command to generate the proper type files would look like this (_in the root of this repository_):
99

1010
```bash
11-
yarn ts-node ./lib/cli.ts "examples/basic/**/*.scss" --includePaths examples/basic/core --aliases.~alias variables
11+
yarn tsm "examples/basic/**/*.scss" --includePaths examples/basic/core --aliases.~alias variables
1212
```
1313

1414
- The glob pattern is wrapped in quotes to pass it as a string and avoid executing.
1515
- `includePaths` with `examples/basic/core` so that `@import 'variables'` is found.
1616
- `aliases` with `~alias: variables` meaning any `@import '~alias'` resolves to `@import 'variables'`.
17-
- No file will be output for `variables.scss` since there are no classes.
17+
- No file will be output for `variables.scss` since there are no classes.

examples/default-export/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Default Export Example
2+
3+
This example contains:
4+
5+
- Class names that are expected to be kebab (param) cased. Since variables cannot contain a `-` this can be achieved using an interface with default export.
6+
- Class names that are TypeScript keywords (eg: `while`) that cannot be used as named constants.
7+
8+
The command to generate the proper type files would look like this (_in the root of this repository_):
9+
10+
```bash
11+
yarn tsm "examples/default-export/**/*.scss" --exportType default --nameFormat kebab
12+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import styles from "./style.scss";
2+
3+
console.log(styles.i);
4+
console.log(styles["i-am-kebab-cased"]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.i {
2+
color: orange;
3+
4+
&-am {
5+
&-kebab {
6+
&-cased {
7+
color: red;
8+
}
9+
}
10+
}
11+
}
12+
13+
.while {
14+
color: blue;
15+
}

lib/cli.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import yargs from "yargs";
44

55
import { Aliases, NAME_FORMATS, NameFormat } from "./sass";
6+
import { ExportType, EXPORT_TYPES } from "./typescript";
67
import { main } from "./main";
78

89
const nameFormatDefault: NameFormat = "camel";
10+
const exportTypeDefault: ExportType = "named";
911

10-
const { _: patterns, includePaths, aliases, nameFormat } = yargs
12+
const { _: patterns, includePaths, aliases, nameFormat, exportType } = yargs
1113
.usage(
1214
"Generate .scss.d.ts from CSS module .scss files.\nUsage: $0 <glob pattern> [options]"
1315
)
@@ -36,11 +38,17 @@ const { _: patterns, includePaths, aliases, nameFormat } = yargs
3638
alias: "n",
3739
describe: "The name format that should be used to transform class names."
3840
})
41+
.option("exportType", {
42+
choices: EXPORT_TYPES,
43+
default: exportTypeDefault,
44+
alias: "e",
45+
describe: "The type of export used for defining the type defintions."
46+
})
3947
.option("includePaths", {
4048
array: true,
4149
string: true,
4250
alias: "i",
4351
describe: "Additional paths to include when trying to resolve imports."
4452
}).argv;
4553

46-
main(patterns[0], { includePaths, aliases, nameFormat });
54+
main(patterns[0], { includePaths, aliases, nameFormat, exportType });

lib/main.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ import glob from "glob";
55
import chalk from "chalk";
66

77
import { Options, fileToClassNames } from "./sass";
8-
import { classNamesToTypeDefinitions } from "./typescript";
8+
import { classNamesToTypeDefinitions, ExportType } from "./typescript";
9+
10+
interface MainOptions extends Options {
11+
exportType: ExportType;
12+
}
913

1014
const error = (message: string) => console.log(chalk.red(`[ERROR] ${message}`));
1115
const warn = (message: string) => console.log(chalk.yellowBright(`${message}`));
1216
const notice = (message: string) => console.log(chalk.gray(`${message}`));
1317
const success = (message: string) => console.log(chalk.green(message));
1418

15-
export const main = (pattern: string, options: Options): void => {
19+
export const main = (pattern: string, options: MainOptions): void => {
1620
// When the provided pattern is a directory construct the proper glob to find
1721
// all .scss files within that directory. Also, add the directory to the
1822
// included paths so any imported with a path relative to the root of the
@@ -28,8 +32,7 @@ export const main = (pattern: string, options: Options): void => {
2832
pattern = path.resolve(pattern, "**/*.scss");
2933
}
3034

31-
// Find all the files that match the provied pattern. Always ignore
32-
// node_modules anywhere in the directory tree.
35+
// Find all the files that match the provied pattern.
3336
const files = glob.sync(pattern);
3437

3538
if (!files || !files.length) {
@@ -52,7 +55,10 @@ export const main = (pattern: string, options: Options): void => {
5255

5356
fileToClassNames(file, options)
5457
.then(classNames => {
55-
const typeDefinition = classNamesToTypeDefinitions(classNames);
58+
const typeDefinition = classNamesToTypeDefinitions(
59+
classNames,
60+
options.exportType
61+
);
5662
const path = `${file}.d.ts`;
5763

5864
if (!typeDefinition) {

lib/typescript/class-names-to-type-definition.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
import { ClassNames, ClassName } from "lib/sass/file-to-class-names";
22

3-
const classNameToTypeDefinition = (className: ClassName) =>
3+
export type ExportType = "named" | "default";
4+
export const EXPORT_TYPES: ExportType[] = ["named", "default"];
5+
6+
const classNameToNamedTypeDefinition = (className: ClassName) =>
47
`export const ${className}: string;`;
58

9+
const classNameToInterfaceKey = (className: ClassName) =>
10+
` '${className}': string;`;
11+
612
export const classNamesToTypeDefinitions = (
7-
classNames: ClassNames
13+
classNames: ClassNames,
14+
exportType: ExportType
815
): string | null => {
916
if (classNames.length) {
10-
const typeDefinitions = classNames.map(classNameToTypeDefinition);
17+
let typeDefinitions;
18+
19+
switch (exportType) {
20+
case "default":
21+
typeDefinitions = "interface Styles {\n";
22+
typeDefinitions += classNames.map(classNameToInterfaceKey).join("\n");
23+
typeDefinitions += "\n}\n\n";
24+
typeDefinitions += "declare const styles: Styles;\n\n";
25+
typeDefinitions += "export default styles;\n";
26+
return typeDefinitions;
27+
case "named":
28+
typeDefinitions = classNames.map(classNameToNamedTypeDefinition);
1129

12-
// Sepearte all type definitions be a newline with a trailing newline.
13-
return typeDefinitions.join("\n") + "\n";
30+
// Sepearte all type definitions be a newline with a trailing newline.
31+
return typeDefinitions.join("\n") + "\n";
32+
default:
33+
return null;
34+
}
1435
} else {
1536
return null;
1637
}

lib/typescript/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
export { classNamesToTypeDefinitions } from "./class-names-to-type-definition"
1+
export {
2+
classNamesToTypeDefinitions,
3+
ExportType,
4+
EXPORT_TYPES
5+
} from "./class-names-to-type-definition";

0 commit comments

Comments
 (0)