Skip to content

Commit d4fb041

Browse files
committed
Great icon integration
1 parent 414ecf1 commit d4fb041

File tree

11 files changed

+968
-86
lines changed

11 files changed

+968
-86
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"css_to_ts": "node dist/bin/css_to_ts"
2626
},
2727
"bin": {
28-
"copy_dsfr_dist_to_public": "dist/bin/copy_dsfr_dist_to_public.js"
28+
"copy_dsfr_dist_to_public": "dist/bin/copy_dsfr_dist_to_public.js",
29+
"only_include_used_icons": "dist/bin/only_include_used_icons.js"
2930
},
3031
"lint-staged": {
3132
"*.{ts,tsx}": [
@@ -69,6 +70,7 @@
6970
},
7071
"dependencies": {
7172
"@gouvfr/dsfr": "1.7.2",
73+
"remixicon": "^2.5.0",
7274
"tsafe": "^1.1.1"
7375
},
7476
"devDependencies": {

src/bin/css_to_ts/classNames.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,29 @@ export const parseClassNames = memoize((rawCssCode: string): string[] => {
2626
return Array.from(classes);
2727
});
2828

29-
export function generateClassNamesTsCode(rawCssCode: string): string {
29+
export function generateClassNamesTsCode(params: {
30+
rawCssCode: string;
31+
dsfrIconClassNames: string[];
32+
remixiconClassNames: string[];
33+
}): string {
34+
const { rawCssCode, dsfrIconClassNames, remixiconClassNames } = params;
35+
3036
const classNames = parseClassNames(rawCssCode);
3137

3238
return [
33-
`export const frClassNames= ${JSON.stringify(classNames, null, 4)} as const;`,
39+
`export const frCoreClassNames= ${JSON.stringify(classNames, null, 4)} as const;`,
40+
``,
41+
`export type FrCoreClassName = typeof frCoreClassNames[number];`,
42+
``,
43+
`export const frIconClassNames= ${JSON.stringify(dsfrIconClassNames, null, 4)} as const;`,
44+
``,
45+
`export type FrIconClassName = typeof frIconClassNames[number];`,
46+
``,
47+
`export const riIconClassNames= ${JSON.stringify(remixiconClassNames, null, 4)} as const;`,
48+
``,
49+
`export type RiIconClassName = typeof riIconClassNames[number];`,
3450
``,
35-
`export type FrClassName = typeof frClassNames[number];`,
51+
`export type FrClassName = FrCoreClassName | frIconClassNames | RiIconClassName;`,
3652
``
3753
].join("\n");
3854
}

src/bin/css_to_ts/css_to_ts.ts

Lines changed: 186 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -6,84 +6,189 @@ import { generateTypographyTsCode } from "./typography";
66
import { generateSpacingTsCode } from "./spacing";
77
import { generateClassNamesTsCode } from "./classNames";
88
import * as fs from "fs";
9-
import { join as pathJoin, basename as pathBasename, relative as pathRelative } from "path";
10-
11-
const projectRoot = getProjectRoot();
12-
13-
const rawCssCode = fs.readFileSync(pathJoin(projectRoot, "dsfr", "dsfr.css")).toString("utf8");
14-
15-
const generatedDirPath = pathJoin(projectRoot, "src", "lib", "generatedFromCss");
16-
17-
fs.mkdirSync(generatedDirPath, { "recursive": true });
18-
19-
const warningMessage = [
20-
`// This file is generated automatically by ${pathRelative(
21-
projectRoot,
22-
__filename
23-
)}, please don't edit.`
24-
].join("\n");
25-
26-
const targetOptionFilePath = pathJoin(generatedDirPath, "getColorOptions.ts");
27-
28-
fs.writeFileSync(
29-
targetOptionFilePath,
30-
Buffer.from(
31-
[
32-
warningMessage,
33-
``,
34-
generateGetColorOptionsTsCode(rawCssCode),
35-
``,
36-
`export type ColorOptions = ReturnType<typeof getColorOptions>;`,
37-
``
38-
].join("\n"),
39-
"utf8"
40-
)
41-
);
42-
43-
fs.writeFileSync(
44-
pathJoin(generatedDirPath, "getColorDecisions.ts"),
45-
Buffer.from(
46-
[
47-
warningMessage,
48-
`import type { ColorOptions } from "./${pathBasename(targetOptionFilePath).replace(
49-
/\.ts$/,
50-
""
51-
)}";`,
52-
``,
53-
generateGetColorDecisionsTsCode(rawCssCode),
54-
``,
55-
`export type ColorDecisions = ReturnType<typeof getColorDecisions>;`,
56-
``
57-
].join("\n"),
58-
"utf8"
59-
)
60-
);
61-
62-
fs.writeFileSync(
63-
pathJoin(generatedDirPath, "breakpoints.ts"),
64-
Buffer.from([warningMessage, ``, generateBreakpointsTsCode(rawCssCode)].join("\n"), "utf8")
65-
);
66-
67-
fs.writeFileSync(
68-
pathJoin(generatedDirPath, "typography.ts"),
69-
Buffer.from(
70-
[
71-
warningMessage,
72-
`import { breakpoints } from "../breakpoints";`,
73-
``,
74-
generateTypographyTsCode(rawCssCode),
75-
``
76-
].join("\n"),
77-
"utf8"
78-
)
79-
);
80-
81-
fs.writeFileSync(
82-
pathJoin(generatedDirPath, "spacing.ts"),
83-
Buffer.from([warningMessage, ``, generateSpacingTsCode(rawCssCode), ``].join("\n"), "utf8")
84-
);
85-
86-
fs.writeFileSync(
87-
pathJoin(generatedDirPath, "classNames.ts"),
88-
Buffer.from([warningMessage, ``, generateClassNamesTsCode(rawCssCode), ``].join("\n"), "utf8")
89-
);
9+
import { readFile } from "fs/promises";
10+
import {
11+
join as pathJoin,
12+
basename as pathBasename,
13+
relative as pathRelative,
14+
dirname as pathDirname
15+
} from "path";
16+
import { crawl } from "../tools/crawl";
17+
import { id } from "tsafe/id";
18+
19+
export async function main() {
20+
const projectRoot = getProjectRoot();
21+
22+
const dsfrDistDirPath = pathJoin(projectRoot, "dsfr");
23+
24+
const rawCssCode = fs.readFileSync(pathJoin(dsfrDistDirPath, "dsfr.css")).toString("utf8");
25+
26+
const generatedDirPath = pathJoin(projectRoot, "src", "lib", "generatedFromCss");
27+
28+
fs.mkdirSync(generatedDirPath, { "recursive": true });
29+
30+
const warningMessage = [
31+
`// This file is generated automatically by ${pathRelative(
32+
projectRoot,
33+
__filename
34+
)}, please don't edit.`
35+
].join("\n");
36+
37+
const targetOptionFilePath = pathJoin(generatedDirPath, "getColorOptions.ts");
38+
39+
fs.writeFileSync(
40+
targetOptionFilePath,
41+
Buffer.from(
42+
[
43+
warningMessage,
44+
``,
45+
generateGetColorOptionsTsCode(rawCssCode),
46+
``,
47+
`export type ColorOptions = ReturnType<typeof getColorOptions>;`,
48+
``
49+
].join("\n"),
50+
"utf8"
51+
)
52+
);
53+
54+
fs.writeFileSync(
55+
pathJoin(generatedDirPath, "getColorDecisions.ts"),
56+
Buffer.from(
57+
[
58+
warningMessage,
59+
`import type { ColorOptions } from "./${pathBasename(targetOptionFilePath).replace(
60+
/\.ts$/,
61+
""
62+
)}";`,
63+
``,
64+
generateGetColorDecisionsTsCode(rawCssCode),
65+
``,
66+
`export type ColorDecisions = ReturnType<typeof getColorDecisions>;`,
67+
``
68+
].join("\n"),
69+
"utf8"
70+
)
71+
);
72+
73+
fs.writeFileSync(
74+
pathJoin(generatedDirPath, "breakpoints.ts"),
75+
Buffer.from([warningMessage, ``, generateBreakpointsTsCode(rawCssCode)].join("\n"), "utf8")
76+
);
77+
78+
fs.writeFileSync(
79+
pathJoin(generatedDirPath, "typography.ts"),
80+
Buffer.from(
81+
[
82+
warningMessage,
83+
`import { breakpoints } from "../breakpoints";`,
84+
``,
85+
generateTypographyTsCode(rawCssCode),
86+
``
87+
].join("\n"),
88+
"utf8"
89+
)
90+
);
91+
92+
fs.writeFileSync(
93+
pathJoin(generatedDirPath, "spacing.ts"),
94+
Buffer.from([warningMessage, ``, generateSpacingTsCode(rawCssCode), ``].join("\n"), "utf8")
95+
);
96+
97+
fs.writeFileSync(
98+
pathJoin(generatedDirPath, "classNames.ts"),
99+
Buffer.from(
100+
[
101+
warningMessage,
102+
``,
103+
generateClassNamesTsCode({
104+
rawCssCode,
105+
106+
...(await (async () => {
107+
const icons = await collectIcons({
108+
dsfrDistDirPath,
109+
"remixiconDirPath": pathJoin(projectRoot, "node_modules", "remixicon")
110+
});
111+
112+
return {
113+
"dsfrIconClassNames": icons
114+
.filter(({ prefix }) => prefix === "fr-icon-")
115+
.map(({ iconId, prefix }) => `${prefix}${iconId}`),
116+
"remixiconClassNames": icons
117+
.filter(({ prefix }) => prefix === "ri-")
118+
.map(({ iconId, prefix }) => `${prefix}${iconId}`)
119+
};
120+
})())
121+
}),
122+
``
123+
].join("\n"),
124+
"utf8"
125+
)
126+
);
127+
}
128+
129+
export type Icon = Icon.Dsfr | Icon.Remixicon;
130+
131+
export namespace Icon {
132+
export type Common = {
133+
iconId: string;
134+
};
135+
136+
export type Dsfr = Common & {
137+
prefix: "fr-icon-";
138+
category: string;
139+
};
140+
141+
export type Remixicon = Common & {
142+
prefix: "ri-";
143+
rawSvgCode: string;
144+
};
145+
}
146+
147+
export async function collectIcons(params: {
148+
dsfrDistDirPath: string;
149+
remixiconDirPath: string;
150+
}): Promise<Icon[]> {
151+
const { dsfrDistDirPath, remixiconDirPath } = params;
152+
153+
return (
154+
await Promise.all([
155+
(async () => {
156+
const iconDirPath = pathJoin(remixiconDirPath, "icons");
157+
158+
return Promise.all(
159+
(await crawl({ "dirPath": iconDirPath }))
160+
.filter(filePath => filePath.endsWith(".svg"))
161+
.map(async svgFilePath =>
162+
id<Icon.Remixicon>({
163+
"prefix": "ri-",
164+
"iconId": pathBasename(svgFilePath),
165+
"rawSvgCode": (
166+
await readFile(pathJoin(iconDirPath, svgFilePath))
167+
).toString("utf8")
168+
})
169+
)
170+
);
171+
})(),
172+
(async () =>
173+
(
174+
await crawl({
175+
"dirPath": pathJoin(dsfrDistDirPath, "icons"),
176+
"getDoCrawlInDir": ({ relativeDirPath }) =>
177+
pathBasename(relativeDirPath) !== "remixicon"
178+
})
179+
)
180+
.filter(filePath => filePath.endsWith(".svg"))
181+
.map(svgFilePath =>
182+
id<Icon.Dsfr>({
183+
"prefix": "fr-icon-",
184+
"category": pathBasename(pathDirname(svgFilePath)),
185+
"iconId": pathBasename(svgFilePath)
186+
})
187+
))()
188+
])
189+
).flat();
190+
}
191+
192+
if (require.main === module) {
193+
main();
194+
}

0 commit comments

Comments
 (0)