Skip to content

Commit a950257

Browse files
authored
feat(react-icons): enable griffel unprocessed (styles.raw.js) output for production assets (#858)
* feat(react-icons): decouple griffel styles to follow .styles pattern * feat(react-icons): encapsulate ts->js transpilation and add .styles.raw.js output * style: remove unused imports * fix: decouple griffel styles to follow .styles pattern after rebase #855 * chore: additional cleanup of npm script, babel * chore(react-icons): invoke ts via npx to target correct bin * chore: update repo pkg-lock * chore: run babel on all tsc emmited js files to retain same production outputs * test(react-icons): add integration tests for raw and non raw styles and enable skipped tests * refactor: cleanup build.js
1 parent 77db24c commit a950257

14 files changed

+378
-150
lines changed

package-lock.json

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-icons/.babelrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
{
2-
"presets": ["@griffel"],
32
"compact": false
43
}

packages/react-icons/build-verify.test.js

Lines changed: 86 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-icons/build.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// @ts-check
2+
// Copyright (c) Microsoft Corporation.
3+
// Licensed under the MIT license.
4+
5+
const { execSync } = require('node:child_process');
6+
const { copyFileSync, readFileSync, writeFileSync } = require('node:fs');
7+
const { join, basename } = require('node:path');
8+
9+
const glob = require('glob');
10+
const { transformSync } = require('@babel/core');
11+
12+
main();
13+
14+
/**
15+
* Builds source TypeScript and copys assets to the output directories.
16+
*
17+
* It transpiles TypeScript files to both ESNext and CommonJS formats,
18+
* applies Babel transformations, and copies font assets.
19+
* It also creates raw style copies for .styles.js files.
20+
*/
21+
function main(){
22+
const projectRoot = __dirname;
23+
24+
transpileTsc({ moduleFormat: 'esnext', outDir: 'lib' },projectRoot);
25+
transpileTsc({ moduleFormat: 'commonjs', outDir: 'lib-cjs' },projectRoot);
26+
27+
applyBabelTransform('lib', projectRoot);
28+
applyBabelTransform('lib-cjs', projectRoot);
29+
30+
copyAssets('src/utils/fonts/*.{ttf,woff,woff2,json}', './lib/utils/fonts', projectRoot);
31+
copyAssets('src/utils/fonts/*.{ttf,woff,woff2,json}', './lib-cjs/utils/fonts', projectRoot);
32+
33+
}
34+
35+
// =================================================================================================
36+
37+
/**
38+
*
39+
* @param {string} styleFile
40+
*/
41+
42+
function createRawStylesCopy(styleFile) {
43+
44+
const sourcePath = join(styleFile);
45+
const targetPath = styleFile.replace('.styles.js', '.styles.raw.js');
46+
47+
try {
48+
copyFileSync(sourcePath, targetPath);
49+
console.log(` ✓ [raw style] ${styleFile} -> ${basename(targetPath)}`);
50+
} catch (error) {
51+
console.error(` ✗ Failed to copy ${styleFile}:`, error.message);
52+
}
53+
54+
}
55+
56+
/**
57+
*
58+
* @param {{moduleFormat:'esnext'|'commonjs'; outDir:string}} options
59+
* @param {string} baseDir
60+
*/
61+
function transpileTsc(options, baseDir) {
62+
const {moduleFormat,outDir} = options;
63+
console.log(`Transpiling module format [${moduleFormat}] to -> ${outDir}/`);
64+
const cmd = `npx tsc -p . --module ${moduleFormat} --outDir ${outDir}`;
65+
return execSync(cmd, {stdio: 'inherit', cwd: baseDir});
66+
}
67+
68+
/**
69+
*
70+
* @param {string} root
71+
* @param {string} baseDir
72+
*/
73+
function applyBabelTransform(root, baseDir) {
74+
const EOL_REGEX = /\r?\n/g;
75+
const griffelPreset = [
76+
['@griffel']
77+
];
78+
79+
const jsRoot = join(baseDir,root)
80+
81+
console.log(`Processing .js files with babel in ${jsRoot}:`);
82+
const jsFiles = glob.sync('**/*.js', { cwd: jsRoot });
83+
84+
85+
for (const filename of jsFiles) {
86+
const isStylesFile = filename.endsWith('.styles.js');
87+
const filePath = join(jsRoot, filename);
88+
89+
if (isStylesFile) {
90+
createRawStylesCopy(filePath)
91+
};
92+
93+
const codeBuffer = readFileSync(filePath);
94+
const sourceCode = codeBuffer.toString().replace(EOL_REGEX, '\n');
95+
96+
const result = transformSync(sourceCode, {
97+
ast: false,
98+
sourceMaps: true,
99+
babelrc: true,
100+
// to avoid leaking of global configs
101+
babelrcRoots: [baseDir],
102+
filename: filePath,
103+
// Only apply @griffel preset to .styles.js files
104+
presets: isStylesFile ? griffelPreset : [],
105+
});
106+
107+
const resultCode = result?.code;
108+
109+
if (!resultCode) {
110+
console.warn(`No code generated for ${filename}. Skipping.`);
111+
continue;
112+
}
113+
114+
const prefix = isStylesFile ? ' ✓ [griffel]' : ' ✓ [babel]';
115+
console.log(`${prefix} ${filename}`);
116+
writeFileSync(filePath, resultCode);
117+
}
118+
}
119+
120+
/**
121+
*
122+
* @param {string} src - globs of files to copy
123+
* @param {string} dest
124+
* @param {string} baseDir
125+
*/
126+
function copyAssets(src, dest, baseDir){
127+
128+
const assets = glob.sync(src, { cwd: baseDir });
129+
console.log(`Copying ${src} assets to -> ${dest}:`);
130+
131+
assets.forEach(file => {
132+
const sourcePath = join(baseDir, file);
133+
const targetPath = join(baseDir,dest, basename(file));
134+
135+
try {
136+
copyFileSync(sourcePath, targetPath);
137+
console.log(` ✓ ${file}`);
138+
} catch (error) {
139+
console.error(` ✗ Failed to copy ${file}:`, error.message);
140+
}
141+
});
142+
}
143+

packages/react-icons/package.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"scripts": {
1515
"clean": "git clean -fXd lib/ lib-cjs/ intermediate/ src/",
1616
"copy": "node ../../importer/generate.js --source=../../assets --dest=./intermediate --extension=svg --target=react",
17-
"copy:font-files": "cpy './src/utils/fonts/*.{ttf,woff,woff2,json}' ./lib/utils/fonts/. && cpy './src/utils/fonts/*.{ttf,woff,woff2,json}' ./lib-cjs/utils/fonts/.",
1817
"convert:svg": "node convert.js --source=./intermediate --dest=./src --rtl=./intermediate/rtl.json",
1918
"convert:fonts": "node convert-font.js --source=./src/utils/fonts --dest=./src/fonts --codepointDest=./src/utils/fonts --rtl=./intermediate/rtl.json",
2019
"generate:font-regular": "node ../../importer/generateFont.js --source=intermediate --dest=src/utils/fonts --iconType=Regular --codepoints=../../fonts/FluentSystemIcons-Regular.json",
@@ -25,23 +24,18 @@
2524
"generate:rtl": "node ../../importer/rtlMetadata.js --source=../../assets --dest=./intermediate/rtl.json",
2625
"optimize": "svgo --config svgo.config.js --folder=./intermediate --precision=2 --quiet",
2726
"unfill": "find ./intermediate -type f -name \"*.svg\" ! -name \"*_color*\" -exec sed -i.bak 's/fill=\"none\"//g' {} \\; && find ./intermediate -type f -name \"*.bak\" -delete",
28-
"build:cjs": "tsc -p . --module commonjs --outDir lib-cjs && babel lib-cjs --out-dir lib-cjs",
29-
"build:esm": "tsc -p . && babel lib --out-dir lib",
3027
"build:fonts-and-svg": "npm run copy && npm run generate:font && npm run generate:rtl && npm run optimize && npm run unfill",
3128
"build:generate-js-chunks": "npm run convert:svg && npm run convert:fonts",
32-
"build:js": "npm run build:cjs && npm run build:esm",
29+
"build:js": "node build.js",
3330
"prebuild": "npm run clean",
34-
"build": "npm run build:fonts-and-svg && npm run build:generate-js-chunks && npm run build:js && npm run copy:font-files",
31+
"build": "npm run build:fonts-and-svg && npm run build:generate-js-chunks && npm run build:js",
3532
"build:verify": "vitest run build-verify.test.js",
3633
"test": "vitest --exclude build-verify.test.js"
3734
},
3835
"devDependencies": {
39-
"@babel/cli": "^7.16.0",
4036
"@babel/core": "^7.16.0",
41-
"@babel/preset-typescript": "^7.16.7",
4237
"@griffel/babel-preset": "^1.0.0",
4338
"@types/react": "^17.0.2",
44-
"cpy-cli": "^4.1.0",
4539
"glob": "^7.2.0",
4640
"lodash": "^4.17.21",
4741
"mkdirp": "^1.0.4",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { makeStyles } from "@griffel/react";
2+
3+
4+
export const useBundledIconStyles = makeStyles({
5+
root: { display: "none" },
6+
visible: { display: "inline" }
7+
});

packages/react-icons/src/utils/bundleIcon.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import * as React from "react";
2+
import { mergeClasses } from "@griffel/react";
3+
24
import { iconFilledClassName, iconRegularClassName } from "./constants";
3-
import { makeStyles, mergeClasses } from "@griffel/react";
45
import { FluentIcon } from "./createFluentIcon";
6+
import { useBundledIconStyles } from "./bundleIcon.styles";
57

6-
const useBundledIconStyles = makeStyles({
7-
root: { display: "none" },
8-
visible: { display: "inline" }
9-
});
108

119
const bundleIcon = (FilledIcon: FluentIcon, RegularIcon: FluentIcon) => {
1210
const Component: FluentIcon = (props) => {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { makeStyles } from "@griffel/react";
2+
3+
export const useRootStyles = makeStyles({
4+
root: {
5+
// This style is required because browsers automatically have SVGs set to
6+
// forced-color-adjust: preserve-parent-color, which will not override
7+
// internally-defined colors in our SVG icons in high contrast mode.
8+
"@media (forced-colors: active)": {
9+
forcedColorAdjust: "auto",
10+
},
11+
},
12+
});

packages/react-icons/src/utils/createFluentIcon.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from "react";
2+
import { mergeClasses } from "@griffel/react";
23
import { FluentIconsProps } from "./FluentIconsProps.types";
34
import { useIconState } from "./useIconState";
4-
import { makeStyles, mergeClasses } from "@griffel/react";
5+
import { useRootStyles } from "./createFluentIcon.styles";
56

67
export type FluentIcon = {
78
(props: FluentIconsProps): JSX.Element;
@@ -13,16 +14,7 @@ export type CreateFluentIconOptions = {
1314
color?: boolean;
1415
}
1516

16-
const useRootStyles = makeStyles({
17-
root: {
18-
// This style is required because browsers automatically have SVGs set to
19-
// forced-color-adjust: preserve-parent-color, which will not override
20-
// internally-defined colors in our SVG icons in high contrast mode.
21-
"@media (forced-colors: active)": {
22-
forcedColorAdjust: "auto",
23-
},
24-
},
25-
});
17+
2618

2719
export const createFluentIcon = (displayName: string, width: string, pathsOrSvg: string[] | string, options?: CreateFluentIconOptions): FluentIcon => {
2820
const styles = useRootStyles();

0 commit comments

Comments
 (0)